ui: Done join page
This commit is contained in:
parent
275a71982b
commit
c5d12b9e9f
11
ui/src/lib/api/user.ts
Normal file
11
ui/src/lib/api/user.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { handleApiResponse } from '$lib/errors';
|
||||
import type { SignUpForm } from '$lib/model/user';
|
||||
|
||||
export async function registerUser(form: SignUpForm): Promise<any> {
|
||||
const res = await fetch('api/users', {
|
||||
method: 'POST',
|
||||
headers: {'Accept': 'application/json'},
|
||||
body: JSON.stringify(form),
|
||||
});
|
||||
return await handleApiResponse(res);
|
||||
}
|
152
ui/src/lib/components/SignUpForm.svelte
Normal file
152
ui/src/lib/components/SignUpForm.svelte
Normal file
|
@ -0,0 +1,152 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
import {
|
||||
Button,
|
||||
FormGroup,
|
||||
Icon,
|
||||
Input,
|
||||
Label,
|
||||
Spinner,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { t, locale } from '$lib/translations';
|
||||
import { registerUser } from '$lib/api/user';
|
||||
import type { SignUpForm } from '$lib/model/user';
|
||||
import { checkWeakPassword, checkPasswordConfirmation } from '$lib/password';
|
||||
import { toasts } from '$lib/stores/toasts';
|
||||
|
||||
|
||||
let signupForm: SignUpForm = {
|
||||
email: "",
|
||||
password: "",
|
||||
wantReceiveUpdate: false,
|
||||
lang: "",
|
||||
};
|
||||
let passwordConfirmation: string = "";
|
||||
let emailState: boolean|undefined;
|
||||
let passwordState: boolean|undefined;
|
||||
let passwordConfirmState: boolean|undefined;
|
||||
let formSent = false;
|
||||
|
||||
$: {
|
||||
if (passwordState == false) {
|
||||
passwordState = checkWeakPassword(signupForm.password);
|
||||
}
|
||||
}
|
||||
|
||||
let formElm: HTMLFormElement;
|
||||
|
||||
function goSignUp() {
|
||||
const valid = formElm.checkValidity()
|
||||
|
||||
if (valid && emailState && passwordState && passwordConfirmState) {
|
||||
formSent = true;
|
||||
signupForm.lang = $locale
|
||||
registerUser(signupForm)
|
||||
.then(
|
||||
() => {
|
||||
formSent = false;
|
||||
toasts.addToast({
|
||||
title: $t('account.signup.success'),
|
||||
message: $t('email.instruction.check-inbox'),
|
||||
type: 'success',
|
||||
timeout: 5000,
|
||||
});
|
||||
goto('/login');
|
||||
},
|
||||
(error) => {
|
||||
formSent = false;
|
||||
toasts.addErrorToast({
|
||||
title: $t('errors.registration'),
|
||||
message: error,
|
||||
timeout: 10000,
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<form
|
||||
class="container my-1"
|
||||
bind:this={formElm}
|
||||
on:submit|preventDefault={goSignUp}
|
||||
>
|
||||
<FormGroup>
|
||||
<Label for="email-input">{$t('email.address')}</Label>
|
||||
<Input
|
||||
aria-describedby="emailHelpBlock"
|
||||
autocomplete="username"
|
||||
autofocus
|
||||
feedback={!emailState?$t('errors.address-valid'):null}
|
||||
id="email-input"
|
||||
placeholder="jPostel@isi.edu"
|
||||
required
|
||||
type="email"
|
||||
invalid={emailState !== undefined && !emailState}
|
||||
valid={emailState}
|
||||
bind:value={signupForm.email}
|
||||
on:change={() => emailState = signupForm.email.indexOf('@') > 0}
|
||||
/>
|
||||
<div id="emailHelpBlock" class="form-text">
|
||||
{$t('account.signup.address-why', {
|
||||
identify: $t('account.signup.identify'),
|
||||
'security-operations': $t('account.signup.security-operations'),
|
||||
})}
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Label for="password-input">{$t('common.password')}</Label>
|
||||
<Input
|
||||
autocomplete="new-password"
|
||||
feedback={!passwordState?$t('errors.password-weak'):null}
|
||||
id="password-input"
|
||||
placeholder="xXxXxXxXxX"
|
||||
required
|
||||
type="password"
|
||||
invalid={passwordState !== undefined && !passwordState}
|
||||
valid={passwordState}
|
||||
bind:value={signupForm.password}
|
||||
on:change={() => passwordState = checkWeakPassword(signupForm.password)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Label for="passwordconfirm-input">{$t('password.confirmation')}</Label>
|
||||
<Input
|
||||
feedback={!passwordConfirmState?$t('errors.password-match'):null}
|
||||
id="passwordconfirm-input"
|
||||
placeholder="xXxXxXxXxX"
|
||||
required
|
||||
type="password"
|
||||
invalid={passwordConfirmState !== undefined && !passwordConfirmState}
|
||||
valid={passwordConfirmState}
|
||||
bind:value={passwordConfirmation}
|
||||
on:change={() => passwordConfirmState = checkPasswordConfirmation(signupForm.password, passwordConfirmation)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<Input
|
||||
id="signup-newsletter"
|
||||
type="checkbox"
|
||||
label={$t('account.signup.receive-update')}
|
||||
bind:value={signupForm.wantReceiveUpdate}
|
||||
/>
|
||||
</FormGroup>
|
||||
<div class="d-flex justify-content-around">
|
||||
<Button type="submit" color="primary" disabled={formSent}>
|
||||
{#if formSent}
|
||||
<Spinner
|
||||
label="Spinning"
|
||||
size="sm"
|
||||
/>
|
||||
{:else}
|
||||
<Icon name="person-plus" />
|
||||
{/if}
|
||||
{$t('account.signup.signup')}
|
||||
</Button>
|
||||
<Button href="/login" outline color="dark">
|
||||
{$t('account.signup.already')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
13
ui/src/lib/errors.ts
Normal file
13
ui/src/lib/errors.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
export async function handleApiResponse(res: Response): Promise<any> {
|
||||
const data = await res.json();
|
||||
|
||||
if (res.status == 200) {
|
||||
return data;
|
||||
} else if (res.status == 401) {
|
||||
throw new Error(data.errmsg);
|
||||
} else if (data.errmsg) {
|
||||
throw new Error(data.errmsg);
|
||||
} else {
|
||||
throw new Error("A " + res.status + " error occurs.");
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
"signup": {
|
||||
"already": "Already a member?",
|
||||
"join-call": "Join our nice platform in less than 2 minutes!",
|
||||
"address-why": "You'll use your address to {identify} yourself on the platform, and it could be used to contact you for {security-operations}.",
|
||||
"address-why": "We'll use your address to {identify} yourself on the platform, and it could be used to contact you for {security-operations}.",
|
||||
"identify": "identify",
|
||||
"security-operations": "security related operations",
|
||||
"receive-update": "Keep me informed of future big improvements",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type UserSettings from './usersettings';
|
||||
|
||||
interface User {
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
CreatedAt: Date;
|
||||
|
@ -8,4 +8,9 @@ interface User {
|
|||
settings: UserSettings;
|
||||
}
|
||||
|
||||
export default User;
|
||||
export interface SignUpForm {
|
||||
email: string;
|
||||
password: string;
|
||||
wantReceiveUpdate: boolean;
|
||||
lang: string;
|
||||
}
|
||||
|
|
45
ui/src/lib/password.ts
Normal file
45
ui/src/lib/password.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright or © or Copr. happyDNS (2022)
|
||||
//
|
||||
// contact@happydomain.org
|
||||
//
|
||||
// This software is a computer program whose purpose is to provide a modern
|
||||
// interface to interact with DNS systems.
|
||||
//
|
||||
// This software is governed by the CeCILL license under French law and abiding
|
||||
// by the rules of distribution of free software. You can use, modify and/or
|
||||
// redistribute the software under the terms of the CeCILL license as
|
||||
// circulated by CEA, CNRS and INRIA at the following URL
|
||||
// "http://www.cecill.info".
|
||||
//
|
||||
// As a counterpart to the access to the source code and rights to copy, modify
|
||||
// and redistribute granted by the license, users are provided only with a
|
||||
// limited warranty and the software's author, the holder of the economic
|
||||
// rights, and the successive licensors have only limited liability.
|
||||
//
|
||||
// In this respect, the user's attention is drawn to the risks associated with
|
||||
// loading, using, modifying and/or developing or reproducing the software by
|
||||
// the user in light of its specific status of free software, that may mean
|
||||
// that it is complicated to manipulate, and that also therefore means that it
|
||||
// is reserved for developers and experienced professionals having in-depth
|
||||
// computer knowledge. Users are therefore encouraged to load and test the
|
||||
// software's suitability as regards their requirements in conditions enabling
|
||||
// the security of their systems and/or data to be ensured and, more generally,
|
||||
// to use and operate it in the same conditions as regards security.
|
||||
//
|
||||
// The fact that you are presently reading this means that you have had
|
||||
// knowledge of the CeCILL license and that you accept its terms.
|
||||
|
||||
export function checkWeakPassword (password: string): boolean|undefined {
|
||||
if (password.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return password.length >= 8 && /[A-Z]/.test(password) && /[a-z]/.test(password) && /[0-9]/.test(password) && (/\W/.test(password) || password.length >= 11);
|
||||
}
|
||||
|
||||
export function checkPasswordConfirmation (password: string, passwordConfirm: string): boolean|undefined {
|
||||
if (passwordConfirm.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return password === passwordConfirm;
|
||||
}
|
36
ui/src/routes/join/+page.svelte
Normal file
36
ui/src/routes/join/+page.svelte
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
Card,
|
||||
CardBody,
|
||||
CardHeader,
|
||||
Container,
|
||||
Col,
|
||||
Row,
|
||||
} from 'sveltestrap';
|
||||
|
||||
import { t } from '$lib/translations';
|
||||
import SignUpForm from '$lib/components/SignUpForm.svelte';
|
||||
</script>
|
||||
|
||||
<Container class="my-3">
|
||||
<Row>
|
||||
<Col sm="4" class="d-none d-sm-flex flex-column align-items-center">
|
||||
<img src="img/screenshot.png" alt="HappyDNS screenshoot" style="max-height: 100%; max-width: 100%;" class="mt-auto">
|
||||
<div class="mt-3 mb-auto text-justify">
|
||||
Join now our open source and free (as freedom) DNS platform, to manage your domains easily!
|
||||
</div>
|
||||
</Col>
|
||||
<Col sm="8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h6 class="card-title my-1 fw-bold">
|
||||
{$t('account.signup.join-call')}
|
||||
</h6>
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<SignUpForm />
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
Loading…
Reference in New Issue
Block a user