From 6561771c435f9d9bed1589b5ed13d17aee0b7873 Mon Sep 17 00:00:00 2001 From: ivarlovlie Date: Sun, 11 Dec 2022 20:46:58 +0100 Subject: feat: Add frontpage --- code/app/.typesafe-i18n.json | 2 +- code/app/src/configuration/index.ts | 2 +- code/app/src/help/persistent-store.ts | 12 ++ code/app/src/i18n/en/index.ts | 4 + code/app/src/i18n/i18n-types.ts | 32 ++++ code/app/src/routes/(main)/(app)/+layout.svelte | 210 +++++++++++---------- code/app/src/routes/(main)/(public)/+layout.svelte | 10 +- .../(main)/(public)/reset-password/+page.svelte | 32 ++-- .../routes/(main)/(public)/reset-password/+page.ts | 11 ++ .../(main)/(public)/reset-password/[id]/+page.ts | 11 ++ .../routes/(main)/(public)/sign-in/+page.svelte | 86 +++++---- .../src/routes/(main)/(public)/sign-in/+page.ts | 11 ++ .../routes/(main)/(public)/sign-up/+page.svelte | 44 ++--- .../src/routes/(main)/(public)/sign-up/+page.ts | 11 ++ code/app/src/routes/(main)/+layout.server.ts | 17 +- code/app/src/routes/(main)/+layout.svelte | 16 +- code/app/src/routes/(main)/+layout.ts | 12 +- code/app/src/services/account-service.ts | 35 ++-- 18 files changed, 334 insertions(+), 224 deletions(-) create mode 100644 code/app/src/routes/(main)/(public)/reset-password/+page.ts create mode 100644 code/app/src/routes/(main)/(public)/reset-password/[id]/+page.ts create mode 100644 code/app/src/routes/(main)/(public)/sign-in/+page.ts create mode 100644 code/app/src/routes/(main)/(public)/sign-up/+page.ts (limited to 'code/app') diff --git a/code/app/.typesafe-i18n.json b/code/app/.typesafe-i18n.json index a856d24..42cea32 100644 --- a/code/app/.typesafe-i18n.json +++ b/code/app/.typesafe-i18n.json @@ -1,5 +1,5 @@ { "adapter": "svelte", "$schema": "https://unpkg.com/typesafe-i18n@5.17.1/schema/typesafe-i18n.json", - "outputPath": "src/lib/i18n" + "outputPath": "src/i18n" } \ No newline at end of file diff --git a/code/app/src/configuration/index.ts b/code/app/src/configuration/index.ts index 9b03b66..a0ec66d 100644 --- a/code/app/src/configuration/index.ts +++ b/code/app/src/configuration/index.ts @@ -1,4 +1,4 @@ -export const BASE_DOMAIN = "dev.greatoffice.life"; +export const BASE_DOMAIN = "stage.greatoffice.app"; export const DEV_BASE_DOMAIN = "http://localhost"; export const API_ADDRESS = "https://api." + BASE_DOMAIN; export const DEV_API_ADDRESS = "http://localhost:5000"; diff --git a/code/app/src/help/persistent-store.ts b/code/app/src/help/persistent-store.ts index f2c14c9..6a54282 100644 --- a/code/app/src/help/persistent-store.ts +++ b/code/app/src/help/persistent-store.ts @@ -1,3 +1,4 @@ +import {browser} from "$app/environment"; import {writable as _writable, readable as _readable} from "svelte/store"; import type {Writable, Readable, StartStopNotifier} from "svelte/store"; @@ -28,6 +29,7 @@ interface ReadableStore { } function get_store(type: StoreType): Storage { + if (!browser) return undefined; switch (type) { case StoreType.SESSION: return window.sessionStorage; @@ -48,6 +50,7 @@ function prepared_store_value(value: any): string { function get_store_value(options: WritableStore | ReadableStore): any { try { const storage = get_store(options.options.store); + if (!storage) return; const value = storage.getItem(options.name); if (!value) return false; return JSON.parse(value); @@ -64,6 +67,7 @@ function hydrate(store: Writable, options: WritableStore | ReadableStor function subscribe(store: Writable | Readable, options: WritableStore | ReadableStore): void { const storage = get_store(options.options.store); + if (!storage) return; if (!store.subscribe) return; store.subscribe((state: any) => { storage.setItem(options.name, prepared_store_value(state)); @@ -71,6 +75,10 @@ function subscribe(store: Writable | Readable, options: WritableStore(options: WritableStore): Writable { + if (!browser) { + console.warn("Persistent store is only available in the browser"); + return; + } if (options.options === undefined) options.options = default_store_options; console.log("Creating writable store with options: ", options); const store = _writable(options.initialState); @@ -80,6 +88,10 @@ function writable_persistent(options: WritableStore): Writable { } function readable_persistent(options: ReadableStore): Readable { + if (!browser) { + console.warn("Persistent store is only available in the browser"); + return; + } if (options.options === undefined) options.options = default_store_options; console.log("Creating readable store with options: ", options); const store = _readable(options.initialState, options.callback); diff --git a/code/app/src/i18n/en/index.ts b/code/app/src/i18n/en/index.ts index fbf5423..b38eb48 100644 --- a/code/app/src/i18n/en/index.ts +++ b/code/app/src/i18n/en/index.ts @@ -27,6 +27,7 @@ const en: BaseTranslation = { createRecordButtonText: "Press enter or click here to create {0}" }, signInPage: { + title: "Sign in", notMyComputer: "This is not my computer", resetPassword: "Reset password", yourPasswordIsUpdated: "Your password is updated", @@ -39,9 +40,12 @@ const en: BaseTranslation = { feelFreeToSignInAgain: "Feel free to sign in again" }, signUpPage: { + title: "Sign up", createYourNewAccount: "Create your new account", }, resetPasswordPage: { + title: "Reset password", + fulfillTitle: "Set new password", setANewPassword: "Set a new password", expired: "Expired", requestHasExpired: "Your request has expired", diff --git a/code/app/src/i18n/i18n-types.ts b/code/app/src/i18n/i18n-types.ts index cf968d7..ef1d664 100644 --- a/code/app/src/i18n/i18n-types.ts +++ b/code/app/src/i18n/i18n-types.ts @@ -116,6 +116,10 @@ type RootTranslation = { createRecordButtonText: RequiredParams<'0'> } signInPage: { + /** + * S​i​g​n​ ​i​n + */ + title: string /** * T​h​i​s​ ​i​s​ ​n​o​t​ ​m​y​ ​c​o​m​p​u​t​e​r */ @@ -158,12 +162,24 @@ type RootTranslation = { feelFreeToSignInAgain: string } signUpPage: { + /** + * S​i​g​n​ ​u​p + */ + title: string /** * C​r​e​a​t​e​ ​y​o​u​r​ ​n​e​w​ ​a​c​c​o​u​n​t */ createYourNewAccount: string } resetPasswordPage: { + /** + * R​e​s​e​t​ ​p​a​s​s​w​o​r​d + */ + title: string + /** + * S​e​t​ ​n​e​w​ ​p​a​s​s​w​o​r​d + */ + fulfillTitle: string /** * S​e​t​ ​a​ ​n​e​w​ ​p​a​s​s​w​o​r​d */ @@ -325,6 +341,10 @@ export type TranslationFunctions = { createRecordButtonText: (arg0: unknown) => LocalizedString } signInPage: { + /** + * Sign in + */ + title: () => LocalizedString /** * This is not my computer */ @@ -367,12 +387,24 @@ export type TranslationFunctions = { feelFreeToSignInAgain: () => LocalizedString } signUpPage: { + /** + * Sign up + */ + title: () => LocalizedString /** * Create your new account */ createYourNewAccount: () => LocalizedString } resetPasswordPage: { + /** + * Reset password + */ + title: () => LocalizedString + /** + * Set new password + */ + fulfillTitle: () => LocalizedString /** * Set a new password */ diff --git a/code/app/src/routes/(main)/(app)/+layout.svelte b/code/app/src/routes/(main)/(app)/+layout.svelte index 6cb70ef..e57bc3b 100644 --- a/code/app/src/routes/(main)/(app)/+layout.svelte +++ b/code/app/src/routes/(main)/(app)/+layout.svelte @@ -10,7 +10,7 @@ QueueListIcon, CalendarIcon, } from "$components/icons"; - import {AccountService} from "$services/account-service"; + import { AccountService } from "$services/account-service"; import { Dialog, Menu, @@ -21,11 +21,11 @@ TransitionChild, TransitionRoot, } from "@rgossiaux/svelte-headlessui"; - import {DialogPanel} from "@developermuch/dev-svelte-headlessui"; - import {Input} from "$components"; - import {goto} from "$app/navigation"; - import {page} from "$app/stores"; - + import { DialogPanel } from "@developermuch/dev-svelte-headlessui"; + import { Input } from "$components"; + import { goto } from "$app/navigation"; + import { page } from "$app/stores"; + const accountService = new AccountService(); const session = { @@ -76,45 +76,45 @@ (sidebarOpen = false)}> -
+
@@ -124,15 +124,17 @@ {#each navigationItems as item} {@const current = $page.url.pathname.startsWith(item.href)} @@ -155,52 +157,52 @@ - - - - - {session.profile.username} - - {session.profile.displayName} - - - + + + + + {session.profile.username} + + {session.profile.displayName} + + +
- sign_out()} - class="text-gray-700 block px-4 py-2 text-sm hover:bg-red-200 hover:text-red-900 cursor-pointer" - > - Sign out - + sign_out()} + class="text-gray-700 block px-4 py-2 text-sm hover:bg-red-200 hover:text-red-900 cursor-pointer" + > + Sign out +
@@ -210,8 +212,7 @@ @@ -220,15 +221,15 @@ {#each navigationItems as item} {@const current = $page.url.pathname.startsWith(item.href)} @@ -243,12 +244,12 @@
@@ -256,12 +257,12 @@
@@ -271,35 +272,38 @@
Open user menu
- View - profile + View profile - - Settings + + Settings +
- sign_out()} - class="text-gray-700 block px-4 py-2 text-sm"> Sign out + sign_out()} class="text-gray-700 block px-4 py-2 text-sm"> + Sign out +
@@ -310,7 +314,7 @@
- +
diff --git a/code/app/src/routes/(main)/(public)/+layout.svelte b/code/app/src/routes/(main)/(public)/+layout.svelte index 0d84f9a..6da653c 100644 --- a/code/app/src/routes/(main)/(public)/+layout.svelte +++ b/code/app/src/routes/(main)/(public)/+layout.svelte @@ -1,18 +1,18 @@ - - + + diff --git a/code/app/src/routes/(main)/(public)/reset-password/+page.svelte b/code/app/src/routes/(main)/(public)/reset-password/+page.svelte index 34dabae..55859f6 100644 --- a/code/app/src/routes/(main)/(public)/reset-password/+page.svelte +++ b/code/app/src/routes/(main)/(public)/reset-password/+page.svelte @@ -1,8 +1,8 @@ + + Reset password - Greatoffice + +

@@ -61,21 +65,21 @@
{#if showErrorAlert} - + {:else if showSuccessAlert} - + {/if} -

diff --git a/code/app/src/routes/(main)/(public)/reset-password/+page.ts b/code/app/src/routes/(main)/(public)/reset-password/+page.ts new file mode 100644 index 0000000..c0859e0 --- /dev/null +++ b/code/app/src/routes/(main)/(public)/reset-password/+page.ts @@ -0,0 +1,11 @@ +import LL from '$i18n/i18n-svelte'; +import { get } from 'svelte/store'; +import type { PageLoad } from './$types'; + +const l = get(LL); + +export const load: PageLoad = async () => { + return { + title: l.resetPasswordPage.title(), + }; +}; \ No newline at end of file diff --git a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.ts b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.ts new file mode 100644 index 0000000..3252b7a --- /dev/null +++ b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.ts @@ -0,0 +1,11 @@ +import LL from '$i18n/i18n-svelte'; +import { get } from 'svelte/store'; +import type { PageLoad } from './$types'; + +const l = get(LL); + +export const load: PageLoad = async () => { + return { + title: l.resetPasswordPage.fulfillTitle(), + }; +}; \ No newline at end of file diff --git a/code/app/src/routes/(main)/(public)/sign-in/+page.svelte b/code/app/src/routes/(main)/(public)/sign-in/+page.svelte index e862050..c4ecb1a 100644 --- a/code/app/src/routes/(main)/(public)/sign-in/+page.svelte +++ b/code/app/src/routes/(main)/(public)/sign-in/+page.svelte @@ -1,13 +1,13 @@ - + + + {$page.data.title ? $page.data.title + " - Greatoffice" : "Greatoffice"} + {#if !online}
- +

You seem to be offline, please check your internet connection.

@@ -24,4 +28,4 @@
{/if} - + diff --git a/code/app/src/routes/(main)/+layout.ts b/code/app/src/routes/(main)/+layout.ts index 0509aaf..3893260 100644 --- a/code/app/src/routes/(main)/+layout.ts +++ b/code/app/src/routes/(main)/+layout.ts @@ -1,10 +1,10 @@ -import type {LayoutLoad} from "./$types"; -import type {Locales} from "$i18n/i18n-types"; -import {loadLocaleAsync} from "$i18n/i18n-util.async"; -import {setLocale} from "$i18n/i18n-svelte"; +import type { LayoutLoad } from "./$types"; +import type { Locales } from "$i18n/i18n-types"; +import { loadLocaleAsync } from "$i18n/i18n-util.async"; +import { setLocale } from "$i18n/i18n-svelte"; -export const load: LayoutLoad<{ locale: Locales }> = async ({data: {locale}}) => { +export const load: LayoutLoad<{ locale: Locales }> = async ({ data: { locale } }) => { await loadLocaleAsync(locale); setLocale(locale); - return {locale}; + return { locale }; }; \ No newline at end of file diff --git a/code/app/src/services/account-service.ts b/code/app/src/services/account-service.ts index 9d45950..92c6126 100644 --- a/code/app/src/services/account-service.ts +++ b/code/app/src/services/account-service.ts @@ -1,4 +1,5 @@ import {http_delete_async, http_get_async, http_post_async} from "$api/_fetch"; +import {browser} from "$app/environment"; import {api_base, CookieNames, StorageKeys} from "$configuration"; import {is_known_problem} from "$models/internal/KnownProblem"; import {log_debug} from "$help/logger"; @@ -19,24 +20,29 @@ import type { } from "./abstractions/IAccountService"; export class AccountService implements IAccountService { - session: Writable; + session: Writable | undefined; private sessionCooldown = 3600; constructor() { - this.session = writable_persistent({ - name: StorageKeys.session, - initialState: {} as Session, - options: { - store: StoreType.LOCAL, - }, - }); - this.refresh_session(); + if (browser) { + this.session = writable_persistent({ + name: StorageKeys.session, + initialState: {} as Session, + options: { + store: StoreType.LOCAL, + }, + }); + this.refresh_session(); + } else { + this.session = undefined; + } } async refresh_session(forceRefresh: boolean = false): Promise { + if (!this.session) return; const currentValue = get(this.session); const currentEpoch = Temporal.Now.instant().epochSeconds; - if (currentValue?._lastUpdated + this.sessionCooldown < currentEpoch) { + if (!forceRefresh && ((currentValue?._lastUpdated ?? 0) + this.sessionCooldown) > currentEpoch) { log_debug("Session is not stale yet", { currentEpoch, staleEpoch: currentValue?._lastUpdated + this.sessionCooldown, @@ -45,11 +51,14 @@ export class AccountService implements IAccountService { } const sessionResponse = await http_get_async(api_base("_/account/session")); if (sessionResponse.ok) { - + this.session.set(await sessionResponse.json()); + } else { + this.session.set(null); } } async end_session(callback: Function): Promise { + if (!this.session) return; await this.logout_async(); this.session.set(null); if (typeof callback === "function") callback(); @@ -70,14 +79,12 @@ export class AccountService implements IAccountService { async logout_async(): Promise { const response = await http_get_async(api_base("_/account/logout")); - if (!response.ok) { const deleteCookieResponse = await fetch("/delete-cookie?key=" + CookieNames.session); if (!deleteCookieResponse.ok) { throw new Error("Could neither logout nor delete session cookie."); } } - return; } @@ -88,7 +95,6 @@ export class AccountService implements IAccountService { isCreated: false, knownProblem: await response.json(), }; - return { isCreated: false, }; @@ -108,7 +114,6 @@ export class AccountService implements IAccountService { isUpdated: false, knownProblem: await response.json(), }; - return { isUpdated: false, }; -- cgit v1.3