summaryrefslogtreecommitdiffstats
path: root/apps/portal/src/app
diff options
context:
space:
mode:
authorivarlovlie <git@ivarlovlie.no>2022-08-05 13:00:16 +0200
committerivarlovlie <git@ivarlovlie.no>2022-08-05 13:00:16 +0200
commitfae5653b9d6c6a318e49217eb4d565252013a904 (patch)
treeb7bc9c656e0b26fc6289a16a2357462c85e285e1 /apps/portal/src/app
parent6187962d0aad866edc7cd4ef3f2b828fa84ce670 (diff)
parent7f4545d78d4e49ff0dee79e71b71ad7d5d6bacdc (diff)
downloadgreatoffice-fae5653b9d6c6a318e49217eb4d565252013a904.tar.xz
greatoffice-fae5653b9d6c6a318e49217eb4d565252013a904.zip
Merge branch 'tailwind'
Diffstat (limited to 'apps/portal/src/app')
-rw-r--r--apps/portal/src/app/index.scss1
-rw-r--r--apps/portal/src/app/pages/_layout@loggedin.svelte15
-rw-r--r--apps/portal/src/app/pages/profile/index.svelte159
3 files changed, 166 insertions, 9 deletions
diff --git a/apps/portal/src/app/index.scss b/apps/portal/src/app/index.scss
index 8633a7d..718adf2 100644
--- a/apps/portal/src/app/index.scss
+++ b/apps/portal/src/app/index.scss
@@ -24,3 +24,4 @@
@use '../../web-shared/src/styles/components/auto-sized-grid';
@use '../../web-shared/src/styles/components/menu';
@use '../../web-shared/src/styles/components/user-menu';
+@use '../../web-shared/src/styles/components/breadcrumbs';
diff --git a/apps/portal/src/app/pages/_layout@loggedin.svelte b/apps/portal/src/app/pages/_layout@loggedin.svelte
index ea56f73..44e2e4a 100644
--- a/apps/portal/src/app/pages/_layout@loggedin.svelte
+++ b/apps/portal/src/app/pages/_layout@loggedin.svelte
@@ -1,7 +1,7 @@
<script>
import BlowoutToolbelt from "$shared/components/blowout-toolbelt.svelte";
- import UserMenu from "$app/components/user-menu.svelte";
- import {get_session_data} from "$shared/lib/session";
+ import {end_session, get_session_data} from "$shared/lib/session";
+ import {replace} from "svelte-spa-router";
const session = get_session_data();
</script>
@@ -30,15 +30,20 @@
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 right-0 top-0 margin-md z-index-2">
- <UserMenu name="{session?.profile?.username}"/>
+ <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"
diff --git a/apps/portal/src/app/pages/profile/index.svelte b/apps/portal/src/app/pages/profile/index.svelte
index 0929c3c..00942ac 100644
--- a/apps/portal/src/app/pages/profile/index.svelte
+++ b/apps/portal/src/app/pages/profile/index.svelte
@@ -1,7 +1,90 @@
<script>
- import {push} from "svelte-spa-router";
- import {Bread, Crumb} from "$shared/components/breadcrumb/index";
- import Layout from "$app/pages/_layout@loggedin.svelte";
+ 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("_/api/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>
@@ -12,5 +95,73 @@
on:click={() => push("/")}/>
<Crumb name="Profile"/>
</Bread>
- <h1>Profile</h1>
+
+ <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>