feat(frontend): validate username and member name client-side too
This commit is contained in:
parent
6532393578
commit
5be0b168c5
3 changed files with 37 additions and 13 deletions
|
@ -75,7 +75,7 @@ func (s *Server) createMember(w http.ResponseWriter, r *http.Request) (err error
|
||||||
if !db.MemberNameValid(cmr.Name) {
|
if !db.MemberNameValid(cmr.Name) {
|
||||||
return server.APIError{
|
return server.APIError{
|
||||||
Code: server.ErrBadRequest,
|
Code: server.ErrBadRequest,
|
||||||
Details: "Member name cannot contain any of the following: @, \\, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), +, <, =, >, ^, |, ~, `, ,",
|
Details: "Member name cannot contain any of the following: @, ?, !, #, /, \\, [, ], \", ', $, %, &, (, ), +, <, =, >, ^, |, ~, `, ,",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,10 +58,13 @@
|
||||||
memberPage = memberPage + 1;
|
memberPage = memberPage + 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const memberNameRegex = /^[^@\\?!#\/\\\\[\]\"'$%&()+<=>^|~`,]{1,100}$/;
|
||||||
let modalOpen = false;
|
let modalOpen = false;
|
||||||
let toggleModal = () => (modalOpen = !modalOpen);
|
let toggleModal = () => (modalOpen = !modalOpen);
|
||||||
let newMemberName = "";
|
let newMemberName = "";
|
||||||
let newMemberError: APIError | null = null;
|
let newMemberError: APIError | null = null;
|
||||||
|
let memberNameValid = true;
|
||||||
|
$: memberNameValid = memberNameRegex.test(newMemberName);
|
||||||
|
|
||||||
const createMember = async () => {
|
const createMember = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -210,10 +213,8 @@
|
||||||
<p>
|
<p>
|
||||||
You don't have any members yet.
|
You don't have any members yet.
|
||||||
<br />
|
<br />
|
||||||
Members are sub-profiles that can have their own avatar,
|
Members are sub-profiles that can have their own avatar, names, pronouns, and preferred terms.
|
||||||
names, pronouns, and preferred terms. <span class="text-muted"
|
<span class="text-muted">(only you can see this)</span>
|
||||||
>(only you can see this)</span
|
|
||||||
>
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -221,12 +222,20 @@
|
||||||
<Modal header="Create member" isOpen={modalOpen} toggle={toggleModal}>
|
<Modal header="Create member" isOpen={modalOpen} toggle={toggleModal}>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Input bind:value={newMemberName} />
|
<Input bind:value={newMemberName} />
|
||||||
|
<p class="text-muted my-2">
|
||||||
|
<Icon name="info-circle-fill" aria-label="Info" /> Your members must have distinct names. Member
|
||||||
|
names must be 100 characters long at most, and cannot contain the following characters: @ ?
|
||||||
|
! # / \ [ ] " ' $ % & ( ) + < = > ^ | ~ ` and ,
|
||||||
|
</p>
|
||||||
{#if newMemberError}
|
{#if newMemberError}
|
||||||
<ErrorAlert error={newMemberError} />
|
<ErrorAlert error={newMemberError} />
|
||||||
{/if}
|
{/if}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button color="primary" on:click={createMember} disabled={newMemberName.length === 0}
|
{#if !memberNameValid && newMemberName.length > 0}
|
||||||
|
<span class="text-danger-emphasis mb-2">That member name is not valid.</span>
|
||||||
|
{/if}
|
||||||
|
<Button color="primary" on:click={createMember} disabled={!memberNameValid}
|
||||||
>Create member</Button
|
>Create member</Button
|
||||||
>
|
>
|
||||||
<Button color="secondary" on:click={toggleModal}>Cancel</Button>
|
<Button color="secondary" on:click={toggleModal}>Cancel</Button>
|
||||||
|
|
|
@ -43,7 +43,11 @@
|
||||||
let deleteCancelled: boolean;
|
let deleteCancelled: boolean;
|
||||||
let deleteError: APIError | null;
|
let deleteError: APIError | null;
|
||||||
|
|
||||||
|
const usernameRegex = /^[\w-.]{2,40}$/;
|
||||||
let username: string;
|
let username: string;
|
||||||
|
let usernameValid = true;
|
||||||
|
$: usernameValid = usernameRegex.test(username);
|
||||||
|
|
||||||
let inviteCode: string;
|
let inviteCode: string;
|
||||||
let forceDeleteName = "";
|
let forceDeleteName = "";
|
||||||
let forceDeleteModalOpen = false;
|
let forceDeleteModalOpen = false;
|
||||||
|
@ -88,10 +92,10 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Log in with the {authType} - pronouns.cc</title>
|
<title>Log in with {authType} - pronouns.cc</title>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<h1>Log in with the {authType}</h1>
|
<h1>Log in with {authType}</h1>
|
||||||
|
|
||||||
{#if error}
|
{#if error}
|
||||||
<ErrorAlert {error} />
|
<ErrorAlert {error} />
|
||||||
|
@ -122,23 +126,34 @@
|
||||||
<FormGroup floating label="Username">
|
<FormGroup floating label="Username">
|
||||||
<Input id="username" name="username" bind:value={username} />
|
<Input id="username" name="username" bind:value={username} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
<div id="username-help" class="text-muted">
|
||||||
|
<Icon name="info-circle-fill" aria-label="Info" /> Your username must be unique, be at most 40
|
||||||
|
characters long, and only contain letters from the basic English alphabet, dashes, underscores,
|
||||||
|
and periods. Your username is used as part of your profile link, you can set a separate display
|
||||||
|
name.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if requireInvite}
|
{#if requireInvite}
|
||||||
<div>
|
<div>
|
||||||
<FormGroup floating label="Invite code">
|
<FormGroup floating label="Invite code">
|
||||||
<Input id="invite" name="invite" aria-describedby="invite-help" bind:value={inviteCode} />
|
<Input id="invite" name="invite" aria-describedby="invite-help" bind:value={inviteCode} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<div id="invite-help" class="form-text">
|
<div id="invite-help" class="text-muted">
|
||||||
<Icon name="info-circle-fill" /> You currently need an invite code to sign up. You can get
|
<Icon name="info-circle-fill" aria-label="Info" /> You currently need an invite code to sign
|
||||||
one from an existing user.
|
up. You can get one from an existing user.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="form-text mb-1">
|
<div class="text-muted my-1">
|
||||||
By signing up, you agree to the <a href="/page/terms">terms of service</a> and the
|
By signing up, you agree to the <a href="/page/terms">terms of service</a> and the
|
||||||
<a href="/page/privacy">privacy policy</a>.
|
<a href="/page/privacy">privacy policy</a>.
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" color="primary">Sign up</Button>
|
<p>
|
||||||
|
<Button type="submit" color="primary" disabled={!usernameValid}>Sign up</Button>
|
||||||
|
{#if !usernameValid && username.length > 0}
|
||||||
|
<span class="text-danger-emphasis mb-2">That username is not valid.</span>
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
{:else if isDeleted && token && selfDelete && deletedAt}
|
{:else if isDeleted && token && selfDelete && deletedAt}
|
||||||
<p>
|
<p>
|
||||||
|
|
Loading…
Reference in a new issue