diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-09-20 09:24:27 +0200 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-09-20 09:24:27 +0200 |
| commit | a9072370ca1eb9a5cce928b1d487db0f307edea6 (patch) | |
| tree | 59c3c23df930a8b5f888dc7813923abf4ceefed4 /old-apps/portal/src/app/pages | |
| parent | 56fa963a1d63cbe0bf28e29e717cceaa417c45c1 (diff) | |
| download | greatoffice-a9072370ca1eb9a5cce928b1d487db0f307edea6.tar.xz greatoffice-a9072370ca1eb9a5cce928b1d487db0f307edea6.zip | |
feat: Move old apps into it's own directory
Diffstat (limited to 'old-apps/portal/src/app/pages')
| -rw-r--r-- | old-apps/portal/src/app/pages/_layout.svelte | 62 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/_layout@loggedin.svelte | 75 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/admin/index.svelte | 18 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/forgot.svelte | 102 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/home.svelte | 103 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/login.svelte | 142 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/profile/index.svelte | 167 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/reset-password.svelte | 138 | ||||
| -rw-r--r-- | old-apps/portal/src/app/pages/sign-up.svelte | 131 |
9 files changed, 938 insertions, 0 deletions
diff --git a/old-apps/portal/src/app/pages/_layout.svelte b/old-apps/portal/src/app/pages/_layout.svelte new file mode 100644 index 0000000..8c75cb9 --- /dev/null +++ b/old-apps/portal/src/app/pages/_layout.svelte @@ -0,0 +1,62 @@ +<script> + import BlowoutToolbelt from "$shared/components/blowout-toolbelt.svelte"; +</script> + +<style> + #decoration { + position: absolute; + top: 0; + left: 0; + pointer-events: none; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 1; + } + + #decoration svg { + position: absolute; + top: 0; + left: 50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + width: 134%; + min-width: 1280px; + max-width: 1920px; + height: auto; + } +</style> +<BlowoutToolbelt/> + +<main class="container-fluid padding-x-xs padding-x-xxl@xs padding-y-md padding-y-lg@md max-width-sm"> + <div class="z-index-2 position-relative"> + <slot/> + </div> + + <figure id="decoration" + class="z-index-1" + aria-hidden="true"> + <svg class="color-contrast-higher opacity-10%" + viewBox="0 0 1920 450" + fill="none"> + <g stroke="currentColor" + stroke-width="2" + stroke-linejoin="round" + stroke-linecap="round"> + <path d="M1449 94.9993V3L1354 48.9995L1259 3V94.9993L1354 140.999L1449 94.9993Z"/> + <path d="M1639 94.9993V3L1544 48.9995L1449 3V94.9993L1544 140.999L1639 94.9993Z"/> + <path d="M1354 49.0002V141"/> + <path d="M1544 49.0002V141"/> + <path d="M1449 94.9995L1544 140.999L1449 186.999L1354 140.999L1449 94.9995Z"/> + <path d="M1544 141V232.999L1449 278.999L1354 232.999V141"/> + <path d="M1449 187V279"/> + <path d="M1544 264L1639 310L1544 355.999L1449 310L1544 264Z"/> + <path d="M1639 310V402L1544 447.999L1449 402V310"/> + <path d="M1544 356.001V448"/> + <path d="M1639 94.9995L1734 140.999L1639 186.999L1544 140.999L1639 94.9995Z"/> + <path d="M1734 141V232.999L1639 278.999L1544 232.999V141"/> + <path d="M1639 187V279"/> + </g> + </svg> + </figure> +</main> diff --git a/old-apps/portal/src/app/pages/_layout@loggedin.svelte b/old-apps/portal/src/app/pages/_layout@loggedin.svelte new file mode 100644 index 0000000..44e2e4a --- /dev/null +++ b/old-apps/portal/src/app/pages/_layout@loggedin.svelte @@ -0,0 +1,75 @@ +<script> + import BlowoutToolbelt from "$shared/components/blowout-toolbelt.svelte"; + import {end_session, get_session_data} from "$shared/lib/session"; + import {replace} from "svelte-spa-router"; + + const session = get_session_data(); +</script> + +<style> + #decoration { + position: absolute; + top: 0; + left: 0; + pointer-events: none; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 1; + } + + #decoration svg { + position: absolute; + top: 0; + left: 50%; + -webkit-transform: translateX(-50%); + transform: translateX(-50%); + width: 134%; + min-width: 1280px; + max-width: 1920px; + height: auto; + } +</style> +<BlowoutToolbelt/> +<main class="container max-width-xl padding-x-xs padding-x-xxl@xs padding-y-md padding-y-lg@md"> + <div class="z-index-2 position-relative"> + <slot/> + </div> + + <div class="flex flex-row gap-xs position-fixed left-0 top-0 margin-md z-index-2"> + <span on:click={async () => { + if (confirm("Are you sure?")) await end_session(() => { + replace("/login"); + }) + }} class="btn btn--sm"> + Logout + </span> + </div> + + <figure id="decoration" + class="z-index-1" + aria-hidden="true"> + <svg class="color-contrast-higher opacity-10%" + viewBox="0 0 1920 450" + fill="none"> + <g stroke="currentColor" + stroke-width="2" + stroke-linejoin="round" + stroke-linecap="round"> + <path d="M1449 94.9993V3L1354 48.9995L1259 3V94.9993L1354 140.999L1449 94.9993Z"/> + <path d="M1639 94.9993V3L1544 48.9995L1449 3V94.9993L1544 140.999L1639 94.9993Z"/> + <path d="M1354 49.0002V141"/> + <path d="M1544 49.0002V141"/> + <path d="M1449 94.9995L1544 140.999L1449 186.999L1354 140.999L1449 94.9995Z"/> + <path d="M1544 141V232.999L1449 278.999L1354 232.999V141"/> + <path d="M1449 187V279"/> + <path d="M1544 264L1639 310L1544 355.999L1449 310L1544 264Z"/> + <path d="M1639 310V402L1544 447.999L1449 402V310"/> + <path d="M1544 356.001V448"/> + <path d="M1639 94.9995L1734 140.999L1639 186.999L1544 140.999L1639 94.9995Z"/> + <path d="M1734 141V232.999L1639 278.999L1544 232.999V141"/> + <path d="M1639 187V279"/> + </g> + </svg> + </figure> +</main> diff --git a/old-apps/portal/src/app/pages/admin/index.svelte b/old-apps/portal/src/app/pages/admin/index.svelte new file mode 100644 index 0000000..f9b91d2 --- /dev/null +++ b/old-apps/portal/src/app/pages/admin/index.svelte @@ -0,0 +1,18 @@ +<script> + import Layout from "../_layout@loggedin.svelte"; + import {Bread, Crumb} from "$shared/components/breadcrumb"; + import {push} from "svelte-spa-router"; + +</script> + +<Layout> + <Bread> + <Crumb name="Home" + withArrow="true" + isLink="true" + on:click={() => push("/")}/> + <Crumb name="Organisation"/> + </Bread> + + <main class="max-width-sm"></main> +</Layout> diff --git a/old-apps/portal/src/app/pages/forgot.svelte b/old-apps/portal/src/app/pages/forgot.svelte new file mode 100644 index 0000000..156deab --- /dev/null +++ b/old-apps/portal/src/app/pages/forgot.svelte @@ -0,0 +1,102 @@ +<script> + import {onMount} from "svelte"; + import {link} from "svelte-spa-router"; + import {create_forgot_password_request} from "$shared/lib/api/user"; + import {is_email} from "$shared/lib/helpers"; + import Alert from "$shared/components/alert.svelte"; + import Button from "$shared/components/button.svelte"; + import Tile from "$shared/components/tile.svelte"; + import Layout from "./_layout.svelte"; + + let isLoading = false; + let username; + + const alert = { + title: "", + type: "", + message: "", + isVisible: false, + show(type, obj) { + alert.title = obj.title; + alert.message = obj.text; + alert.type = type; + alert.isVisible = true; + isLoading = false; + }, + hide() { + alert.isVisible = false; + alert.title = ""; + alert.message = ""; + alert.type = ""; + isLoading = false; + }, + }; + + function is_valid() { + return is_email(username); + } + + async function submit_form() { + if (isLoading) { + return; + } + if (is_valid()) { + isLoading = true; + const response = await create_forgot_password_request(username); + if (response.ok) { + alert.show("success", { + title: "Request is sent", + text: "If we find an account associated with this email address, you will receive an email with a reset link very soon.", + }); + } else { + console.error(response.data); + alert.show("error", { + title: response.data?.title ?? "An error occured", + text: response.data?.text ?? "Please try again soon", + }); + } + } + } + + onMount(() => { + document.addEventListener("DOMContentLoaded", () => { + document.getElementById("email-address").focus(); + }); + }); +</script> + +<Layout> + <Tile> + <form on:submit|preventDefault={submit_form} + class="max-width-xxs"> + <fieldset> + <legend class="form-legend"> + <span class="margin-bottom-xs text-xl">Send reset link</span> <br/> + <span class="text-sm">... or <a href="/login" + use:link>log in</a></span> + </legend> + <div class="margin-bottom-xs"> + <p>Provide your email address, and we'll send you a link to set your new password.</p> + </div> + <div class="margin-bottom-xxs max-width-xxs"> + <Alert visible={alert.isVisible} + title={alert.title} + message={alert.message} + type={alert.type}/> + </div> + <div class="margin-bottom-xs"> + <input type="email" + id="email-address" + placeholder="Email address" + class="form-control width-100%" + bind:value={username}/> + </div> + <div class="flex justify-end"> + <Button text="Send reset link" + type="primary" + loading={isLoading}/> + </div> + </fieldset> + </form> + </Tile> +</Layout> diff --git a/old-apps/portal/src/app/pages/home.svelte b/old-apps/portal/src/app/pages/home.svelte new file mode 100644 index 0000000..0e325ee --- /dev/null +++ b/old-apps/portal/src/app/pages/home.svelte @@ -0,0 +1,103 @@ +<script> + import {projects_base} from "$shared/lib/configuration"; + import {get_session_data} from "$shared/lib/session"; + import {push} from "svelte-spa-router"; + import Layout from "./_layout@loggedin.svelte"; + import LinkCard from "$shared/components/link-card.svelte"; + import Alert from "$shared/components/alert.svelte"; + import {UserIcon, UsersIcon, WatchIcon, SendIcon, ListIcon} from "svelte-feather-icons"; + + let showUsers = true; + const session = get_session_data(); +</script> + +<Layout> + <div class="grid gap-md"> + <div class="row"> + <Alert closeable="true" + closeableCooldown="~" + id="welcome-note" + title="Hello {session.profile?.username}" + message="This is your portal to Greatoffice, here you will find all your great apps and management options."/> + </div> + <div class="row"> + <h2 class="margin-bottom-xs">Apps</h2> + <div class="grid-auto-md gap-sm"> + <LinkCard name="Projects" + description="The home for your projects" + text="Open in a new tab" + title="Open Projects" + href="{projects_base()}"> + <figure slot="icon"> + <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2"> + <WatchIcon size="42" + class="color-primary" + strokeWidth="1.2"/> + </div> + </figure> + </LinkCard> + <LinkCard name="Tickets" + description="The home for your tickets" + class="c-disabled user-select-none" + text="Coming soon" + title="Open Tickets" + href="{projects_base()}"> + <figure slot="icon"> + <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2"> + <SendIcon size="42" + class="color-primary" + strokeWidth="1.2"/> + </div> + </figure> + </LinkCard> + <LinkCard name="Todo" + description="The home for your todos" + class="c-disabled user-select-none" + text="Coming soon" + title="Open Todo" + href="{projects_base()}"> + <figure slot="icon"> + <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2"> + <ListIcon size="42" + class="color-primary" + strokeWidth="1.2"/> + </div> + </figure> + </LinkCard> + </div> + </div> + <div class="row"> + <h2 class="margin-bottom-xs">Manage</h2> + <div class="grid-auto-md gap-sm"> + <LinkCard name="Profile" + description="Manage your profile" + text="Open" + title="Go to your profile management page" + on:click={() => push("/profile")}> + <figure slot="icon"> + <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2"> + <UserIcon size="42" + class="color-primary" + strokeWidth="1.2"/> + </div> + </figure> + </LinkCard> + {#if showUsers} + <LinkCard name="Organisation" + description="Manage your organisation" + title="Go to your organisations management page" + text="Open" + on:click={() => push("/admin")}> + <figure slot="icon"> + <div class="bg-primary bg-opacity-10% padding-xs border-left border-primary border-2"> + <UsersIcon size="42" + class="color-primary" + strokeWidth="1.2"/> + </div> + </figure> + </LinkCard> + {/if} + </div> + </div> + </div> +</Layout> diff --git a/old-apps/portal/src/app/pages/login.svelte b/old-apps/portal/src/app/pages/login.svelte new file mode 100644 index 0000000..1ca6b61 --- /dev/null +++ b/old-apps/portal/src/app/pages/login.svelte @@ -0,0 +1,142 @@ +<script> + import {onMount} from "svelte"; + import {link, replace, querystring} from "svelte-spa-router"; + import {api_base, IconNames, frontpage_base} from "$shared/lib/configuration"; + import Button from "$shared/components/button.svelte"; + import Alert from "$shared/components/alert.svelte"; + import Tile from "$shared/components/tile.svelte"; + import {login} from "$shared/lib/api/user"; + import {is_email} from "$shared/lib/helpers"; + import Layout from "./_layout.svelte"; + + const loginForm = { + loading: false, + values: { + username: "", + password: "", + persist: true + }, + alert: { + title: "", + type: "", + message: "", + isVisible: false, + show(type, obj) { + loginForm.alert.title = obj.title; + loginForm.alert.message = obj.text; + loginForm.alert.type = type; + loginForm.alert.isVisible = true; + loginForm.loading = false; + }, + hide() { + loginForm.alert.isVisible = false; + loginForm.alert.title = ""; + loginForm.alert.message = ""; + loginForm.alert.type = ""; + }, + }, + is_valid() { + return is_email(loginForm.values.username) && loginForm.values.password.length > 0; + }, + async submit_form() { + if (loginForm.loading) { + return; + } + if (loginForm.is_valid()) { + loginForm.alert.hide(); + loginForm.loading = true; + try { + const response = await login(loginForm.values); + if (response.ok) { + await replace("#/home"); + } else { + if (response.data.title || response.data.text) { + loginForm.alert.show("error", { + title: response.data.title ?? "", + text: response.data.text ?? "", + }); + } else { + loginForm.alert.show("error", { + title: "An unknown error occured", + text: "Try again soon", + }); + } + } + } catch (e) { + loginForm.alert.show("error", { + title: "An error occured", + text: "Could not connect to server, please check your internet connection", + }); + } + } else { + loginForm.alert.show("error", { + title: "Invalid form", + }); + } + }, + }; + + onMount(() => { + if ($querystring === "deleted") { + loginForm.alert.show("info", { + title: "Account deleted", + text: "Your account and all its data was successfully deleted.", + }); + } + if ($querystring === "expired") { + loginForm.alert.show("info", { + title: "Session expired", + text: "Your session has expired, feel free to log in again.", + }); + } + }); +</script> + +<Layout> + <a href="{frontpage_base()}" class="block margin-bottom-xs">Go to {frontpage_base()}</a> + <Tile> + <form on:submit|preventDefault={loginForm.submit_form} + class="max-width-xxs"> + <fieldset> + <legend class="form-legend"> + <span class="margin-bottom-xs text-xl">Log into your account</span> + <br/> + <span class="text-sm">... or <a href="/signup" + use:link>create a new one</a></span> + </legend> + <div class="margin-bottom-xxs max-width-xxs"> + <Alert visible={loginForm.alert.isVisible} + title={loginForm.alert.title} + message={loginForm.alert.message} + type={loginForm.alert.type}/> + </div> + <div class="margin-bottom-xxs"> + <input type="email" + placeholder="Email address" + class="form-control width-100%" + id="username" + bind:value={loginForm.values.username}/> + </div> + <div class="margin-bottom-xxs"> + <input type="password" + placeholder="Password" + id="password" + class="form-control width-100%" + bind:value={loginForm.values.password}/> + <div class="flex justify-end"> + <a tabindex="-1" + class="text-sm" + href="/forgot" + use:link>Reset password</a> + </div> + </div> + <div class="flex justify-between"> + <Button text="Login" + type="submit" + variant="primary" + loading={loginForm.loading}/> + </div> + </fieldset> + </form> + </Tile> +</Layout> diff --git a/old-apps/portal/src/app/pages/profile/index.svelte b/old-apps/portal/src/app/pages/profile/index.svelte new file mode 100644 index 0000000..a7291d6 --- /dev/null +++ b/old-apps/portal/src/app/pages/profile/index.svelte @@ -0,0 +1,167 @@ +<script> + import {push} from "svelte-spa-router"; + import {Bread, Crumb} from "$shared/components/breadcrumb/index"; + import Layout from "$app/pages/_layout@loggedin.svelte"; + import {update_profile} from "$shared/lib/api/user"; + import Alert from "$shared/components/alert.svelte"; + import Button from "$shared/components/button.svelte"; + import {is_email} from "$shared/lib/helpers"; + import {api_base} from "$shared/lib/configuration"; + import {get_session_data} from "$shared/lib/session"; + + const archiveLink = api_base("_/account/archive"); + + let modal; + let understands = false; + + let formIsLoading = false; + let formError; + + let username = get_session_data()?.profile.username; + let usernameFieldMessage; + let usernameFieldMessageClass = "color-error"; + + let password; + let passwordFieldMessage; + let passwordFieldMessageClass = "color-error"; + + async function submit_form(e) { + e.preventDefault(); + if (!username && !password) { + console.error("Not submitting because both values is empty"); + return; + } + + usernameFieldMessage = ""; + passwordFieldMessage = ""; + + if (username && !is_email(username)) { + usernameFieldMessage = "Username has to be a valid email"; + return; + } + + if (password && password?.length < 6) { + passwordFieldMessage = "The new password must contain at least 6 characters"; + return; + } + + formIsLoading = true; + + const response = await update_profile({ + username, + password, + }); + + formIsLoading = false; + + if (response.ok) { + if (password) { + passwordFieldMessage = "Successfully updated"; + passwordFieldMessageClass = "color-success"; + password = ""; + } + if (username) { + usernameFieldMessage = "Successfully updated"; + usernameFieldMessageClass = "color-success"; + password = ""; + } + } else { + formError = response.data.title ?? "An unknown error occured"; + } + } + + async function handle_delete_account_button_click() { + alert("Not implemented"); + return; + if (understands && confirm("Are you absolutely sure that you want to delete your account?")) { + } + } + + export const functions = { + open() { + modal.open(); + }, + close() { + // modal.close(); + }, + }; +</script> + +<Layout> + <Bread> + <Crumb name="Home" + withArrow="true" + isLink="true" + on:click={() => push("/")}/> + <Crumb name="Profile"/> + </Bread> + + <main class="max-width-sm"> + <section class="margin-bottom-md"> + <p class="text-md margin-bottom-sm">Update your information</p> + <form on:submit={submit_form} + autocomplete="new-password"> + {#if formError} + <small class="color-danger">{formError}</small> + {/if} + <div class="margin-bottom-sm"> + <label for="email" + class="form-label margin-bottom-xxs">New username</label> + <input type="email" + class="form-control width-100%" + id="email" + placeholder={username} + bind:value={username}/> + {#if usernameFieldMessage} + <small class={usernameFieldMessageClass}>{usernameFieldMessage}</small> + {/if} + </div> + <div class="margin-bottom-sm"> + <label for="password" + class="form-label margin-bottom-xxs">New password</label> + <input type="password" + class="form-control width-100%" + id="password" + bind:value={password}/> + {#if passwordFieldMessage} + <small class={passwordFieldMessageClass}>{passwordFieldMessage}</small> + {/if} + </div> + <div class="flex justify-end"> + <Button text="Save" + on:click={submit_form} + variant="primary" + loading={formIsLoading}/> + </div> + </form> + </section> + <section class="margin-bottom-md"> + <p class="text-md margin-bottom-sm">Download your data</p> + <a class="btn btn--subtle" + href={archiveLink} + download>Click here to download your data</a> + </section> + <section> + <p class="text-md margin-bottom-sm">Delete account</p> + <div class="margin-bottom-sm"> + <Alert + message="Deleting your account and data means that all of your data (entries, categories, etc.) will be unrecoverable forever.<br>You should probably download your data before continuing." + type="info" + /> + </div> + <div class="form-check margin-bottom-sm"> + <input type="checkbox" + class="checkbox" + id="the-consequences" + bind:checked={understands}/> + <label for="the-consequences">I understand the consequences of deleting my account and data.</label> + </div> + <div class="flex justify-end"> + <Button text="Delete everything" + variant="accent" + disabled={!understands} + on:click={handle_delete_account_button_click}/> + </div> + </section> + </main> +</Layout> diff --git a/old-apps/portal/src/app/pages/reset-password.svelte b/old-apps/portal/src/app/pages/reset-password.svelte new file mode 100644 index 0000000..dabf5c9 --- /dev/null +++ b/old-apps/portal/src/app/pages/reset-password.svelte @@ -0,0 +1,138 @@ +<script> + import {querystring, link} from "svelte-spa-router"; + import {check_forgot_password_request, fulfill_forgot_password_request} from "$shared/lib/api/user"; + import Alert from "$shared/components/alert.svelte"; + import Button from "$shared/components/button.svelte"; + import Tile from "$shared/components/tile.svelte"; + import Layout from "./_layout.svelte"; + + const requestId = new URLSearchParams($querystring).get("id"); + let isLoading = false; + let newPassword; + let newPasswordError; + let alert = { + title: "", + type: "", + message: "", + isVisible: false, + show(type, obj) { + alert.title = obj.title; + alert.message = obj.text; + alert.type = type; + alert.isVisible = true; + isLoading = false; + }, + hide() { + alert.isVisible = false; + alert.title = ""; + alert.message = ""; + alert.type = ""; + isLoading = false; + }, + }; + + function is_valid() { + let isValid = true; + if (!newPassword.length > 5) { + newPasswordError = "The new password must be at least 5 characters"; + isValid = false; + } + return isValid; + } + + async function submit() { + if (isLoading) { + return; + } + if (is_valid()) { + isLoading = true; + const response = await fulfill_forgot_password_request(requestId, newPassword); + if (response.ok) { + alert.show("success", { + title: "Your new password is set", + text: "<a href='/#/login'>Click here to log in</a>", + }); + } else { + console.error(response.data); + alert.show("error", { + title: response.data?.title ?? "An error occured", + text: response.data?.text ?? "Please try again soon", + }); + } + } + } + + async function is_valid_password_reset_request() { + const response = await check_forgot_password_request(requestId); + if (response.ok) { + return response.data === true; + } + return false; + } +</script> + +<Layout> + <Tile> + <form on:submit|preventDefault={submit} + class="max-width-xxs {isLoading ? 'c-disabled loading' : ''}"> + {#if requestId} + {#await is_valid_password_reset_request()} + <p>Checking your request...</p> + <a href="/login" + use:link>cancel</a> + {:then isActive} + {#if isActive === true} + <fieldset> + <legend class="form-legend"> + <span class="margin-bottom-xs text-xl">Set your new password</span> <br/> + <span class="text-sm"> + ... or + <a href="/login" + use:link> log in </a> + </span> + </legend> + <div class="margin-bottom-xxs max-width-xxs"> + <Alert visible={alert.isVisible} + title={alert.title} + message={alert.message} + type={alert.type}/> + </div> + <div class="margin-bottom-xs"> + <input + type="password" + id="new-password" + placeholder="New password" + class="form-control width-100%" + bind:value={newPassword} + /> + {#if newPasswordError} + <small class="color-danger">{newPasswordError}</small> + {/if} + </div> + <div class="flex justify-end"> + <Button text="Set new password" + type="primary" + loading={isLoading} + on:click={submit}/> + </div> + </fieldset> + {:else} + <Alert title="This request is expired" + message="Please submit the forgot password form again" + type="warning"/> + <div class="flex justify-between width-100% margin-y-sm"> + <a href="/forgot" + use:link>Go to forgot form</a> + <a href="/login" + use:link>Go to login form</a> + </div> + {/if} + {:catch _} + <Alert title="An error occured" + message="Please try again soon" + type="error"/> + {/await} + {/if} + </form> + </Tile> +</Layout> diff --git a/old-apps/portal/src/app/pages/sign-up.svelte b/old-apps/portal/src/app/pages/sign-up.svelte new file mode 100644 index 0000000..3bcab6d --- /dev/null +++ b/old-apps/portal/src/app/pages/sign-up.svelte @@ -0,0 +1,131 @@ +<script> + import {create_account} from "$shared/lib/api/user"; + import {frontpage_base} from "$shared/lib/configuration"; + import {is_email} from "$shared/lib/helpers"; + import Alert from "$shared/components/alert.svelte"; + import Button from "$shared/components/button.svelte"; + import Tile from "$shared/components/tile.svelte"; + import {link} from "svelte-spa-router"; + import Layout from "./_layout.svelte"; + + const signupForm = { + loading: false, + values: { + username: "", + password: "", + }, + alert: { + title: "", + type: "", + message: "", + isVisible: false, + show(type, obj) { + signupForm.alert.title = obj.title; + signupForm.alert.message = obj.text; + signupForm.alert.type = type; + signupForm.alert.isVisible = true; + signupForm.loading = false; + }, + hide() { + signupForm.alert.isVisible = false; + signupForm.alert.title = ""; + signupForm.alert.message = ""; + signupForm.alert.type = ""; + }, + }, + is_valid() { + return ( + is_email(signupForm.values.username) && + signupForm.values.password.length > 0 + ); + }, + async submit_form() { + if (signupForm.loading) { + return; + } + if (signupForm.is_valid()) { + signupForm.alert.hide(); + signupForm.loading = true; + try { + const response = await create_account(signupForm.values); + if (response.ok) { + location.reload(); + } else { + if (response.data.title || response.data.text) { + signupForm.alert.show("error", { + title: response.data.title ?? "", + text: response.data.text ?? "", + }); + } else { + signupForm.alert.show("error", { + title: "An unknown error occured", + text: "Try again soon", + }); + } + } + } catch (e) { + console.error(e); + signupForm.alert.show("error", { + title: "An error occured", + text: "Could not connect to server, please check your internet connection", + }); + } + } else { + signupForm.alert.show("error", { + title: "Invalid form", + }); + } + }, + }; +</script> + +<Layout> + <a href="{frontpage_base()}" + class="block margin-bottom-xs">Go to {frontpage_base()}</a> + <Tile> + <form on:submit|preventDefault={signupForm.submit_form} + class="max-width-xxs"> + <fieldset> + <legend class="form-legend"> + <span class="margin-bottom-xs text-xl">Create your account</span> <br/> + <span class="text-sm" + >... or <a href="/login" + use:link>log in</a></span + > + </legend> + <div class="margin-bottom-xs"> + <p>Provide an email and password to get immediate access to your new environment (30 days full access, no billing details required, no promotion emails).</p> + </div> + <div class="margin-bottom-xxs max-width-xxs"> + <Alert visible={signupForm.alert.isVisible} + title={signupForm.alert.title} + message={signupForm.alert.message} + type={signupForm.alert.type} + /> + </div> + <div class="margin-bottom-xxs"> + <input type="email" + placeholder="Email address" + class="form-control width-100%" + id="email-address" + bind:value={signupForm.values.username} + /> + </div> + <div class="margin-bottom-xxs"> + <input type="password" + placeholder="Password" + class="form-control width-100%" + bind:value={signupForm.values.password} + /> + </div> + <div class="flex justify-end"> + <Button class="margin-bottom-xs" + text="Create account" + type="primary" + loading={signupForm.loading} + /> + </div> + </fieldset> + </form> + </Tile> +</Layout> |
