diff options
Diffstat (limited to 'code/app')
| -rw-r--r-- | code/app/src/actions/pwKey.ts | 2 | ||||
| -rw-r--r-- | code/app/src/api/_fetch.ts (renamed from code/app/src/lib/api/_fetch.ts) | 19 | ||||
| -rw-r--r-- | code/app/src/api/account/index.ts (renamed from code/app/src/lib/api/account/index.ts) | 2 | ||||
| -rw-r--r-- | code/app/src/api/api-tokens/index.ts (renamed from code/app/src/lib/api/api-tokens/index.ts) | 6 | ||||
| -rw-r--r-- | code/app/src/api/projects/index.ts | 12 | ||||
| -rw-r--r-- | code/app/src/components/alert.svelte (renamed from code/app/src/lib/components/alert.svelte) | 50 | ||||
| -rw-r--r-- | code/app/src/components/badge.svelte (renamed from code/app/src/lib/components/badge.svelte) | 19 | ||||
| -rw-r--r-- | code/app/src/components/button.svelte (renamed from code/app/src/lib/components/button.svelte) | 23 | ||||
| -rw-r--r-- | code/app/src/components/checkbox.svelte (renamed from code/app/src/lib/components/checkbox.svelte) | 18 | ||||
| -rw-r--r-- | code/app/src/components/combobox.svelte (renamed from code/app/src/lib/components/combobox.svelte) | 126 | ||||
| -rw-r--r-- | code/app/src/components/icons/adjustments.svelte (renamed from code/app/src/lib/components/icons/adjustments.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/bars-3-center-left.svelte (renamed from code/app/src/lib/components/icons/bars-3-center-left.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/calendar.svelte (renamed from code/app/src/lib/components/icons/calendar.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/check-circle.svelte (renamed from code/app/src/lib/components/icons/check-circle.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/chevron-down.svelte (renamed from code/app/src/lib/components/icons/chevron-down.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/chevron-up-down.svelte (renamed from code/app/src/lib/components/icons/chevron-up-down.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/chevron-up.svelte (renamed from code/app/src/lib/components/icons/chevron-up.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/database.svelte (renamed from code/app/src/lib/components/icons/database.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/exclamation-circle.svelte (renamed from code/app/src/lib/components/icons/exclamation-circle.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/exclamation-triangle.svelte (renamed from code/app/src/lib/components/icons/exclamation-triangle.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/folder-open.svelte (renamed from code/app/src/lib/components/icons/folder-open.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/funnel.svelte (renamed from code/app/src/lib/components/icons/funnel.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/home.svelte (renamed from code/app/src/lib/components/icons/home.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/index.ts (renamed from code/app/src/lib/components/icons/index.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/information-circle.svelte (renamed from code/app/src/lib/components/icons/information-circle.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/magnifying-glass.svelte (renamed from code/app/src/lib/components/icons/magnifying-glass.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/megaphone.svelte (renamed from code/app/src/lib/components/icons/megaphone.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/menu.svelte (renamed from code/app/src/lib/components/icons/menu.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/queue-list.svelte (renamed from code/app/src/lib/components/icons/queue-list.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/spinner.svelte (renamed from code/app/src/lib/components/icons/spinner.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/x-circle.svelte (renamed from code/app/src/lib/components/icons/x-circle.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/x-mark.svelte (renamed from code/app/src/lib/components/icons/x-mark.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/icons/x.svelte (renamed from code/app/src/lib/components/icons/x.svelte) | 0 | ||||
| -rw-r--r-- | code/app/src/components/index.ts (renamed from code/app/src/lib/components/index.ts) | 4 | ||||
| -rw-r--r-- | code/app/src/components/input.svelte (renamed from code/app/src/lib/components/input.svelte) | 25 | ||||
| -rw-r--r-- | code/app/src/components/locale-switcher.svelte (renamed from code/app/src/lib/components/locale-switcher.svelte) | 22 | ||||
| -rw-r--r-- | code/app/src/components/project-status-badge.svelte (renamed from code/app/src/lib/components/project-status-badge.svelte) | 5 | ||||
| -rw-r--r-- | code/app/src/components/switch.svelte (renamed from code/app/src/lib/components/switch.svelte) | 72 | ||||
| -rw-r--r-- | code/app/src/components/textarea.svelte (renamed from code/app/src/lib/components/textarea.svelte) | 22 | ||||
| -rw-r--r-- | code/app/src/configuration/index.ts (renamed from code/app/src/lib/configuration.ts) | 16 | ||||
| -rw-r--r-- | code/app/src/help/colors.ts (renamed from code/app/src/lib/colors.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/help/index.ts (renamed from code/app/src/lib/helpers.ts) | 18 | ||||
| -rw-r--r-- | code/app/src/help/logger.ts (renamed from code/app/src/lib/logger.ts) | 6 | ||||
| -rw-r--r-- | code/app/src/help/persistent-store.ts (renamed from code/app/src/lib/persistent-store.ts) | 12 | ||||
| -rw-r--r-- | code/app/src/hooks.server.ts | 32 | ||||
| -rw-r--r-- | code/app/src/i18n/en/app/index.ts (renamed from code/app/src/lib/i18n/en/app/index.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/en/index.ts (renamed from code/app/src/lib/i18n/en/index.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/formatters.ts | 13 | ||||
| -rw-r--r-- | code/app/src/i18n/i18n-svelte.ts (renamed from code/app/src/lib/i18n/i18n-svelte.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/i18n-types.ts (renamed from code/app/src/lib/i18n/i18n-types.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/i18n-util.async.ts (renamed from code/app/src/lib/i18n/i18n-util.async.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/i18n-util.sync.ts (renamed from code/app/src/lib/i18n/i18n-util.sync.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/i18n-util.ts (renamed from code/app/src/lib/i18n/i18n-util.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/nb/app/index.ts (renamed from code/app/src/lib/i18n/nb/app/index.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/i18n/nb/index.ts (renamed from code/app/src/lib/i18n/nb/index.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/lib/api/projects/index.ts | 12 | ||||
| -rw-r--r-- | code/app/src/lib/i18n/formatters.ts | 13 | ||||
| -rw-r--r-- | code/app/src/lib/models/internal/ISession.ts | 8 | ||||
| -rw-r--r-- | code/app/src/lib/models/work/WorkQuery.ts | 30 | ||||
| -rw-r--r-- | code/app/src/lib/services/account-service.ts | 61 | ||||
| -rw-r--r-- | code/app/src/lib/session.ts | 69 | ||||
| -rw-r--r-- | code/app/src/models/base/Customer.ts (renamed from code/app/src/lib/models/base/Customer.ts) | 4 | ||||
| -rw-r--r-- | code/app/src/models/base/CustomerContact.ts (renamed from code/app/src/lib/models/base/CustomerContact.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/base/CustomerEvent.ts (renamed from code/app/src/lib/models/base/CustomerEvent.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/base/SessionData.ts (renamed from code/app/src/lib/models/base/SessionData.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/base/Tenant.ts (renamed from code/app/src/lib/models/base/Tenant.ts) | 2 | ||||
| -rw-r--r-- | code/app/src/models/base/User.ts (renamed from code/app/src/lib/models/base/User.ts) | 2 | ||||
| -rw-r--r-- | code/app/src/models/base/UserRole.ts (renamed from code/app/src/lib/models/base/UserRole.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/internal/FormError.ts (renamed from code/app/src/lib/models/internal/FormError.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/internal/KnownProblem.ts (renamed from code/app/src/lib/models/internal/KnownProblem.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/projects/Project.ts (renamed from code/app/src/lib/models/projects/Project.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/projects/ProjectLabel.ts (renamed from code/app/src/lib/models/projects/ProjectLabel.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/projects/ProjectMember.ts (renamed from code/app/src/lib/models/projects/ProjectMember.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/projects/ProjectMeta.ts (renamed from code/app/src/lib/models/projects/ProjectMeta.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/projects/ProjectRole.ts (renamed from code/app/src/lib/models/projects/ProjectRole.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/projects/ProjectStatus.ts (renamed from code/app/src/lib/models/projects/ProjectStatus.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/work/WorkCategory.ts (renamed from code/app/src/lib/models/work/WorkCategory.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/work/WorkEntry.ts (renamed from code/app/src/lib/models/work/WorkEntry.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/work/WorkEntryQueryResponse.ts (renamed from code/app/src/lib/models/work/WorkEntryQueryResponse.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/work/WorkLabel.ts (renamed from code/app/src/lib/models/work/WorkLabel.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/models/work/WorkQuery.ts | 17 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(app)/+layout.svelte | 534 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(app)/projects/+page.svelte | 228 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(app)/projects/create/+page.svelte | 20 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(app)/settings/+page.svelte | 106 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(public)/+layout.svelte | 8 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(public)/reset-password/+page.svelte | 30 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts | 10 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte | 33 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(public)/sign-in/+page.svelte | 88 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts | 12 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/(public)/sign-up/+page.svelte | 46 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/+layout.server.ts | 35 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/+layout.svelte | 12 | ||||
| -rw-r--r-- | code/app/src/routes/(main)/+layout.ts | 18 | ||||
| -rw-r--r-- | code/app/src/routes/book/alerts/+page.svelte | 50 | ||||
| -rw-r--r-- | code/app/src/routes/book/badges/+page.svelte | 25 | ||||
| -rw-r--r-- | code/app/src/routes/book/buttons/+page.svelte | 16 | ||||
| -rw-r--r-- | code/app/src/routes/book/inputs/+page.svelte | 28 | ||||
| -rw-r--r-- | code/app/src/routes/book/toggles/+page.svelte | 12 | ||||
| -rw-r--r-- | code/app/src/services/abstractions/IAccountService.ts (renamed from code/app/src/lib/services/abstractions/IAccountService.ts) | 20 | ||||
| -rw-r--r-- | code/app/src/services/abstractions/IPasswordResetService.ts (renamed from code/app/src/lib/services/abstractions/IPasswordResetService.ts) | 2 | ||||
| -rw-r--r-- | code/app/src/services/abstractions/ISettingsService.ts (renamed from code/app/src/lib/services/abstractions/ISettingsService.ts) | 0 | ||||
| -rw-r--r-- | code/app/src/services/account-service.ts | 116 | ||||
| -rw-r--r-- | code/app/src/services/password-reset-service.ts (renamed from code/app/src/lib/services/password-reset-service.ts) | 41 | ||||
| -rw-r--r-- | code/app/src/services/settings-service.ts (renamed from code/app/src/lib/services/settings-service.ts) | 2 | ||||
| -rw-r--r-- | code/app/svelte.config.js | 9 |
107 files changed, 1133 insertions, 1110 deletions
diff --git a/code/app/src/actions/pwKey.ts b/code/app/src/actions/pwKey.ts index a2f22e7..351a4b8 100644 --- a/code/app/src/actions/pwKey.ts +++ b/code/app/src/actions/pwKey.ts @@ -1,4 +1,4 @@ -import { is_development, is_testing } from "$lib/configuration"; +import {is_development, is_testing} from "$configuration"; export default function pwKey(node: HTMLElement, value: string | undefined) { if (!value) return; diff --git a/code/app/src/lib/api/_fetch.ts b/code/app/src/api/_fetch.ts index c29d262..370b071 100644 --- a/code/app/src/lib/api/_fetch.ts +++ b/code/app/src/api/_fetch.ts @@ -1,28 +1,27 @@ -import { Temporal } from "temporal-polyfill"; -import { clear_session_data } from "$lib/session"; -import { redirect } from "@sveltejs/kit"; -import { browser } from "$app/environment"; -import { goto } from "$app/navigation"; -import { SignInPageMessage, signInPageMessageQueryKey } from "$routes/(main)/(public)/sign-in"; -import { log_error } from "$lib/logger"; +import {Temporal} from "temporal-polyfill"; +import {redirect} from "@sveltejs/kit"; +import {browser} from "$app/environment"; +import {goto} from "$app/navigation"; +import {SignInPageMessage, signInPageMessageQueryKey} from "$routes/(main)/(public)/sign-in"; +import {log_error} from "$help/logger"; export async function http_post_async(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> { const init = make_request_init("post", body, abort_signal); - const response = await internal_fetch_async({ url, init, timeout }); + const response = await internal_fetch_async({url, init, timeout}); if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401"); return response; } export async function http_get_async(url: string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> { const init = make_request_init("get", undefined, abort_signal); - const response = await internal_fetch_async({ url, init, timeout }); + const response = await internal_fetch_async({url, init, timeout}); if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401"); return response; } export async function http_delete_async(url: string, body?: object | string, timeout = -1, skip_401_check = false, abort_signal?: AbortSignal): Promise<Response> { const init = make_request_init("delete", body, abort_signal); - const response = await internal_fetch_async({ url, init, timeout }); + const response = await internal_fetch_async({url, init, timeout}); if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401"); return response; } diff --git a/code/app/src/lib/api/account/index.ts b/code/app/src/api/account/index.ts index 6dbcdc8..7cbcefc 100644 --- a/code/app/src/lib/api/account/index.ts +++ b/code/app/src/api/account/index.ts @@ -1,4 +1,4 @@ -import {api_base} from "$lib/configuration"; +import {api_base} from "$configuration"; import {http_delete_async, http_get_async, http_post_async} from "../_fetch"; export const http_account = { diff --git a/code/app/src/lib/api/api-tokens/index.ts b/code/app/src/api/api-tokens/index.ts index 77bfd91..6c27a06 100644 --- a/code/app/src/lib/api/api-tokens/index.ts +++ b/code/app/src/api/api-tokens/index.ts @@ -1,5 +1,5 @@ -import {http_delete_async, http_get_async, http_post_async} from "src/lib/api/_fetch"; -import {api_base} from "src/lib/configuration"; +import {http_delete_async, http_get_async, http_post_async} from "../_fetch"; +import {api_base} from "$configuration"; import type {Temporal} from "temporal-polyfill"; export const http_api_tokens = { @@ -10,7 +10,7 @@ export const http_api_tokens = { return http_delete_async(api_base("v1/api-tokens/delete?id=" + id)); }, get_tokens_async(): Promise<Response> { - return http_get_async(api_base("v1/api-tokens")) + return http_get_async(api_base("v1/api-tokens")); }, }; diff --git a/code/app/src/api/projects/index.ts b/code/app/src/api/projects/index.ts new file mode 100644 index 0000000..316785f --- /dev/null +++ b/code/app/src/api/projects/index.ts @@ -0,0 +1,12 @@ +import {api_base} from "$configuration"; +import {http_post_async} from "../_fetch"; + +export const http_projects = { + create_async(payload: CreateProjectPayload): Promise<Response> { + return http_post_async(api_base("projects/create"), payload); + }, +}; + +export type CreateProjectPayload = { + name: "" +}
\ No newline at end of file diff --git a/code/app/src/lib/components/alert.svelte b/code/app/src/components/alert.svelte index fd57105..b0abe81 100644 --- a/code/app/src/lib/components/alert.svelte +++ b/code/app/src/components/alert.svelte @@ -1,10 +1,10 @@ <script lang="ts"> - import { random_string } from "$lib/helpers"; - import { createEventDispatcher } from "svelte"; - import { onMount } from "svelte"; + import {random_string} from "$help"; + import {createEventDispatcher} from "svelte"; + import {onMount} from "svelte"; import pwKey from "$actions/pwKey"; - import { Temporal } from "temporal-polyfill"; - import { ExclamationTriangleIcon, CheckCircleIcon, InformationCircleIcon, XCircleIcon, XMarkIcon } from "./icons"; + import {Temporal} from "temporal-polyfill"; + import {ExclamationTriangleIcon, CheckCircleIcon, InformationCircleIcon, XCircleIcon, XMarkIcon} from "./icons"; const dispatch = createEventDispatcher(); const noCooldownSetting = "no-cooldown"; @@ -16,8 +16,8 @@ * An optional id for this alert, a default is set if not specified. * This value is necessary for closeable cooldown to work. */ - // if no unique id is supplied, cooldown will not work between page loads. - // Therefore we are disabling it with noCooldownSetting in the fallback id. + // if no unique id is supplied, cooldown will not work between page loads. + // Therefore we are disabling it with noCooldownSetting in the fallback id. export let id = "alert--" + noCooldownSetting + "--" + random_string(4); /** * The title to communicate, value is optional @@ -133,15 +133,15 @@ } const lastSeen = Temporal.Instant.fromEpochSeconds(parseInt(localStorage.getItem(cooldownStorageKey) ?? "-1")); - if (Temporal.Instant.compare(Temporal.Now.instant(), lastSeen.add({ seconds: parseInt(closeableCooldown) })) === 1) { + if (Temporal.Instant.compare(Temporal.Now.instant(), lastSeen.add({seconds: parseInt(closeableCooldown)})) === 1) { console.log( "Alert " + - id + - " has a cooldown of " + - closeableCooldown + - " and was last seen " + - lastSeen.toLocaleString() + - " making it due for a showing" + id + + " has a cooldown of " + + closeableCooldown + + " and was last seen " + + lastSeen.toLocaleString() + + " making it due for a showing", ); visible = true; } else { @@ -166,7 +166,7 @@ <div class="rounded-md bg-{colorClassPart}-50 p-4 {$$restProps.class ?? ''}" use:pwKey={_pwKey}> <div class="flex"> <div class="flex-shrink-0"> - <svelte:component this={iconComponent} class="text-{colorClassPart}-400" /> + <svelte:component this={iconComponent} class="text-{colorClassPart}-400"/> </div> <div class="ml-3 text-sm w-full"> {#if !rightLinkText} @@ -214,9 +214,9 @@ </div> <p class="mt-3 text-sm md:mt-0 md:ml-6 flex items-end"> <a - href={rightLinkHref} - on:click={() => rightLinkClicked()} - class="whitespace-nowrap font-medium text-{colorClassPart}-700 hover:text-{colorClassPart}-600" + href={rightLinkHref} + on:click={() => rightLinkClicked()} + class="whitespace-nowrap font-medium text-{colorClassPart}-700 hover:text-{colorClassPart}-600" > {rightLinkText} <span aria-hidden="true"> →</span> @@ -230,9 +230,9 @@ {#each actions as action} {@const color = action?.color ?? colorClassPart} <button - type="button" - on:click={() => actionClicked(action.id)} - class="rounded-md + type="button" + on:click={() => actionClicked(action.id)} + class="rounded-md bg-{color}-50 px-2 py-1.5 text-sm font-medium text-{color}-800 @@ -253,12 +253,12 @@ <div class="ml-auto pl-3"> <div class="-mx-1.5 -my-1.5"> <button - type="button" - on:click={() => close()} - class="inline-flex rounded-md bg-{colorClassPart}-50 p-1.5 text-{colorClassPart}-500 hover:bg-{colorClassPart}-100 focus:outline-none focus:ring-2 focus:ring-{colorClassPart}-600 focus:ring-offset-2 focus:ring-offset-{colorClassPart}-50" + type="button" + on:click={() => close()} + class="inline-flex rounded-md bg-{colorClassPart}-50 p-1.5 text-{colorClassPart}-500 hover:bg-{colorClassPart}-100 focus:outline-none focus:ring-2 focus:ring-{colorClassPart}-600 focus:ring-offset-2 focus:ring-offset-{colorClassPart}-50" > <span class="sr-only">Dismiss</span> - <XMarkIcon /> + <XMarkIcon/> </button> </div> </div> diff --git a/code/app/src/lib/components/badge.svelte b/code/app/src/components/badge.svelte index 6ec48d5..e89ef36 100644 --- a/code/app/src/lib/components/badge.svelte +++ b/code/app/src/components/badge.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { createEventDispatcher } from "svelte"; + import {createEventDispatcher} from "svelte"; export let id: string | undefined = undefined; export let type: "default" | "red" | "yellow" | "green" | "blue" | "tame" = "default"; @@ -17,7 +17,7 @@ const dispatch = createEventDispatcher(); function handle_remove(event) { - dispatch("remove", { event, id, text }); + dispatch("remove", {event, id, text}); } $: switch (type) { @@ -53,23 +53,24 @@ } </script> -<span class="inline-flex items-center font-medium {uppercase ? 'uppercase' : ''} bg-{colorName}-100 text-{colorName}-800 {sizeClass}" {id}> +<span class="inline-flex items-center font-medium {uppercase ? 'uppercase' : ''} bg-{colorName}-100 text-{colorName}-800 {sizeClass}" + {id}> {#if withDot} <svg class="{dotSizeClass} text-{colorName}-400" fill="currentColor" viewBox="0 0 8 8"> - <circle cx="4" cy="4" r="3" /> + <circle cx="4" cy="4" r="3"/> </svg> {/if} {text} {#if removable} <button - on:click={handle_remove} - tabindex={parseInt(tabindex)} - type="button" - class="ml-0.5 inline-flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full text-{colorName}-400 hover:bg-{colorName}-200 hover:text-{colorName}-500 focus:bg-{colorName}-500 focus:text-white focus:outline-none" + on:click={handle_remove} + tabindex={parseInt(tabindex)} + type="button" + class="ml-0.5 inline-flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-full text-{colorName}-400 hover:bg-{colorName}-200 hover:text-{colorName}-500 focus:bg-{colorName}-500 focus:text-white focus:outline-none" > <span class="sr-only">Remove badge</span> <svg class="h-2 w-2" stroke="currentColor" fill="none" viewBox="0 0 8 8"> - <path stroke-linecap="round" stroke-width="1.5" d="M1 1l6 6m0-6L1 7" /> + <path stroke-linecap="round" stroke-width="1.5" d="M1 1l6 6m0-6L1 7"/> </svg> </button> {/if} diff --git a/code/app/src/lib/components/button.svelte b/code/app/src/components/button.svelte index 49a9354..d573d01 100644 --- a/code/app/src/lib/components/button.svelte +++ b/code/app/src/components/button.svelte @@ -5,7 +5,7 @@ <script lang="ts"> import pwKey from "$actions/pwKey"; - import { SpinnerIcon } from "./icons"; + import {SpinnerIcon} from "./icons"; export let kind = "primary" as ButtonKind; export let size = "md" as ButtonSize; @@ -75,30 +75,30 @@ {#if href} <a - use:pwKey={_pwKey} - {...shared_props} - {href} - class="{sizeClasses} {kindClasses} {loading ? 'disabled:' : ''} {$$restProps.class ?? ''} {fullWidth + use:pwKey={_pwKey} + {...shared_props} + {href} + class="{sizeClasses} {kindClasses} {loading ? 'disabled:' : ''} {$$restProps.class ?? ''} {fullWidth ? 'w-full justify-center' : ''} inline-flex items-center border font-medium rounded shadow-sm focus:outline-none focus:ring-2" > {#if loading} - <SpinnerIcon class={spinnerTextClasses + " " + spinnerMarginClasses} /> + <SpinnerIcon class={spinnerTextClasses + " " + spinnerMarginClasses}/> {/if} {text} </a> {:else} <button - use:pwKey={_pwKey} - {...shared_props} - on:click - class="btn {sizeClasses} {kindClasses} {$$restProps.class ?? ''} + use:pwKey={_pwKey} + {...shared_props} + on:click + class="btn {sizeClasses} {kindClasses} {$$restProps.class ?? ''} {fullWidth ? 'w-full justify-center' : ''} inline-flex items-center border font-medium rounded shadow-sm focus:outline-none focus:ring-2" > {#if loading} - <SpinnerIcon class={spinnerTextClasses + " " + spinnerMarginClasses} /> + <SpinnerIcon class={spinnerTextClasses + " " + spinnerMarginClasses}/> {/if} {text} </button> @@ -109,6 +109,7 @@ border: 0px; outline: none; } + .reset:focus { outline: none; } diff --git a/code/app/src/lib/components/checkbox.svelte b/code/app/src/components/checkbox.svelte index 12ebedb..bf3c293 100644 --- a/code/app/src/lib/components/checkbox.svelte +++ b/code/app/src/components/checkbox.svelte @@ -1,6 +1,6 @@ <script lang="ts"> import pwKey from "$actions/pwKey"; - import { random_string } from "$lib/helpers"; + import {random_string} from "$help"; export let label: string; export let id: string | undefined = "input__" + random_string(4); @@ -13,14 +13,14 @@ <div class="flex items-center"> <input - {name} - use:pwKey={_pwKey} - {disabled} - {id} - {required} - type="checkbox" - bind:checked - class="h-4 w-4 text-teal-600 focus:ring-teal-500 border-gray-300 rounded" + {name} + use:pwKey={_pwKey} + {disabled} + {id} + {required} + type="checkbox" + bind:checked + class="h-4 w-4 text-teal-600 focus:ring-teal-500 border-gray-300 rounded" /> <label for={id} class="ml-2 block text-sm text-gray-900"> {@html required ? "<span class='text-red-500'>*</span>" : ""} diff --git a/code/app/src/lib/components/combobox.svelte b/code/app/src/components/combobox.svelte index 4e7b1cd..5df3ec9 100644 --- a/code/app/src/lib/components/combobox.svelte +++ b/code/app/src/components/combobox.svelte @@ -7,12 +7,12 @@ </script> <script lang="ts"> - import { CheckCircleIcon, ChevronUpDownIcon, XIcon } from "./icons"; - import { element_has_focus, random_string } from "$lib/helpers"; - import { go, highlight } from "fuzzysort"; + import {CheckCircleIcon, ChevronUpDownIcon, XIcon} from "./icons"; + import {element_has_focus, random_string} from "$help"; + import {go, highlight} from "fuzzysort"; import Badge from "./badge.svelte"; import Button from "./button.svelte"; - import LL from "$lib/i18n/i18n-svelte"; + import LL from "$i18n/i18n-svelte"; export let id = "combobox-" + random_string(3); export let label: string | undefined = undefined; @@ -26,7 +26,8 @@ export let loading = false; export let multiple = false; export let noResultsText: string = $LL.combobox.noRecordsFound(); - export let on_create_async = async ({ name: string }) => {}; + export let on_create_async = async ({name: string}) => { + }; export const reset = () => methods.reset(); export const select = (id: string) => methods.select_entry(id); @@ -138,11 +139,11 @@ }, async create_entry(name: string) { if (!name || !createable || loading) { - console.log("Not sending creation event due to failed preconditions", { name, createable, loading }); + console.log("Not sending creation event due to failed preconditions", {name, createable, loading}); return; } try { - await on_create_async({ name }); + await on_create_async({name}); searchValue = ""; loading = false; } catch (e) { @@ -296,10 +297,10 @@ </script> <svelte:window - on:keydown={windowEvents.on_keydown} - on:mousemove={windowEvents.on_mousemove} - on:touchend={windowEvents.on_touchend} - on:click={windowEvents.on_click} + on:keydown={windowEvents.on_keydown} + on:mousemove={windowEvents.on_mousemove} + on:touchend={windowEvents.on_touchend} + on:click={windowEvents.on_click} /> <div id={INTERNAL_ID} class:cursor-wait={loading}> @@ -311,53 +312,54 @@ {/if} <div class="relative {label ? 'mt-1' : ''}"> <div - on:click={search.on_input_wrapper_focus} - on:keypress={search.on_input_wrapper_focus} - class="cursor-text w-full flex rounded-md border bg-white py-2 pl-3 pr-12 sm:text-sm + on:click={search.on_input_wrapper_focus} + on:keypress={search.on_input_wrapper_focus} + class="cursor-text w-full flex rounded-md border bg-white py-2 pl-3 pr-12 sm:text-sm {inputHasFocus ? `border-${colorName}-500 outline-none ring-1 ring-${colorName}-500` : 'shadow-sm border-gray-300'}" > {#if multiple === true && hasSelection} <div class="flex gap-1 flex-wrap"> {#each options.filter((c) => c.selected === true) as option} <Badge - id={option.id} - removable - tabindex="-1" - on:remove={(e) => methods.deselect_entry(e.detail.id)} - text={option.name} + id={option.id} + removable + tabindex="-1" + on:remove={(e) => methods.deselect_entry(e.detail.id)} + text={option.name} /> {/each} </div> {/if} <div> <input - {...attributes} - type="text" - style="all: unset;" - role="combobox" - aria-controls={optionsListId} - aria-expanded={showDropdown} - bind:value={searchValue} - bind:this={searchInputNode} - on:input={() => search.do()} - on:click={search.on_input_click} - on:focus={search.on_input_focus} - on:blur={search.on_input_focusout} - autocomplete="off" + {...attributes} + type="text" + style="all: unset;" + role="combobox" + aria-controls={optionsListId} + aria-expanded={showDropdown} + bind:value={searchValue} + bind:this={searchInputNode} + on:input={() => search.do()} + on:click={search.on_input_click} + on:focus={search.on_input_focus} + on:blur={search.on_input_focusout} + autocomplete="off" /> {#if hasSelection} <button - type="button" - on:click={() => reset()} - title={$LL.reset()} - tabindex="-1" - class="text-gray-400 absolute cursor-pointer inset-y-0 right-0 flex items-center rounded-r-md px-2" + type="button" + on:click={() => reset()} + title={$LL.reset()} + tabindex="-1" + class="text-gray-400 absolute cursor-pointer inset-y-0 right-0 flex items-center rounded-r-md px-2" > - <XIcon /> + <XIcon/> </button> {:else} - <span tabindex="-1" class="text-gray-400 absolute inset-y-0 right-0 flex items-center rounded-r-md px-2"> - <ChevronUpDownIcon /> + <span tabindex="-1" + class="text-gray-400 absolute inset-y-0 right-0 flex items-center rounded-r-md px-2"> + <ChevronUpDownIcon/> </span> {/if} </div> @@ -368,7 +370,7 @@ </p> {/if} <div - class="tongue {showDropdown ? 'absolute' : 'hidden'} + class="tongue {showDropdown ? 'absolute' : 'hidden'} z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white text-base shadow-lg ring-1 ring-teal ring-opacity-5 focus:outline-none sm:text-sm" > @@ -376,13 +378,13 @@ {#if searchResults.length > 0} {#each searchResults.filter((c) => !c.selected) as result} <li - class="item" - data-id={result.obj.id} - aria-selected={result.obj.selected} - role="option" - on:click={on_select} - on:keypress={on_select} - tabindex="-1" + class="item" + data-id={result.obj.id} + aria-selected={result.obj.selected} + role="option" + on:click={on_select} + on:keypress={on_select} + tabindex="-1" > {@html highlight(result, '<span class="font-bold">', "</span>")} </li> @@ -394,18 +396,18 @@ Active: "text-white bg-indigo-600", Not Active: "text-gray-900" --> <li - class="item" - aria-selected={option.selected} - role="option" - data-id={option.id} - on:click={on_select} - on:keypress={on_select} - tabindex="-1" + class="item" + aria-selected={option.selected} + role="option" + data-id={option.id} + on:click={on_select} + on:keypress={on_select} + tabindex="-1" > <span class="block truncate {option.selected ? 'text-semibold' : ''}">{option.name}</span> {#if option.selected} <span class="absolute inset-y-0 right-0 flex items-center pr-4 text-{colorName}-600"> - <CheckCircleIcon /> + <CheckCircleIcon/> </span> {/if} </li> @@ -422,12 +424,12 @@ {#if showCreationHint} <div class="sticky bottom-0 w-full bg-white"> <Button - text={$LL.combobox.createRecordButtonText(searchValue.trim())} - title={$LL.combobox.createRecordButtonText(searchValue.trim())} - {loading} - kind="reset" - type="button" - on:click={() => methods.create_entry(searchValue.trim())} + text={$LL.combobox.createRecordButtonText(searchValue.trim())} + title={$LL.combobox.createRecordButtonText(searchValue.trim())} + {loading} + kind="reset" + type="button" + on:click={() => methods.create_entry(searchValue.trim())} /> </div> {/if} diff --git a/code/app/src/lib/components/icons/adjustments.svelte b/code/app/src/components/icons/adjustments.svelte index 83bda27..83bda27 100644 --- a/code/app/src/lib/components/icons/adjustments.svelte +++ b/code/app/src/components/icons/adjustments.svelte diff --git a/code/app/src/lib/components/icons/bars-3-center-left.svelte b/code/app/src/components/icons/bars-3-center-left.svelte index 785ece3..785ece3 100644 --- a/code/app/src/lib/components/icons/bars-3-center-left.svelte +++ b/code/app/src/components/icons/bars-3-center-left.svelte diff --git a/code/app/src/lib/components/icons/calendar.svelte b/code/app/src/components/icons/calendar.svelte index e0053ee..e0053ee 100644 --- a/code/app/src/lib/components/icons/calendar.svelte +++ b/code/app/src/components/icons/calendar.svelte diff --git a/code/app/src/lib/components/icons/check-circle.svelte b/code/app/src/components/icons/check-circle.svelte index e30778e..e30778e 100644 --- a/code/app/src/lib/components/icons/check-circle.svelte +++ b/code/app/src/components/icons/check-circle.svelte diff --git a/code/app/src/lib/components/icons/chevron-down.svelte b/code/app/src/components/icons/chevron-down.svelte index 5b29ece..5b29ece 100644 --- a/code/app/src/lib/components/icons/chevron-down.svelte +++ b/code/app/src/components/icons/chevron-down.svelte diff --git a/code/app/src/lib/components/icons/chevron-up-down.svelte b/code/app/src/components/icons/chevron-up-down.svelte index c07aed5..c07aed5 100644 --- a/code/app/src/lib/components/icons/chevron-up-down.svelte +++ b/code/app/src/components/icons/chevron-up-down.svelte diff --git a/code/app/src/lib/components/icons/chevron-up.svelte b/code/app/src/components/icons/chevron-up.svelte index 289e71d..289e71d 100644 --- a/code/app/src/lib/components/icons/chevron-up.svelte +++ b/code/app/src/components/icons/chevron-up.svelte diff --git a/code/app/src/lib/components/icons/database.svelte b/code/app/src/components/icons/database.svelte index 6ffdadb..6ffdadb 100644 --- a/code/app/src/lib/components/icons/database.svelte +++ b/code/app/src/components/icons/database.svelte diff --git a/code/app/src/lib/components/icons/exclamation-circle.svelte b/code/app/src/components/icons/exclamation-circle.svelte index 2ce79b1..2ce79b1 100644 --- a/code/app/src/lib/components/icons/exclamation-circle.svelte +++ b/code/app/src/components/icons/exclamation-circle.svelte diff --git a/code/app/src/lib/components/icons/exclamation-triangle.svelte b/code/app/src/components/icons/exclamation-triangle.svelte index 8d807db..8d807db 100644 --- a/code/app/src/lib/components/icons/exclamation-triangle.svelte +++ b/code/app/src/components/icons/exclamation-triangle.svelte diff --git a/code/app/src/lib/components/icons/folder-open.svelte b/code/app/src/components/icons/folder-open.svelte index 409c8e2..409c8e2 100644 --- a/code/app/src/lib/components/icons/folder-open.svelte +++ b/code/app/src/components/icons/folder-open.svelte diff --git a/code/app/src/lib/components/icons/funnel.svelte b/code/app/src/components/icons/funnel.svelte index 7e9daeb..7e9daeb 100644 --- a/code/app/src/lib/components/icons/funnel.svelte +++ b/code/app/src/components/icons/funnel.svelte diff --git a/code/app/src/lib/components/icons/home.svelte b/code/app/src/components/icons/home.svelte index ee8305d..ee8305d 100644 --- a/code/app/src/lib/components/icons/home.svelte +++ b/code/app/src/components/icons/home.svelte diff --git a/code/app/src/lib/components/icons/index.ts b/code/app/src/components/icons/index.ts index eb5b439..eb5b439 100644 --- a/code/app/src/lib/components/icons/index.ts +++ b/code/app/src/components/icons/index.ts diff --git a/code/app/src/lib/components/icons/information-circle.svelte b/code/app/src/components/icons/information-circle.svelte index 68dbc60..68dbc60 100644 --- a/code/app/src/lib/components/icons/information-circle.svelte +++ b/code/app/src/components/icons/information-circle.svelte diff --git a/code/app/src/lib/components/icons/magnifying-glass.svelte b/code/app/src/components/icons/magnifying-glass.svelte index f8fdb6e..f8fdb6e 100644 --- a/code/app/src/lib/components/icons/magnifying-glass.svelte +++ b/code/app/src/components/icons/magnifying-glass.svelte diff --git a/code/app/src/lib/components/icons/megaphone.svelte b/code/app/src/components/icons/megaphone.svelte index 7ada5f3..7ada5f3 100644 --- a/code/app/src/lib/components/icons/megaphone.svelte +++ b/code/app/src/components/icons/megaphone.svelte diff --git a/code/app/src/lib/components/icons/menu.svelte b/code/app/src/components/icons/menu.svelte index 471d85f..471d85f 100644 --- a/code/app/src/lib/components/icons/menu.svelte +++ b/code/app/src/components/icons/menu.svelte diff --git a/code/app/src/lib/components/icons/queue-list.svelte b/code/app/src/components/icons/queue-list.svelte index 6148394..6148394 100644 --- a/code/app/src/lib/components/icons/queue-list.svelte +++ b/code/app/src/components/icons/queue-list.svelte diff --git a/code/app/src/lib/components/icons/spinner.svelte b/code/app/src/components/icons/spinner.svelte index 80cc57c..80cc57c 100644 --- a/code/app/src/lib/components/icons/spinner.svelte +++ b/code/app/src/components/icons/spinner.svelte diff --git a/code/app/src/lib/components/icons/x-circle.svelte b/code/app/src/components/icons/x-circle.svelte index 3793b5a..3793b5a 100644 --- a/code/app/src/lib/components/icons/x-circle.svelte +++ b/code/app/src/components/icons/x-circle.svelte diff --git a/code/app/src/lib/components/icons/x-mark.svelte b/code/app/src/components/icons/x-mark.svelte index fd1c6a1..fd1c6a1 100644 --- a/code/app/src/lib/components/icons/x-mark.svelte +++ b/code/app/src/components/icons/x-mark.svelte diff --git a/code/app/src/lib/components/icons/x.svelte b/code/app/src/components/icons/x.svelte index 6125ab8..6125ab8 100644 --- a/code/app/src/lib/components/icons/x.svelte +++ b/code/app/src/components/icons/x.svelte diff --git a/code/app/src/lib/components/index.ts b/code/app/src/components/index.ts index d6abd4c..2285f08 100644 --- a/code/app/src/lib/components/index.ts +++ b/code/app/src/components/index.ts @@ -19,5 +19,5 @@ export { Checkbox, Input, LocaleSwitcher, - Switch -}
\ No newline at end of file + Switch, +};
\ No newline at end of file diff --git a/code/app/src/lib/components/input.svelte b/code/app/src/components/input.svelte index 80b1543..4fcec16 100644 --- a/code/app/src/lib/components/input.svelte +++ b/code/app/src/components/input.svelte @@ -1,8 +1,7 @@ <script lang="ts"> import pwKey from "$actions/pwKey"; - import { random_string } from "$lib/helpers"; - import { error } from "@sveltejs/kit"; - import { ExclamationCircleIcon } from "./icons"; + import {random_string} from "$help"; + import {ExclamationCircleIcon} from "./icons"; export let label: string | undefined = undefined; export let type: string = "text"; @@ -67,7 +66,7 @@ <div class="{label ? 'mt-1' : ''} {hasBling ? 'relative rounded-md' : ''} {addon ? 'flex' : ''}"> {#if icon} <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"> - <svelte:component this={icon} class={errorText ? "text-red-500" : "text-gray-400"} /> + <svelte:component this={icon} class={errorText ? "text-red-500" : "text-gray-400"}/> </div> {:else if addon} <div class="inline-flex items-center rounded-l-md border border-r-0 border-gray-300 bg-gray-50 px-3 text-gray-500 sm:text-sm"> @@ -75,22 +74,22 @@ </div> {/if} <input - use:typeAction - use:pwKey={_pwKey} - {name} - {id} - {...attributes} - bind:value - class="block w-full rounded-md shadow-sm sm:text-sm + use:typeAction + use:pwKey={_pwKey} + {name} + {id} + {...attributes} + bind:value + class="block w-full rounded-md shadow-sm sm:text-sm {colorClass} {disabled ? 'disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500' : ''} {addon ? 'min-w-0 flex-1 rounded-none rounded-r-md' : ''} {icon ? 'pl-10' : ''}" - {placeholder} + {placeholder} /> {#if errorText} <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3"> - <ExclamationCircleIcon class="text-red-500" /> + <ExclamationCircleIcon class="text-red-500"/> </div> {/if} </div> diff --git a/code/app/src/lib/components/locale-switcher.svelte b/code/app/src/components/locale-switcher.svelte index 3681bf5..fc03f39 100644 --- a/code/app/src/lib/components/locale-switcher.svelte +++ b/code/app/src/components/locale-switcher.svelte @@ -1,12 +1,12 @@ <script lang="ts"> import pwKey from "$actions/pwKey"; - import { browser } from "$app/environment"; - import { page } from "$app/stores"; - import { CookieNames } from "$lib/configuration"; - import { setLocale, locale } from "$lib/i18n/i18n-svelte"; - import type { Locales } from "$lib/i18n/i18n-types"; - import { locales } from "$lib/i18n/i18n-util"; - import { loadLocaleAsync } from "$lib/i18n/i18n-util.async"; + import {browser} from "$app/environment"; + import {page} from "$app/stores"; + import {CookieNames} from "$configuration"; + import {setLocale, locale} from "$i18n/i18n-svelte"; + import type {Locales} from "$i18n/i18n-types"; + import {locales} from "$i18n/i18n-util"; + import {loadLocaleAsync} from "$i18n/i18n-util.async"; import Cookies from "js-cookie"; export let _pwKey: string | undefined = undefined; @@ -45,10 +45,10 @@ </script> <select - {tabindex} - use:pwKey={_pwKey} - on:change={on_change} - class="mt-1 mr-1 block border-none py-2 pl-3 pr-10 text-base rounded-md right-0 absolute focus:outline-none focus:ring-teal-500 sm:text-sm" + {tabindex} + use:pwKey={_pwKey} + on:change={on_change} + class="mt-1 mr-1 block border-none py-2 pl-3 pr-10 text-base rounded-md right-0 absolute focus:outline-none focus:ring-teal-500 sm:text-sm" > {#each locales as aLocale} <option value={aLocale} selected={aLocale === currentLocale}>{get_locale_name(aLocale)}</option> diff --git a/code/app/src/lib/components/project-status-badge.svelte b/code/app/src/components/project-status-badge.svelte index 5390344..3e93935 100644 --- a/code/app/src/lib/components/project-status-badge.svelte +++ b/code/app/src/components/project-status-badge.svelte @@ -1,6 +1,7 @@ <script lang="ts"> - import type { ProjectStatus } from "$lib/models/projects/ProjectStatus"; + import type {ProjectStatus} from "$models/projects/ProjectStatus"; import Badge from "./badge.svelte"; + export let status: string | ProjectStatus; let text = ""; @@ -21,4 +22,4 @@ } </script> -<Badge {text} {type} uppercase /> +<Badge {text} {type} uppercase/> diff --git a/code/app/src/lib/components/switch.svelte b/code/app/src/components/switch.svelte index 79f2d67..1b67f80 100644 --- a/code/app/src/lib/components/switch.svelte +++ b/code/app/src/components/switch.svelte @@ -35,62 +35,62 @@ {/if} {#if type === "short"} <button - type="button" - class="group relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2" - role="switch" - aria-checked={enabled} - use:pwKey={_pwKey} - on:click={toggle} + type="button" + class="group relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2" + role="switch" + aria-checked={enabled} + use:pwKey={_pwKey} + on:click={toggle} > <span class="sr-only">{srText}</span> - <span aria-hidden="true" class="pointer-events-none absolute h-full w-full rounded-md" /> + <span aria-hidden="true" class="pointer-events-none absolute h-full w-full rounded-md"/> <span - aria-hidden="true" - class="{colorClass} pointer-events-none absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out" + aria-hidden="true" + class="{colorClass} pointer-events-none absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out" /> <span - aria-hidden="true" - class="{translateClass} pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow ring-0 transition-transform duration-200 ease-in-out" + aria-hidden="true" + class="{translateClass} pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow ring-0 transition-transform duration-200 ease-in-out" /> </button> {:else if type === "icon"} <button - type="button" - class="{colorClass} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2" - role="switch" - aria-checked={enabled} - use:pwKey={_pwKey} - on:click={toggle} + type="button" + class="{colorClass} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2" + role="switch" + aria-checked={enabled} + use:pwKey={_pwKey} + on:click={toggle} > <span class="sr-only">{srText}</span> <span - class="{translateClass} pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" + class="{translateClass} pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" > <span - class="{enabled + class="{enabled ? 'opacity-0 ease-out duration-100' : 'opacity-100 ease-in duration-200'} absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" - aria-hidden="true" + aria-hidden="true" > <svg class="h-3 w-3 text-gray-400" fill="none" viewBox="0 0 12 12"> <path - d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" /> </svg> </span> <span - class="{enabled + class="{enabled ? 'opacity-100 ease-in duration-200' : 'opacity-0 ease-out duration-100'} absolute inset-0 flex h-full w-full items-center justify-center transition-opacity" - aria-hidden="true" + aria-hidden="true" > <svg class="h-3 w-3 text-indigo-600" fill="currentColor" viewBox="0 0 12 12"> <path - d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" + d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" /> </svg> </span> @@ -98,17 +98,17 @@ </button> {:else if type === "default"} <button - type="button" - class="{colorClass} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2" - role="switch" - aria-checked={enabled} - use:pwKey={_pwKey} - on:click={toggle} + type="button" + class="{colorClass} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2" + role="switch" + aria-checked={enabled} + use:pwKey={_pwKey} + on:click={toggle} > <span class="sr-only">{srText}</span> <span - aria-hidden="true" - class="{translateClass} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" + aria-hidden="true" + class="{translateClass} pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" /> </button> {/if} diff --git a/code/app/src/lib/components/textarea.svelte b/code/app/src/components/textarea.svelte index a3dd06a..fa77e5c 100644 --- a/code/app/src/lib/components/textarea.svelte +++ b/code/app/src/components/textarea.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { random_string } from "$lib/helpers"; + import {random_string} from "$help"; export let id = "textarea-" + random_string(4); export let disabled = false; @@ -55,16 +55,16 @@ {/if} <div class="mt-1"> <textarea - {rows} - {name} - {id} - {...attributes} - style="overflow-y:hidden;min-height:calc(1.5em + .75rem + 2px);{scrollHeight ? 'height:{scrollHeight}px' : ''};" - bind:value - bind:this={textareaElement} - on:input={on_input} - {placeholder} - class="block w-full rounded-md {colorClass} shadow-sm sm:text-sm" + {rows} + {name} + {id} + {...attributes} + style="overflow-y:hidden;min-height:calc(1.5em + .75rem + 2px);{scrollHeight ? 'height:{scrollHeight}px' : ''};" + bind:value + bind:this={textareaElement} + on:input={on_input} + {placeholder} + class="block w-full rounded-md {colorClass} shadow-sm sm:text-sm" /> {#if errorText || errors?.length === 1} <p class="mt-2 text-sm text-red-600" id={ariaErrorDescribedBy}> diff --git a/code/app/src/lib/configuration.ts b/code/app/src/configuration/index.ts index 8debef6..9b03b66 100644 --- a/code/app/src/lib/configuration.ts +++ b/code/app/src/configuration/index.ts @@ -2,9 +2,9 @@ export const BASE_DOMAIN = "dev.greatoffice.life"; export const DEV_BASE_DOMAIN = "http://localhost"; export const API_ADDRESS = "https://api." + BASE_DOMAIN; export const DEV_API_ADDRESS = "http://localhost:5000"; -export const SECONDS_BETWEEN_SESSION_CHECK = 600; -export function api_base(path: string = "", useDefaultVersion = false): string { +export function api_base(path: string = "", explicitVersion = 1): string { + if (path && !path.startsWith("_")) path = "v" + explicitVersion + path; return (is_development() ? DEV_API_ADDRESS : API_ADDRESS) + (path !== "" ? "/" + path : ""); } @@ -23,23 +23,23 @@ export function is_debug(): boolean { export const CookieNames = { theme: "go_theme", locale: "go_locale", - session: "go_session" + session: "go_session", }; export function get_test_context(): TestContext { return { user: { username: import.meta.env.VITE_TEST_USERNAME, - password: import.meta.env.VITE_TEST_PASSWORD - } - } + password: import.meta.env.VITE_TEST_PASSWORD, + }, + }; } export interface TestContext { user: { username: string, password: string - } + }; } export const QueryKeys = { @@ -56,5 +56,5 @@ export const StorageKeys = { labels: "labels", entries: "entries", stopwatch: "stopwatchState", - logLevel: "logLevel" + logLevel: "logLevel", };
\ No newline at end of file diff --git a/code/app/src/lib/colors.ts b/code/app/src/help/colors.ts index 34c7992..34c7992 100644 --- a/code/app/src/lib/colors.ts +++ b/code/app/src/help/colors.ts diff --git a/code/app/src/lib/helpers.ts b/code/app/src/help/index.ts index 4584de7..a69228e 100644 --- a/code/app/src/lib/helpers.ts +++ b/code/app/src/help/index.ts @@ -1,7 +1,7 @@ -import { browser } from "$app/environment"; -import type { WorkEntry } from "$lib/models/work/WorkEntry"; -import { log_info } from "$lib/logger"; -import { Temporal } from "temporal-polyfill"; +import {browser} from "$app/environment"; +import type {WorkEntry} from "$models/work/WorkEntry"; +import {log_info} from "./logger"; +import {Temporal} from "temporal-polyfill"; export const EMAIL_REGEX = new RegExp(/^([a-z0-9]+(?:([._\-])[a-z0-9]+)*@(?:[a-z0-9]+(?:(-)[a-z0-9]+)?\.)+[a-z0-9](?:[a-z0-9]*[a-z0-9])?)$/i); export const URL_REGEX = new RegExp(/^(http:\/\/www\.|https:\/\/www\.|http:\/\/|https:\/\/)?[a-z0-9]+([\-.][a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/gm); @@ -64,7 +64,7 @@ export function merge_obj_arr<T>(a: Array<T>, b: Array<T>, props: Array<string>) if (a[start] === b[start]) { //pushing the merged objects into array - merge.push({ ...a[start], ...b[start] }); + merge.push({...a[start], ...b[start]}); } //incrementing start value start = start + 1; @@ -88,9 +88,11 @@ export function set_favicon(url: string) { document.head.appendChild(link); } } + export function no_type_check(x: any) { return x; } + export function capitalise(value: string): string { return value.charAt(0).toUpperCase() + value.slice(1); } @@ -126,7 +128,7 @@ export function seconds_to_hour_minute(seconds: number) { const hours = Math.floor(seconds / (60 * 60)); seconds -= hours * (60 * 60); const minutes = Math.floor(seconds / 60); - return { hours, minutes }; + return {hours, minutes}; } export function get_query_string(params: any = {}): string { @@ -231,7 +233,7 @@ export function create_element(name: string, properties?: object, children?: Arr } export function get_element_position(element: HTMLElement | any) { - if (!element) return { x: 0, y: 0 }; + if (!element) return {x: 0, y: 0}; let x = 0; let y = 0; while (true) { @@ -242,7 +244,7 @@ export function get_element_position(element: HTMLElement | any) { } element = element.offsetParent; } - return { x, y }; + return {x, y}; } export function restrict_input_to_numbers(element: HTMLElement, specials: Array<string> = [], mergeSpecialsWithDefaults: boolean = false): void { diff --git a/code/app/src/lib/logger.ts b/code/app/src/help/logger.ts index 831694c..a5b450b 100644 --- a/code/app/src/lib/logger.ts +++ b/code/app/src/help/logger.ts @@ -1,11 +1,11 @@ -import { browser, dev } from "$app/environment"; -import { StorageKeys } from "$lib/configuration"; +import {browser, dev} from "$app/environment"; +import {StorageKeys} from "$configuration"; import pino from "pino"; const pinoConfig = dev ? { transport: { target: "pino-pretty", - } + }, } : {}; const pinoLogger = pino(pinoConfig); diff --git a/code/app/src/lib/persistent-store.ts b/code/app/src/help/persistent-store.ts index 922f3ab..f2c14c9 100644 --- a/code/app/src/lib/persistent-store.ts +++ b/code/app/src/help/persistent-store.ts @@ -1,5 +1,5 @@ -import { writable as _writable, readable as _readable, } from "svelte/store"; -import type { Writable, Readable, StartStopNotifier } from "svelte/store"; +import {writable as _writable, readable as _readable} from "svelte/store"; +import type {Writable, Readable, StartStopNotifier} from "svelte/store"; enum StoreType { SESSION = 0, @@ -11,7 +11,7 @@ interface StoreOptions { } const default_store_options = { - store: StoreType.SESSION + store: StoreType.SESSION, } as StoreOptions; interface WritableStore<T> { @@ -53,7 +53,7 @@ function get_store_value<T>(options: WritableStore<T> | ReadableStore<T>): any { return JSON.parse(value); } catch (e) { console.error(e); - return { __INVALID__: true }; + return {__INVALID__: true}; } } @@ -91,12 +91,12 @@ function readable_persistent<T>(options: ReadableStore<T>): Readable<T> { export { writable_persistent, readable_persistent, - StoreType + StoreType, }; export type { WritableStore, ReadableStore, - StoreOptions + StoreOptions, }; diff --git a/code/app/src/hooks.server.ts b/code/app/src/hooks.server.ts index 59acab6..9a83379 100644 --- a/code/app/src/hooks.server.ts +++ b/code/app/src/hooks.server.ts @@ -1,29 +1,29 @@ -import { CookieNames } from "$lib/configuration"; -import { detectLocale, i18n, isLocale, locales } from '$lib/i18n/i18n-util' -import type { Handle, RequestEvent } from '@sveltejs/kit' -import { initAcceptLanguageHeaderDetector } from 'typesafe-i18n/detectors' -import type { Locales } from "$lib/i18n/i18n-types"; -import { loadAllLocales } from "$lib/i18n/i18n-util.sync"; +import {CookieNames} from "$configuration"; +import {detectLocale, i18n, isLocale, locales} from "$i18n/i18n-util"; +import {log_debug} from "$help/logger"; +import type {Handle, RequestEvent} from "@sveltejs/kit"; +import {initAcceptLanguageHeaderDetector} from "typesafe-i18n/detectors"; +import type {Locales} from "$i18n/i18n-types"; +import {loadAllLocales} from "$i18n/i18n-util.sync"; +loadAllLocales(); +const L = i18n(); -loadAllLocales() -const L = i18n() - -export const handle: Handle = async ({ event, resolve }) => { +export const handle: Handle = async ({event, resolve}) => { const localeCookie = event.cookies.get(CookieNames.locale); const preferredLocale = getPreferredLocale(event); let finalLocale = localeCookie ?? preferredLocale; let forceCookieSet = false; - console.log("Handling locale", { + log_debug("Handling locale", { locales, localeCookie, preferredLocale, - finalLocale + finalLocale, }); if (!isLocale(finalLocale)) { - console.log(finalLocale + " is not a valid locale or it does not exist, switching to default: en"); + log_debug(finalLocale + " is not a valid locale or it does not exist, switching to default: en"); finalLocale = "en"; forceCookieSet = true; } @@ -33,15 +33,15 @@ export const handle: Handle = async ({ event, resolve }) => { event.cookies.set(CookieNames.locale, finalLocale, { sameSite: "strict", path: "/", - httpOnly: false + httpOnly: false, }); } event.locals.locale = finalLocale as Locales; event.locals.LL = L[finalLocale as Locales]; - return resolve(event, { transformPageChunk: ({ html }) => html.replace('%lang%', finalLocale) }); -} + return resolve(event, {transformPageChunk: ({html}) => html.replace("%lang%", finalLocale)}); +}; function getPreferredLocale(event: RequestEvent) { const acceptLanguageDetector = initAcceptLanguageHeaderDetector(event.request); diff --git a/code/app/src/lib/i18n/en/app/index.ts b/code/app/src/i18n/en/app/index.ts index 7ccfc97..7ccfc97 100644 --- a/code/app/src/lib/i18n/en/app/index.ts +++ b/code/app/src/i18n/en/app/index.ts diff --git a/code/app/src/lib/i18n/en/index.ts b/code/app/src/i18n/en/index.ts index fbf5423..fbf5423 100644 --- a/code/app/src/lib/i18n/en/index.ts +++ b/code/app/src/i18n/en/index.ts diff --git a/code/app/src/i18n/formatters.ts b/code/app/src/i18n/formatters.ts new file mode 100644 index 0000000..05d93ae --- /dev/null +++ b/code/app/src/i18n/formatters.ts @@ -0,0 +1,13 @@ +import {capitalise} from "$help"; +import type {FormattersInitializer} from "typesafe-i18n"; +import type {Locales, Formatters} from "./i18n-types"; + +export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + capitalise: (value: string) => capitalise(value), + }; + + return formatters; +}; diff --git a/code/app/src/lib/i18n/i18n-svelte.ts b/code/app/src/i18n/i18n-svelte.ts index 6cdffb3..6cdffb3 100644 --- a/code/app/src/lib/i18n/i18n-svelte.ts +++ b/code/app/src/i18n/i18n-svelte.ts diff --git a/code/app/src/lib/i18n/i18n-types.ts b/code/app/src/i18n/i18n-types.ts index cf968d7..cf968d7 100644 --- a/code/app/src/lib/i18n/i18n-types.ts +++ b/code/app/src/i18n/i18n-types.ts diff --git a/code/app/src/lib/i18n/i18n-util.async.ts b/code/app/src/i18n/i18n-util.async.ts index 2e6717e..2e6717e 100644 --- a/code/app/src/lib/i18n/i18n-util.async.ts +++ b/code/app/src/i18n/i18n-util.async.ts diff --git a/code/app/src/lib/i18n/i18n-util.sync.ts b/code/app/src/i18n/i18n-util.sync.ts index 8144fdc..8144fdc 100644 --- a/code/app/src/lib/i18n/i18n-util.sync.ts +++ b/code/app/src/i18n/i18n-util.sync.ts diff --git a/code/app/src/lib/i18n/i18n-util.ts b/code/app/src/i18n/i18n-util.ts index 12feb33..12feb33 100644 --- a/code/app/src/lib/i18n/i18n-util.ts +++ b/code/app/src/i18n/i18n-util.ts diff --git a/code/app/src/lib/i18n/nb/app/index.ts b/code/app/src/i18n/nb/app/index.ts index 6bf9ba6..6bf9ba6 100644 --- a/code/app/src/lib/i18n/nb/app/index.ts +++ b/code/app/src/i18n/nb/app/index.ts diff --git a/code/app/src/lib/i18n/nb/index.ts b/code/app/src/i18n/nb/index.ts index ef67504..ef67504 100644 --- a/code/app/src/lib/i18n/nb/index.ts +++ b/code/app/src/i18n/nb/index.ts diff --git a/code/app/src/lib/api/projects/index.ts b/code/app/src/lib/api/projects/index.ts deleted file mode 100644 index ea49631..0000000 --- a/code/app/src/lib/api/projects/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { api_base } from "$lib/configuration"; -import { http_post_async } from "../_fetch"; - -export const http_projects = { - create_async(payload: CreateProjectPayload): Promise<Response> { - return http_post_async(api_base("projects/create", true), payload); - } -}; - -export type CreateProjectPayload = { - name: "" -}
\ No newline at end of file diff --git a/code/app/src/lib/i18n/formatters.ts b/code/app/src/lib/i18n/formatters.ts deleted file mode 100644 index 5232b7d..0000000 --- a/code/app/src/lib/i18n/formatters.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { capitalise } from '$lib/helpers' -import type { FormattersInitializer } from 'typesafe-i18n' -import type { Locales, Formatters } from './i18n-types' - -export const initFormatters: FormattersInitializer<Locales, Formatters> = (locale: Locales) => { - - const formatters: Formatters = { - // add your formatter functions here - capitalise: (value: string) => capitalise(value) - } - - return formatters -} diff --git a/code/app/src/lib/models/internal/ISession.ts b/code/app/src/lib/models/internal/ISession.ts deleted file mode 100644 index a452e20..0000000 --- a/code/app/src/lib/models/internal/ISession.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type Session = { - profile: { - username: string, - displayName: string, - id: string, - }, - lastChecked: number, -}
\ No newline at end of file diff --git a/code/app/src/lib/models/work/WorkQuery.ts b/code/app/src/lib/models/work/WorkQuery.ts deleted file mode 100644 index bccc589..0000000 --- a/code/app/src/lib/models/work/WorkQuery.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { HourEntry } from "./WorkEntry"; -import type { IValidationResult } from "../internal/IValidationResult"; -import ValidationResult from "../internal/IValidationResult"; - -export interface IWorkQuery { - results: Array<HourEntry>, - page: number, - pageSize: number, - totalRecords: number, - totalPageCount: number, - is_valid: Function -} - -export class WorkQuery implements IWorkQuery { - results: HourEntry[]; - page: number; - pageSize: number; - totalRecords: number; - totalPageCount: number; - - is_valid(): IValidationResult { - const result = new ValidationResult(); - if (this.page < 0) { - result.add_error("page", { - title: "Page cannot be less than zero", - }) - } - return result; - } -} diff --git a/code/app/src/lib/services/account-service.ts b/code/app/src/lib/services/account-service.ts deleted file mode 100644 index dedf39e..0000000 --- a/code/app/src/lib/services/account-service.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { http_delete_async, http_get_async, http_post_async } from "$lib/api/_fetch"; -import { api_base, CookieNames } from "$lib/configuration"; -import { is_known_problem } from "$lib/models/internal/KnownProblem"; -import type { CreateAccountPayload, CreateAccountResponse, DeleteAccountResponse, IAccountService, LoginPayload, LoginResponse, Session, UpdateAccountPayload, UpdateAccountResponse } from "./abstractions/IAccountService"; - -export class AccountService implements IAccountService { - session: Session; - async login_async(payload: LoginPayload): Promise<LoginResponse> { - const response = await http_post_async(api_base("_/account/login"), payload); - if (response.ok) return { isLoggedIn: true }; - if (is_known_problem(response)) return { - isLoggedIn: false, - knownProblem: await response.json() - }; - return { - isLoggedIn: false - } - } - async logout_async(): Promise<void> { - 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; - } - async create_account_async(payload: CreateAccountPayload): Promise<CreateAccountResponse> { - const response = await http_post_async(api_base("_/account/create"), payload); - if (response.ok) return { isCreated: true }; - if (is_known_problem(response)) return { - isCreated: false, - knownProblem: await response.json() - } - - return { - isCreated: false - } - } - async delete_current_async(): Promise<DeleteAccountResponse> { - const response = await http_delete_async(api_base("_/account/delete")); - return { - isDeleted: response.ok - } - } - async update_current_async(payload: UpdateAccountPayload): Promise<UpdateAccountResponse> { - const response = await http_post_async(api_base("_/account/update"), payload); - if (response.ok) return { isUpdated: true }; - if (is_known_problem(response)) return { - isUpdated: false, - knownProblem: await response.json() - } - - return { - isUpdated: false - } - } -}
\ No newline at end of file diff --git a/code/app/src/lib/session.ts b/code/app/src/lib/session.ts deleted file mode 100644 index 5c29cd6..0000000 --- a/code/app/src/lib/session.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { log_error, log_info } from "$lib/logger"; -import { Temporal } from "temporal-polyfill"; -import { http_account } from "$lib/api/account"; -import { is_guid, session_storage_get_json, session_storage_set_json } from "./helpers"; -import { SECONDS_BETWEEN_SESSION_CHECK, StorageKeys } from "./configuration"; -import type { Session } from "$lib/models/internal/ISession"; - -export async function is_active(forceRefresh: boolean = false): Promise<boolean> { - const nowEpoch = Temporal.Now.instant().epochSeconds; - const data = session_storage_get_json(StorageKeys.session) as Session; - const expiryEpoch = data?.lastChecked + SECONDS_BETWEEN_SESSION_CHECK; - const lastCheckIsStaleOrNone = !is_guid(data?.profile?.id) || (expiryEpoch < nowEpoch); - if (forceRefresh || lastCheckIsStaleOrNone) { - return await call_api(); - } else { - const sessionIsValid = data.profile && is_guid(data.profile.id); - if (!sessionIsValid) { - clear_session_data(); - log_info("Session data is not valid"); - } - return sessionIsValid; - } -} - -export async function end_session(cb: Function): Promise<void> { - await http_account.logout_async(); - clear_session_data(); - if (typeof cb === "function") cb(); -} - -async function call_api(): Promise<boolean> { - log_info("Getting profile data while checking session state"); - try { - const response = await http_account.get_profile_async(true); - if (response.ok) { - const userData = await response.json(); - if (is_guid(userData.id) && userData.username) { - const session = { - profile: userData, - lastChecked: Temporal.Now.instant().epochSeconds - } as Session; - session_storage_set_json(StorageKeys.session, session); - log_info("Successfully got profile data while checking session state"); - return true; - } else { - log_error("Api returned invalid data while getting profile data"); - clear_session_data(); - return false; - } - } else { - log_error("Api returned unsuccessfully while getting profile data"); - clear_session_data(); - return false; - } - } catch (e) { - log_error(e); - clear_session_data(); - return false; - } -} - -export function clear_session_data() { - session_storage_set_json(StorageKeys.session, {}); - log_info("Cleared session data."); -} - -export function get_session_data(): Session { - return session_storage_get_json(StorageKeys.session) as Session; -} diff --git a/code/app/src/lib/models/base/Customer.ts b/code/app/src/models/base/Customer.ts index e44ebb6..ff52fbd 100644 --- a/code/app/src/lib/models/base/Customer.ts +++ b/code/app/src/models/base/Customer.ts @@ -1,5 +1,5 @@ -import type { CustomerContact } from "./CustomerContact" -import type { User } from "./User" +import type {CustomerContact} from "./CustomerContact"; +import type {User} from "./User"; export type Customer = { /** diff --git a/code/app/src/lib/models/base/CustomerContact.ts b/code/app/src/models/base/CustomerContact.ts index e8abea5..e8abea5 100644 --- a/code/app/src/lib/models/base/CustomerContact.ts +++ b/code/app/src/models/base/CustomerContact.ts diff --git a/code/app/src/lib/models/base/CustomerEvent.ts b/code/app/src/models/base/CustomerEvent.ts index af86511..af86511 100644 --- a/code/app/src/lib/models/base/CustomerEvent.ts +++ b/code/app/src/models/base/CustomerEvent.ts diff --git a/code/app/src/lib/models/base/SessionData.ts b/code/app/src/models/base/SessionData.ts index 015cbf3..015cbf3 100644 --- a/code/app/src/lib/models/base/SessionData.ts +++ b/code/app/src/models/base/SessionData.ts diff --git a/code/app/src/lib/models/base/Tenant.ts b/code/app/src/models/base/Tenant.ts index 983122b..6307efc 100644 --- a/code/app/src/lib/models/base/Tenant.ts +++ b/code/app/src/models/base/Tenant.ts @@ -1,4 +1,4 @@ -import type { User } from "./User" +import type {User} from "./User"; export type Tenant = { id: string, diff --git a/code/app/src/lib/models/base/User.ts b/code/app/src/models/base/User.ts index 371c38e..2b74d0e 100644 --- a/code/app/src/lib/models/base/User.ts +++ b/code/app/src/models/base/User.ts @@ -1,4 +1,4 @@ -import type { UserRole } from "./UserRole" +import type {UserRole} from "./UserRole"; export type User = { /** diff --git a/code/app/src/lib/models/base/UserRole.ts b/code/app/src/models/base/UserRole.ts index ec32852..ec32852 100644 --- a/code/app/src/lib/models/base/UserRole.ts +++ b/code/app/src/models/base/UserRole.ts diff --git a/code/app/src/lib/models/internal/FormError.ts b/code/app/src/models/internal/FormError.ts index f6d8978..f6d8978 100644 --- a/code/app/src/lib/models/internal/FormError.ts +++ b/code/app/src/models/internal/FormError.ts diff --git a/code/app/src/lib/models/internal/KnownProblem.ts b/code/app/src/models/internal/KnownProblem.ts index b6923d9..b6923d9 100644 --- a/code/app/src/lib/models/internal/KnownProblem.ts +++ b/code/app/src/models/internal/KnownProblem.ts diff --git a/code/app/src/lib/models/projects/Project.ts b/code/app/src/models/projects/Project.ts index f265e67..f265e67 100644 --- a/code/app/src/lib/models/projects/Project.ts +++ b/code/app/src/models/projects/Project.ts diff --git a/code/app/src/lib/models/projects/ProjectLabel.ts b/code/app/src/models/projects/ProjectLabel.ts index 59aa9d5..59aa9d5 100644 --- a/code/app/src/lib/models/projects/ProjectLabel.ts +++ b/code/app/src/models/projects/ProjectLabel.ts diff --git a/code/app/src/lib/models/projects/ProjectMember.ts b/code/app/src/models/projects/ProjectMember.ts index de348ef..de348ef 100644 --- a/code/app/src/lib/models/projects/ProjectMember.ts +++ b/code/app/src/models/projects/ProjectMember.ts diff --git a/code/app/src/lib/models/projects/ProjectMeta.ts b/code/app/src/models/projects/ProjectMeta.ts index c583b47..c583b47 100644 --- a/code/app/src/lib/models/projects/ProjectMeta.ts +++ b/code/app/src/models/projects/ProjectMeta.ts diff --git a/code/app/src/lib/models/projects/ProjectRole.ts b/code/app/src/models/projects/ProjectRole.ts index 0fa2347..0fa2347 100644 --- a/code/app/src/lib/models/projects/ProjectRole.ts +++ b/code/app/src/models/projects/ProjectRole.ts diff --git a/code/app/src/lib/models/projects/ProjectStatus.ts b/code/app/src/models/projects/ProjectStatus.ts index 2df4b88..2df4b88 100644 --- a/code/app/src/lib/models/projects/ProjectStatus.ts +++ b/code/app/src/models/projects/ProjectStatus.ts diff --git a/code/app/src/lib/models/work/WorkCategory.ts b/code/app/src/models/work/WorkCategory.ts index 7dd85d5..7dd85d5 100644 --- a/code/app/src/lib/models/work/WorkCategory.ts +++ b/code/app/src/models/work/WorkCategory.ts diff --git a/code/app/src/lib/models/work/WorkEntry.ts b/code/app/src/models/work/WorkEntry.ts index 2108b88..2108b88 100644 --- a/code/app/src/lib/models/work/WorkEntry.ts +++ b/code/app/src/models/work/WorkEntry.ts diff --git a/code/app/src/lib/models/work/WorkEntryQueryResponse.ts b/code/app/src/models/work/WorkEntryQueryResponse.ts index a6974f1..a6974f1 100644 --- a/code/app/src/lib/models/work/WorkEntryQueryResponse.ts +++ b/code/app/src/models/work/WorkEntryQueryResponse.ts diff --git a/code/app/src/lib/models/work/WorkLabel.ts b/code/app/src/models/work/WorkLabel.ts index f7e2795..f7e2795 100644 --- a/code/app/src/lib/models/work/WorkLabel.ts +++ b/code/app/src/models/work/WorkLabel.ts diff --git a/code/app/src/models/work/WorkQuery.ts b/code/app/src/models/work/WorkQuery.ts new file mode 100644 index 0000000..93b0aa4 --- /dev/null +++ b/code/app/src/models/work/WorkQuery.ts @@ -0,0 +1,17 @@ +import type {WorkEntry} from "./WorkEntry"; + +export interface IWorkQuery { + results: Array<WorkEntry>, + page: number, + pageSize: number, + totalRecords: number, + totalPageCount: number, +} + +export class WorkQuery implements IWorkQuery { + results: WorkEntry[]; + page: number; + pageSize: number; + totalRecords: number; + totalPageCount: number; +} diff --git a/code/app/src/routes/(main)/(app)/+layout.svelte b/code/app/src/routes/(main)/(app)/+layout.svelte index a280fa7..6cb70ef 100644 --- a/code/app/src/routes/(main)/(app)/+layout.svelte +++ b/code/app/src/routes/(main)/(app)/+layout.svelte @@ -1,151 +1,162 @@ <script lang="ts"> - import { - ChevronUpDownIcon, - MagnifyingGlassIcon, - Bars3CenterLeftIcon, - XMarkIcon, - HomeIcon, - MegaphoneIcon, - FolderOpenIcon, - QueueListIcon, - CalendarIcon, - } from "$lib/components/icons"; - import { Dialog, Menu, MenuButton, MenuItem, MenuItems, Transition, TransitionChild, TransitionRoot } from "@rgossiaux/svelte-headlessui"; - import { DialogPanel } from "@developermuch/dev-svelte-headlessui"; - import { Input } from "$lib/components"; - import { end_session } from "$lib/session"; - import { goto } from "$app/navigation"; - import { page } from "$app/stores"; + import { + ChevronUpDownIcon, + MagnifyingGlassIcon, + Bars3CenterLeftIcon, + XMarkIcon, + HomeIcon, + MegaphoneIcon, + FolderOpenIcon, + QueueListIcon, + CalendarIcon, + } from "$components/icons"; + import {AccountService} from "$services/account-service"; + import { + Dialog, + Menu, + MenuButton, + MenuItem, + MenuItems, + Transition, + 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"; + + const accountService = new AccountService(); - const session = { - profile: { - username: "Brukernavn", - displayName: "epost@adresse.no", - }, - }; + const session = { + profile: { + username: "Brukernavn", + displayName: "epost@adresse.no", + }, + }; - let sidebarOpen = false; - let sidebarSearchValue: string | undefined; + let sidebarOpen = false; + let sidebarSearchValue: string | undefined; - function sign_out() { - end_session(() => goto("/sign-in")); - } + function sign_out() { + accountService.end_session(() => goto("/sign-in")); + } - const navigationItems = [ - { - href: "/home", - name: "Home", - icon: HomeIcon, - }, - { - href: "/projects", - name: "Projects", - icon: CalendarIcon, - }, - { - href: "/tickets", - name: "Tickets", - icon: MegaphoneIcon, - }, - { - href: "/todo", - name: "Todo", - icon: QueueListIcon, - }, - { - href: "/wiki", - name: "Wiki", - icon: FolderOpenIcon, - }, - ]; + const navigationItems = [ + { + href: "/home", + name: "Home", + icon: HomeIcon, + }, + { + href: "/projects", + name: "Projects", + icon: CalendarIcon, + }, + { + href: "/tickets", + name: "Tickets", + icon: MegaphoneIcon, + }, + { + href: "/todo", + name: "Todo", + icon: QueueListIcon, + }, + { + href: "/wiki", + name: "Wiki", + icon: FolderOpenIcon, + }, + ]; </script> <div class="min-h-full"> - <!-- Mobile sidebar --> - <TransitionRoot show={sidebarOpen}> - <Dialog as="div" class="relative z-40 lg:hidden" on:close={() => (sidebarOpen = false)}> - <TransitionChild - as="div" - enter="transition-opacity ease-linear duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="transition-opacity ease-linear duration-300" - leaveFrom="opacity-100" - leaveTo="opacity-0" - > - <div class="fixed inset-0 bg-gray-600 bg-opacity-75" /> - </TransitionChild> - - <div class="fixed inset-0 z-40 flex"> - <TransitionChild - as="div" - enter="transition ease-in-out duration-300 transform" - enterFrom="-translate-x-full" - enterTo="translate-x-0" - leave="transition ease-in-out duration-300 transform" - leaveFrom="translate-x-0" - leaveTo="-translate-x-full" - > - <DialogPanel class="relative flex w-full max-w-xs flex-1 flex-col bg-white pt-5 pb-4"> + <!-- Mobile sidebar --> + <TransitionRoot show={sidebarOpen}> + <Dialog as="div" class="relative z-40 lg:hidden" on:close={() => (sidebarOpen = false)}> <TransitionChild - as="div" - enter="ease-in-out duration-300" - enterFrom="opacity-0" - enterTo="opacity-100" - leave="ease-in-out duration-300" - leaveFrom="opacity-100" - leaveTo="opacity-0" + as="div" + enter="transition-opacity ease-linear duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="transition-opacity ease-linear duration-300" + leaveFrom="opacity-100" + leaveTo="opacity-0" > - <div class="absolute top-0 right-0 -mr-12 pt-2"> - <button - type="button" - class="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" - on:click={() => (sidebarOpen = false)} - > - <span class="sr-only">Close sidebar</span> - <XMarkIcon class="text-white" aria-hidden="true" /> - </button> - </div> + <div class="fixed inset-0 bg-gray-600 bg-opacity-75"/> </TransitionChild> - <div class="mt-5 h-0 flex-1 overflow-y-auto"> - <nav class="px-2"> - <div class="space-y-1"> - {#each navigationItems as item} - {@const current = $page.url.pathname.startsWith(item.href)} - <a - href={item.href} - aria-current={current ? "page" : undefined} - class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md + + <div class="fixed inset-0 z-40 flex"> + <TransitionChild + as="div" + enter="transition ease-in-out duration-300 transform" + enterFrom="-translate-x-full" + enterTo="translate-x-0" + leave="transition ease-in-out duration-300 transform" + leaveFrom="translate-x-0" + leaveTo="-translate-x-full" + > + <DialogPanel class="relative flex w-full max-w-xs flex-1 flex-col bg-white pt-5 pb-4"> + <TransitionChild + as="div" + enter="ease-in-out duration-300" + enterFrom="opacity-0" + enterTo="opacity-100" + leave="ease-in-out duration-300" + leaveFrom="opacity-100" + leaveTo="opacity-0" + > + <div class="absolute top-0 right-0 -mr-12 pt-2"> + <button + type="button" + class="ml-1 flex h-10 w-10 items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white" + on:click={() => (sidebarOpen = false)} + > + <span class="sr-only">Close sidebar</span> + <XMarkIcon class="text-white" aria-hidden="true"/> + </button> + </div> + </TransitionChild> + <div class="mt-5 h-0 flex-1 overflow-y-auto"> + <nav class="px-2"> + <div class="space-y-1"> + {#each navigationItems as item} + {@const current = $page.url.pathname.startsWith(item.href)} + <a + href={item.href} + aria-current={current ? "page" : undefined} + class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md {current ? 'bg-gray-100 text-gray-900' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'}" - > - <svelte:component - this={item.icon} - class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" - aria-hidden="true" - /> - {item.name} - </a> - {/each} + > + <svelte:component + this={item.icon} + class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" + aria-hidden="true" + /> + {item.name} + </a> + {/each} + </div> + </nav> + </div> + </DialogPanel> + </TransitionChild> + <div class="w-14 flex-shrink-0" aria-hidden="true"> + <!-- Dummy element to force sidebar to shrink to fit close icon --> </div> - </nav> </div> - </DialogPanel> - </TransitionChild> - <div class="w-14 flex-shrink-0" aria-hidden="true"> - <!-- Dummy element to force sidebar to shrink to fit close icon --> - </div> - </div> - </Dialog> - </TransitionRoot> + </Dialog> + </TransitionRoot> - <!-- Static sidebar for desktop --> - <div class="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col lg:border-r lg:border-gray-200 lg:bg-gray-100 lg:pb-4"> - <div class="flex h-0 flex-1 p-3 flex-col overflow-y-auto"> - <!-- User account dropdown --> - <Menu class="relative inline-block text-left"> - <MenuButton - class="group w-full rounded-md bg-gray-100 px-3.5 py-2 text-left text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-gray-100" - > + <!-- Static sidebar for desktop --> + <div class="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col lg:border-r lg:border-gray-200 lg:bg-gray-100 lg:pb-4"> + <div class="flex h-0 flex-1 p-3 flex-col overflow-y-auto"> + <!-- User account dropdown --> + <Menu class="relative inline-block text-left"> + <MenuButton + class="group w-full rounded-md bg-gray-100 px-3.5 py-2 text-left text-sm font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2 focus:ring-offset-gray-100" + > <span class="flex w-full items-center justify-between"> <span class="flex min-w-0 items-center justify-between space-x-3"> <span class="flex min-w-0 flex-1 flex-col"> @@ -155,142 +166,151 @@ <span class="truncate text-sm text-gray-500">{session.profile.displayName}</span> </span> </span> - <ChevronUpDownIcon class="flex-shrink-0 text-gray-400 group-hover:text-gray-500" aria-hidden="true" /> + <ChevronUpDownIcon class="flex-shrink-0 text-gray-400 group-hover:text-gray-500" aria-hidden="true"/> </span> - </MenuButton> - <Transition - leave="transition ease-in duration-75" - enter="transition ease-out duration-100" - enterFrom="transform opacity-0 scale-95" - enterTo="transform opacity-100 scale-100" - leaveFrom="transform opacity-100 scale-100" - leaveTo="transform opacity-0 scale-95" - as="div" - > - <MenuItems - class="absolute right-0 left-0 z-10 mt-1 origin-top divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" - > - <div class="py-1"> - <MenuItem> - <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> View profile </a> - </MenuItem> - <MenuItem> - <a href="/settings" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> Settings </a> - </MenuItem> - </div> - <div class="py-1"> - <MenuItem> + </MenuButton> + <Transition + leave="transition ease-in duration-75" + enter="transition ease-out duration-100" + enterFrom="transform opacity-0 scale-95" + enterTo="transform opacity-100 scale-100" + leaveFrom="transform opacity-100 scale-100" + leaveTo="transform opacity-0 scale-95" + as="div" + > + <MenuItems + class="absolute right-0 left-0 z-10 mt-1 origin-top divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" + > + <div class="py-1"> + <MenuItem> + <a href="/profile" + class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> + View profile </a> + </MenuItem> + <MenuItem> + <a href="/settings" + class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> + Settings </a> + </MenuItem> + </div> + <div class="py-1"> + <MenuItem> <span - on:click={() => sign_out()} - class="text-gray-700 block px-4 py-2 text-sm hover:bg-red-200 hover:text-red-900 cursor-pointer" + on:click={() => 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 </span> - </MenuItem> + </MenuItem> + </div> + </MenuItems> + </Transition> + </Menu> + <!-- Sidebar Search --> + <div class="mt-3 hidden"> + <label for="search" class="sr-only">Search</label> + <div class="relative mt-1 rounded-md shadow-sm"> + <Input type="search" name="search" icon={MagnifyingGlassIcon} placeholder="Search" + bind:value={sidebarSearchValue}/> + </div> </div> - </MenuItems> - </Transition> - </Menu> - <!-- Sidebar Search --> - <div class="mt-3 hidden"> - <label for="search" class="sr-only">Search</label> - <div class="relative mt-1 rounded-md shadow-sm"> - <Input type="search" name="search" icon={MagnifyingGlassIcon} placeholder="Search" bind:value={sidebarSearchValue} /> - </div> - </div> - <!-- Navigation --> - <nav class="mt-5"> - <div class="space-y-1"> - {#each navigationItems as item} - {@const current = $page.url.pathname.startsWith(item.href)} - <a - href={item.href} - aria-current={current ? "page" : undefined} - class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md + <!-- Navigation --> + <nav class="mt-5"> + <div class="space-y-1"> + {#each navigationItems as item} + {@const current = $page.url.pathname.startsWith(item.href)} + <a + href={item.href} + aria-current={current ? "page" : undefined} + class="group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md {current ? 'bg-gray-200 text-gray-900' : 'text-gray-700 hover:text-gray-900 hover:bg-gray-50'}" - > - <svelte:component - this={item.icon} - class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" - aria-hidden="true" - /> - {item.name} - </a> - {/each} + > + <svelte:component + this={item.icon} + class="mr-3 flex-shrink-0 h-6 w-6 {current ? 'text-gray-500' : 'text-gray-400 group-hover:text-gray-500'}" + aria-hidden="true" + /> + {item.name} + </a> + {/each} + </div> + </nav> </div> - </nav> </div> - </div> - <!-- Main column --> - <div class="flex flex-col lg:pl-64"> - <!-- Search header --> - <div class="sticky top-0 z-10 flex h-16 flex-shrink-0 border-b border-gray-200 bg-white lg:hidden"> - <button - type="button" - class="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 lg:hidden" - on:click={() => (sidebarOpen = true)} - > - <span class="sr-only">Open sidebar</span> - <Bars3CenterLeftIcon aria-hidden="true" /> - </button> - <div class="flex flex-1 justify-between px-4 sm:px-6 lg:px-8"> - <div class="flex flex-1"> - <form class="flex w-full md:ml-0" action="#" method="GET"> - <label for="search-field" class="sr-only">Search</label> - <div class="relative w-full text-gray-400 focus-within:text-gray-600"> - <Input - bind:value={sidebarSearchValue} - icon={MagnifyingGlassIcon} - id="search-field" - name="search-field" - placeholder="Search" - type="search" - /> - </div> - </form> - </div> - <div class="flex items-center"> - <!-- Profile dropdown --> - <Menu as="div" class="relative ml-3"> - <div> - <MenuButton - class="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2" - > - <span class="sr-only">Open user menu</span> - </MenuButton> - </div> - <Transition - enterFrom="transform opacity-0 scale-95" - enterTo="transform opacity-100 scale-100" - leaveFrom="transform opacity-100 scale-100" - leaveTo="transform opacity-0 scale-95" - as="div" + <!-- Main column --> + <div class="flex flex-col lg:pl-64"> + <!-- Search header --> + <div class="sticky top-0 z-10 flex h-16 flex-shrink-0 border-b border-gray-200 bg-white lg:hidden"> + <button + type="button" + class="border-r border-gray-200 px-4 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-teal-500 lg:hidden" + on:click={() => (sidebarOpen = true)} > - <MenuItems - class="absolute right-0 z-10 mt-2 w-48 origin-top-right divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" - > - <div class="py-1"> - <MenuItem> - <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm"> View profile </a> - </MenuItem> - <MenuItem> - <a href="/settings" class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> Settings </a> - </MenuItem> - <div class="py-1"> - <MenuItem> - <span on:click={() => sign_out()} class="text-gray-700 block px-4 py-2 text-sm"> Sign out </span> - </MenuItem> - </div> + <span class="sr-only">Open sidebar</span> + <Bars3CenterLeftIcon aria-hidden="true"/> + </button> + <div class="flex flex-1 justify-between px-4 sm:px-6 lg:px-8"> + <div class="flex flex-1"> + <form class="flex w-full md:ml-0" action="#" method="GET"> + <label for="search-field" class="sr-only">Search</label> + <div class="relative w-full text-gray-400 focus-within:text-gray-600"> + <Input + bind:value={sidebarSearchValue} + icon={MagnifyingGlassIcon} + id="search-field" + name="search-field" + placeholder="Search" + type="search" + /> + </div> + </form> </div> - </MenuItems> - </Transition> - </Menu> + <div class="flex items-center"> + <!-- Profile dropdown --> + <Menu as="div" class="relative ml-3"> + <div> + <MenuButton + class="flex max-w-xs items-center rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-teal-500 focus:ring-offset-2" + > + <span class="sr-only">Open user menu</span> + </MenuButton> + </div> + <Transition + enterFrom="transform opacity-0 scale-95" + enterTo="transform opacity-100 scale-100" + leaveFrom="transform opacity-100 scale-100" + leaveTo="transform opacity-0 scale-95" + as="div" + > + <MenuItems + class="absolute right-0 z-10 mt-2 w-48 origin-top-right divide-y divide-gray-200 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" + > + <div class="py-1"> + <MenuItem> + <a href="/profile" class="text-gray-700 block px-4 py-2 text-sm"> View + profile </a> + </MenuItem> + <MenuItem> + <a href="/settings" + class="text-gray-700 block px-4 py-2 text-sm hover:text-gray-900 hover:bg-gray-100"> + Settings </a> + </MenuItem> + <div class="py-1"> + <MenuItem> + <span on:click={() => sign_out()} + class="text-gray-700 block px-4 py-2 text-sm"> Sign out </span> + </MenuItem> + </div> + </div> + </MenuItems> + </Transition> + </Menu> + </div> + </div> </div> - </div> + <main class="flex-1 p-3"> + <slot/> + </main> </div> - <main class="flex-1 p-3"> - <slot /> - </main> - </div> </div> diff --git a/code/app/src/routes/(main)/(app)/projects/+page.svelte b/code/app/src/routes/(main)/(app)/projects/+page.svelte index e39a886..1508118 100644 --- a/code/app/src/routes/(main)/(app)/projects/+page.svelte +++ b/code/app/src/routes/(main)/(app)/projects/+page.svelte @@ -1,137 +1,143 @@ <script lang="ts"> - import { Button, ProjectStatusBadge, Input } from "$lib/components"; - import type { Project } from "$lib/models/projects/Project"; - import { onMount } from "svelte"; - import { faker } from "@faker-js/faker"; - import { Temporal } from "temporal-polyfill"; - import { createTable, Subscribe, Render } from "svelte-headless-table"; - import { addSortBy, addTableFilter } from "svelte-headless-table/plugins"; - import { ProjectStatus } from "$lib/models/projects/ProjectStatus"; - import { writable, type Writable } from "svelte/store"; - import { ChevronDownIcon, ChevronUpIcon, ChevronUpDownIcon, MagnifyingGlassIcon, FunnelIcon } from "$lib/components/icons"; - import LL from "$lib/i18n/i18n-svelte"; - import { goto } from "$app/navigation"; + import {Button, ProjectStatusBadge, Input} from "$components"; + import type {Project} from "$models/projects/Project"; + import {onMount} from "svelte"; + import {faker} from "@faker-js/faker"; + import {Temporal} from "temporal-polyfill"; + import {createTable, Subscribe, Render} from "svelte-headless-table"; + import {addSortBy, addTableFilter} from "svelte-headless-table/plugins"; + import {ProjectStatus} from "$models/projects/ProjectStatus"; + import {writable, type Writable} from "svelte/store"; + import { + ChevronDownIcon, + ChevronUpIcon, + ChevronUpDownIcon, + MagnifyingGlassIcon, + FunnelIcon, + } from "$components/icons"; + import LL from "$i18n/i18n-svelte"; + import {goto} from "$app/navigation"; - let projects: Writable<Array<Project>> = writable([]); + let projects: Writable<Array<Project>> = writable([]); - onMount(() => { - let i = 0; - const tempProjects = []; - while (i < 101) { - tempProjects.push({ - id: crypto.randomUUID(), - name: faker.lorem.word(), - start: Temporal.Now.plainDateISO().toLocaleString(), - description: faker.lorem.words(3), - members: [], - status: ProjectStatus.IDLE, - }); - i++; - } - projects.set(tempProjects); - }); + onMount(() => { + let i = 0; + const tempProjects = []; + while (i < 101) { + tempProjects.push({ + id: crypto.randomUUID(), + name: faker.lorem.word(), + start: Temporal.Now.plainDateISO().toLocaleString(), + description: faker.lorem.words(3), + members: [], + status: ProjectStatus.IDLE, + }); + i++; + } + projects.set(tempProjects); + }); - function on_open_project(event) { - if (event.code && (event.code !== "Enter" || event.code !== "Space")) return; - const name = event.target.innerText; - const projectId = $projects.find((p) => p.name === name).id; - goto("/projects/" + projectId); - } + function on_open_project(event) { + if (event.code && (event.code !== "Enter" || event.code !== "Space")) return; + const name = event.target.innerText; + const projectId = $projects.find((p) => p.name === name).id; + goto("/projects/" + projectId); + } - const table = createTable(projects, { - sort: addSortBy(), - filter: addTableFilter(), - }); + const table = createTable(projects, { + sort: addSortBy(), + filter: addTableFilter(), + }); - const columns = table.createColumns([ - table.column({ header: $LL.name(), accessor: "name" }), - table.column({ header: "Status", accessor: "status" }), - table.column({ header: "Start", accessor: "start" }), - table.column({ header: "Description", accessor: "description", plugins: { sort: { disable: true } } }), - ]); + const columns = table.createColumns([ + table.column({header: $LL.name(), accessor: "name"}), + table.column({header: "Status", accessor: "status"}), + table.column({header: "Start", accessor: "start"}), + table.column({header: "Description", accessor: "description", plugins: {sort: {disable: true}}}), + ]); - const { headerRows, rows, tableAttrs, tableBodyAttrs, pluginStates } = table.createViewModel(columns); - const { filterValue } = pluginStates.filter; + const {headerRows, rows, tableAttrs, tableBodyAttrs, pluginStates} = table.createViewModel(columns); + const {filterValue} = pluginStates.filter; </script> <div class="sm:flex sm:items-center"> - <div class="sm:flex-auto"> - <h1 class="text-xl font-semibold text-gray-900">Projects</h1> - <p class="mt-2 text-sm text-gray-700">A list of all the projects in your organsation.</p> - </div> - <div class="mt-4 sm:mt-0 sm:ml-16 inline-flex gap-1 sm:flex-none"> - <Input icon={MagnifyingGlassIcon} placeholder="Search" bind:value={$filterValue} /> - <Button text="Create project" href="/projects/create" /> - </div> + <div class="sm:flex-auto"> + <h1 class="text-xl font-semibold text-gray-900">Projects</h1> + <p class="mt-2 text-sm text-gray-700">A list of all the projects in your organsation.</p> + </div> + <div class="mt-4 sm:mt-0 sm:ml-16 inline-flex gap-1 sm:flex-none"> + <Input icon={MagnifyingGlassIcon} placeholder="Search" bind:value={$filterValue}/> + <Button text="Create project" href="/projects/create"/> + </div> </div> <div class="-mx-2 mt-6 rounded-md shadow overflow-auto max-h-[80vh] sm:-mx-6 md:mx-0"> - <table {...$tableAttrs} class="min-w-full divide-y divide-gray-300"> - <thead class="bg-gray-50"> - {#each $headerRows as headerRow (headerRow.id)} - <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs> - <tr {...rowAttrs} class="shadow-sm"> - {#each headerRow.cells as cell (cell.id)} - <Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props> - <th - {...attrs} - scope="col" - class="sticky top-0 bg-gray-50 bg-opacity-100 whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900" - > - <div class="group inline-flex"> - <Render of={cell.render()} /> - <span - on:click={props.sort.toggle} - on:keypress={props.sort.toggle} - class="{props.sort.disabled + <table {...$tableAttrs} class="min-w-full divide-y divide-gray-300"> + <thead class="bg-gray-50"> + {#each $headerRows as headerRow (headerRow.id)} + <Subscribe rowAttrs={headerRow.attrs()} let:rowAttrs> + <tr {...rowAttrs} class="shadow-sm"> + {#each headerRow.cells as cell (cell.id)} + <Subscribe attrs={cell.attrs()} let:attrs props={cell.props()} let:props> + <th + {...attrs} + scope="col" + class="sticky top-0 bg-gray-50 bg-opacity-100 whitespace-nowrap px-2 py-3.5 text-left text-sm font-semibold text-gray-900" + > + <div class="group inline-flex"> + <Render of={cell.render()}/> + <span + on:click={props.sort.toggle} + on:keypress={props.sort.toggle} + class="{props.sort.disabled ? 'bg-gray-200 text-gray-900 group-hover:bg-gray-300' : 'invisible text-gray-400 group-hover:visible group-focus:visible'} {props.sort.disabled ? '' : 'cursor-pointer'} ml-2 flex-none rounded" - > + > {#if props.sort.order === "asc"} - <ChevronUpIcon /> + <ChevronUpIcon/> {:else if props.sort.order === "desc"} - <ChevronDownIcon /> + <ChevronDownIcon/> {:else if !props.sort.disabled} - <ChevronUpDownIcon /> + <ChevronUpDownIcon/> {/if} </span> - {#if cell.id === "status"} + {#if cell.id === "status"} <span class="invisible text-gray-400 cursor-pointer group-hover:visible group-focus:visible ml-2 flex-none rounded"> - <FunnelIcon /> + <FunnelIcon/> </span> - {/if} - </div> - </th> - </Subscribe> - {/each} - </tr> - </Subscribe> - {/each} - </thead> - <tbody {...$tableBodyAttrs} class="divide-y divide-gray-200 bg-white"> - {#each $rows as row (row.id)} - <Subscribe rowAttrs={row.attrs()} let:rowAttrs> - <tr {...rowAttrs}> - {#each row.cells as cell (cell.id)} - {@const materialisedCell = cell.render()} - <Subscribe attrs={cell.attrs()} let:attrs> - <td {...attrs} class="whitespace-nowrap px-2 py-2 text-sm"> - {#if cell.id === "name"} + {/if} + </div> + </th> + </Subscribe> + {/each} + </tr> + </Subscribe> + {/each} + </thead> + <tbody {...$tableBodyAttrs} class="divide-y divide-gray-200 bg-white"> + {#each $rows as row (row.id)} + <Subscribe rowAttrs={row.attrs()} let:rowAttrs> + <tr {...rowAttrs}> + {#each row.cells as cell (cell.id)} + {@const materialisedCell = cell.render()} + <Subscribe attrs={cell.attrs()} let:attrs> + <td {...attrs} class="whitespace-nowrap px-2 py-2 text-sm"> + {#if cell.id === "name"} <span class="link" title="Open project" on:click={on_open_project} on:keypress={on_open_project}> - <Render of={materialisedCell} /> + <Render of={materialisedCell}/> </span> - {:else if cell.id === "status"} - <ProjectStatusBadge status={materialisedCell.toString()} /> - {:else} - <Render of={materialisedCell} /> - {/if} - </td> - </Subscribe> - {/each} - </tr> - </Subscribe> - {/each} - </tbody> - </table> + {:else if cell.id === "status"} + <ProjectStatusBadge status={materialisedCell.toString()}/> + {:else} + <Render of={materialisedCell}/> + {/if} + </td> + </Subscribe> + {/each} + </tr> + </Subscribe> + {/each} + </tbody> + </table> </div> diff --git a/code/app/src/routes/(main)/(app)/projects/create/+page.svelte b/code/app/src/routes/(main)/(app)/projects/create/+page.svelte index 1741506..ab38b2e 100644 --- a/code/app/src/routes/(main)/(app)/projects/create/+page.svelte +++ b/code/app/src/routes/(main)/(app)/projects/create/+page.svelte @@ -1,8 +1,8 @@ <script lang="ts"> - import { useSWR } from "sswr"; - import { Input, TextArea, Combobox, Button } from "$lib/components"; - import type { ProjectMember } from "$lib/models/projects/ProjectMember"; - import LL from "$lib/i18n/i18n-svelte"; + import {useSWR} from "sswr"; + import {Input, TextArea, Combobox, Button} from "$components"; + import type {ProjectMember} from "$models/projects/ProjectMember"; + import LL from "$i18n/i18n-svelte"; const formData = { name: { @@ -36,16 +36,16 @@ alert("Submitted"); } - const { data: members } = useSWR("projectMembers"); + const {data: members} = useSWR("projectMembers"); </script> <h1>Create a new project</h1> <form on:submit|preventDefault={submit_form_async} class="max-w-md flex flex-col gap-2"> - <Input label="Name" bind:value={formData.name.value} errors={formData.name.errors} required /> - <TextArea label="Description" bind:value={formData.description.value} errors={formData.description.errors} /> + <Input label="Name" bind:value={formData.name.value} errors={formData.name.errors} required/> + <TextArea label="Description" bind:value={formData.description.value} errors={formData.description.errors}/> <section class="grid grid-flow-row sm:grid-flow-col gap-2"> - <Input type="date" label="Start" bind:value={formData.start.value} errors={formData.start.errors} /> - <Input type="date" label="Stop" bind:value={formData.stop.value} errors={formData.stop.errors} /> + <Input type="date" label="Start" bind:value={formData.start.value} errors={formData.start.errors}/> + <Input type="date" label="Stop" bind:value={formData.stop.value} errors={formData.stop.errors}/> </section> <Combobox options={$members} label={$LL.app.members()}> <svelte:fragment slot="no-records"> @@ -57,5 +57,5 @@ {/if} </svelte:fragment> </Combobox> - <Button text={$LL.submit()} /> + <Button text={$LL.submit()}/> </form> diff --git a/code/app/src/routes/(main)/(app)/settings/+page.svelte b/code/app/src/routes/(main)/(app)/settings/+page.svelte index 1f0cc67..1c9a910 100644 --- a/code/app/src/routes/(main)/(app)/settings/+page.svelte +++ b/code/app/src/routes/(main)/(app)/settings/+page.svelte @@ -1,5 +1,5 @@ <script lang="ts"> - import { Input, Button, Switch } from "$lib/components"; + import {Input, Button, Switch} from "$components"; </script> <div class="relative mx-auto max-w-4xl md:px-8 xl:px-0"> @@ -13,9 +13,9 @@ <div class="lg:hidden"> <label for="selected-tab" class="sr-only">Select a tab</label> <select - id="selected-tab" - name="selected-tab" - class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-purple-500 focus:outline-none focus:ring-purple-500 sm:text-sm" + id="selected-tab" + name="selected-tab" + class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-purple-500 focus:outline-none focus:ring-purple-500 sm:text-sm" > <option selected>General</option> @@ -34,38 +34,39 @@ <div class="border-b border-gray-200"> <nav class="-mb-px flex space-x-8"> <!-- Current: "border-purple-500 text-purple-600", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" --> - <a href="#" class="border-purple-500 text-purple-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >General</a + <a href="#" + class="border-purple-500 text-purple-600 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >General</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Password</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Password</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Notifications</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Notifications</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Plan</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Plan</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Billing</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Billing</a > <a - href="#" - class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" - >Team Members</a + href="#" + class="border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm" + >Team Members</a > </nav> </div> @@ -87,9 +88,9 @@ <span class="flex-grow">Chelsea Hagon</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -99,22 +100,22 @@ <dd class="mt-1 flex text-sm text-gray-900 sm:col-span-2 sm:mt-0"> <span class="flex-grow"> <img - class="h-8 w-8 rounded-full" - src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" - alt="" + class="h-8 w-8 rounded-full" + src="https://images.unsplash.com/photo-1550525811-e5869dd03032?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" + alt="" /> </span> <span class="ml-4 flex flex-shrink-0 items-start space-x-4"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > <span class="text-gray-300" aria-hidden="true">|</span> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Remove</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Remove</button > </span> </dd> @@ -125,9 +126,9 @@ <span class="flex-grow">chelsea.hagon@example.com</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -138,9 +139,9 @@ <span class="flex-grow">Human Resources Manager</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -152,7 +153,8 @@ <div class="mt-10 divide-y divide-gray-200"> <div class="space-y-1"> <h3 class="text-lg font-medium leading-6 text-gray-900">Account</h3> - <p class="max-w-2xl text-sm text-gray-500">Manage how information is displayed on your account.</p> + <p class="max-w-2xl text-sm text-gray-500">Manage how information is displayed on your + account.</p> </div> <div class="mt-6"> <dl class="divide-y divide-gray-200"> @@ -162,9 +164,9 @@ <span class="flex-grow">English</span> <span class="ml-4 flex-shrink-0"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > </span> </dd> @@ -175,22 +177,24 @@ <span class="flex-grow">DD-MM-YYYY</span> <span class="ml-4 flex flex-shrink-0 items-start space-x-4"> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Update</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Update</button > <span class="text-gray-300" aria-hidden="true">|</span> <button - type="button" - class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" - >Remove</button + type="button" + class="rounded-md bg-white font-medium text-purple-600 hover:text-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2" + >Remove</button > </span> </dd> </div> <div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5 sm:pt-5"> - <dt class="text-sm font-medium text-gray-500" id="timezone-option-label">Automatic timezone</dt> - <Switch /> + <dt class="text-sm font-medium text-gray-500" id="timezone-option-label">Automatic + timezone + </dt> + <Switch/> </div> </dl> </div> diff --git a/code/app/src/routes/(main)/(public)/+layout.svelte b/code/app/src/routes/(main)/(public)/+layout.svelte index cbda776..0d84f9a 100644 --- a/code/app/src/routes/(main)/(public)/+layout.svelte +++ b/code/app/src/routes/(main)/(public)/+layout.svelte @@ -1,10 +1,10 @@ <script> - import { LocaleSwitcher } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; + import {LocaleSwitcher} from "$components"; + import LL from "$i18n/i18n-svelte"; </script> -<LocaleSwitcher tabindex={-1} /> -<slot /> +<LocaleSwitcher tabindex={-1}/> +<slot/> <footer class="grid sm:gap-5 grid-flow-row sm:justify-center px-2 sm:grid-flow-col"> <a href="https://greatoffice.life/privacy" class="link"> {$LL.privacyPolicy()} 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 2b34dfc..34dabae 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 @@ <script lang="ts"> - import { Alert, Input, Button } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; - import { FormError } from "$lib/models/internal/FormError"; - import { PasswordResetService } from "$lib/services/password-reset-service"; + import {Alert, Input, Button} from "$components"; + import LL from "$i18n/i18n-svelte"; + import {FormError} from "$models/internal/FormError"; + import {PasswordResetService} from "$services/password-reset-service"; const formData = { email: { @@ -61,21 +61,21 @@ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> <form class="space-y-6" on:submit|preventDefault={submit_form_async}> {#if showErrorAlert} - <Alert title={formError.title} message={formError.subtitle} type="error" /> + <Alert title={formError.title} message={formError.subtitle} type="error"/> {:else if showSuccessAlert} - <Alert type="success" title={$LL.success()} message={$LL.resetPasswordPage.requestSentMessage()} /> + <Alert type="success" title={$LL.success()} message={$LL.resetPasswordPage.requestSentMessage()}/> {/if} <Input - id="email" - name="email" - type="email" - autocomplete="email" - errors={formData.email.errors} - bind:value={formData.email.value} - required - label={$LL.emailAddress()} + id="email" + name="email" + type="email" + autocomplete="email" + errors={formData.email.errors} + bind:value={formData.email.value} + required + label={$LL.emailAddress()} /> - <Button text={$LL.submit()} type="submit" {loading} fullWidth /> + <Button text={$LL.submit()} type="submit" {loading} fullWidth/> </form> </div> </div> diff --git a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts index 389d04c..907b444 100644 --- a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts +++ b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.server.ts @@ -1,11 +1,11 @@ -import { is_guid } from '$lib/helpers'; -import { redirect } from '@sveltejs/kit'; -import type { PageServerLoad } from './$types'; +import {is_guid} from "$help"; +import {redirect} from "@sveltejs/kit"; +import type {PageServerLoad} from "./$types"; -export const load: PageServerLoad = async ({ params }) => { +export const load: PageServerLoad = async ({params}) => { const resetRequestId = params.id ?? ""; if (!is_guid(resetRequestId)) throw redirect(302, "/reset-password"); return { - resetRequestId + resetRequestId, }; };
\ No newline at end of file diff --git a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte index ba59a8f..8f817bf 100644 --- a/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte +++ b/code/app/src/routes/(main)/(public)/reset-password/[id]/+page.svelte @@ -1,11 +1,11 @@ <script lang="ts"> - import { onMount } from "svelte"; - import LL from "$lib/i18n/i18n-svelte"; - import { Alert, Input, Button } from "$lib/components"; - import type { PageServerData } from "./$types"; - import { goto } from "$app/navigation"; - import { SignInPageMessage, signInPageMessageQueryKey } from "$routes/(main)/(public)/sign-in"; - import { PasswordResetService } from "$lib/services/password-reset-service"; + import {onMount} from "svelte"; + import LL from "$i18n/i18n-svelte"; + import {Alert, Input, Button} from "$components"; + import type {PageServerData} from "./$types"; + import {goto} from "$app/navigation"; + import {SignInPageMessage, signInPageMessageQueryKey} from "$routes/(main)/(public)/sign-in"; + import {PasswordResetService} from "$services/password-reset-service"; export let data: PageServerData; const passwordResets = new PasswordResetService(); @@ -57,18 +57,19 @@ <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> <form class="space-y-6" on:submit|preventDefault={submitFormAsync}> {#if requestIsInvalid} - <Alert title={$LL.resetPasswordPage.invalidRequestTitle()} message={$LL.resetPasswordPage.invalidRequestMessage()} /> + <Alert title={$LL.resetPasswordPage.invalidRequestTitle()} + message={$LL.resetPasswordPage.invalidRequestMessage()}/> {/if} <Input - id="password" - name="password" - type="password" - autocomplete="new-password" - required - bind:value={formData.newPassword.value} - label={$LL.resetPasswordPage.newPassword()} + id="password" + name="password" + type="password" + autocomplete="new-password" + required + bind:value={formData.newPassword.value} + label={$LL.resetPasswordPage.newPassword()} /> - <Button text={$LL.submit()} type="submit" {loading} fullWidth /> + <Button text={$LL.submit()} type="submit" {loading} fullWidth/> </form> </div> </div> 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 12801c5..e862050 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 @@ <script lang="ts"> - import { goto } from "$app/navigation"; - import { Button, Checkbox, Input, Alert } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; + import {goto} from "$app/navigation"; + import {Button, Checkbox, Input, Alert} from "$components"; + import LL from "$i18n/i18n-svelte"; import pwKey from "$actions/pwKey"; - import { onMount } from "svelte"; - import { signInPageMessageQueryKey, signInPageTestKeys, type SignInPageMessage } from "."; - import { AccountService } from "$lib/services/account-service"; - import type { LoginPayload } from "$lib/services/abstractions/IAccountService"; - import { FormError } from "$lib/models/internal/FormError"; + import {onMount} from "svelte"; + import {signInPageMessageQueryKey, signInPageTestKeys, type SignInPageMessage} from "."; + import {AccountService} from "$services/account-service"; + import type {LoginPayload} from "$services/abstractions/IAccountService"; + import {FormError} from "$models/internal/FormError"; let loading = false; let showErrorAlert = false; @@ -71,24 +71,24 @@ <div class="sm:max-w-md sm:mx-auto sm:w-full"> {#if messageType === "after-password-reset"} <Alert - title={$LL.signInPage.yourNewPasswordIsApplied()} - _pwKey={signInPageTestKeys.afterPasswordResetAlert} - message={$LL.signInPage.signInBelow()} - closeable + title={$LL.signInPage.yourNewPasswordIsApplied()} + _pwKey={signInPageTestKeys.afterPasswordResetAlert} + message={$LL.signInPage.signInBelow()} + closeable /> {:else if messageType === "user-disabled"} <Alert - title={$LL.signInPage.yourAccountIsDisabled()} - _pwKey={signInPageTestKeys.userDisabledAlert} - message={$LL.signInPage.contactYourAdminIfDisabled()} - closeable + title={$LL.signInPage.yourAccountIsDisabled()} + _pwKey={signInPageTestKeys.userDisabledAlert} + message={$LL.signInPage.contactYourAdminIfDisabled()} + closeable /> {:else if messageType === "user-inactivity"} <Alert - title={$LL.signInPage.youHaveReachedInactivityLimit()} - _pwKey={signInPageTestKeys.userInactivityAlert} - message={$LL.signInPage.feelFreeToSignInAgain()} - closeable + title={$LL.signInPage.youHaveReachedInactivityLimit()} + _pwKey={signInPageTestKeys.userInactivityAlert} + message={$LL.signInPage.feelFreeToSignInAgain()} + closeable /> {/if} </div> @@ -107,37 +107,39 @@ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> {#if showErrorAlert} - <Alert title={formError.title} message={formError.subtitle} type="error" _pwKey={signInPageTestKeys.formErrorAlert} /> + <Alert title={formError.title} message={formError.subtitle} type="error" + _pwKey={signInPageTestKeys.formErrorAlert}/> {/if} - <form class="space-y-6 mt-2" use:pwKey={signInPageTestKeys.signInForm} on:submit|preventDefault={submit_form_async}> + <form class="space-y-6 mt-2" use:pwKey={signInPageTestKeys.signInForm} + on:submit|preventDefault={submit_form_async}> <Input - id="username" - _pwKey={signInPageTestKeys.usernameInput} - name="username" - type="email" - label={$LL.emailAddress()} - required - bind:value={formData.username.value} + id="username" + _pwKey={signInPageTestKeys.usernameInput} + name="username" + type="email" + label={$LL.emailAddress()} + required + bind:value={formData.username.value} /> <Input - id="password" - name="password" - type="password" - label={$LL.password()} - _pwKey={signInPageTestKeys.passwordInput} - autocomplete="current-password" - required - bind:value={formData.password.value} + id="password" + name="password" + type="password" + label={$LL.password()} + _pwKey={signInPageTestKeys.passwordInput} + autocomplete="current-password" + required + bind:value={formData.password.value} /> <div class="flex items-center justify-between"> <Checkbox - id="remember-me" - _pwKey={signInPageTestKeys.rememberMeCheckbox} - name="remember-me" - bind:checked={formData.persist.value} - label={$LL.signInPage.notMyComputer()} + id="remember-me" + _pwKey={signInPageTestKeys.rememberMeCheckbox} + name="remember-me" + bind:checked={formData.persist.value} + label={$LL.signInPage.notMyComputer()} /> <div class="text-sm"> <a href="/reset-password" class="link" use:pwKey={signInPageTestKeys.resetPasswordAnchor}> @@ -146,7 +148,7 @@ </div> </div> - <Button text={$LL.submit()} fullWidth type="submit" {loading} /> + <Button text={$LL.submit()} fullWidth type="submit" {loading}/> </form> </div> </div> diff --git a/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts b/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts index ea8c494..9a9f7a5 100644 --- a/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts +++ b/code/app/src/routes/(main)/(public)/sign-in/tests/index.spec.ts @@ -1,12 +1,12 @@ -import { test, expect } from "@playwright/test"; -import { signInPageTestKeys } from "../index"; -import { get_test_context } from "$lib/configuration"; -import { get_pw_key_selector } from "$lib/helpers"; +import {test, expect} from "@playwright/test"; +import {signInPageTestKeys} from "../index"; +import {get_test_context} from "$configuration"; +import {get_pw_key_selector} from "$help"; const context = get_test_context(); -test("form loads", async ({ page }) => { +test("form loads", async ({page}) => { page.goto("/sign-in"); const form = page.locator(get_pw_key_selector(signInPageTestKeys.signInForm)); expect(form.isVisible()).toBeTruthy(); -}) +}); diff --git a/code/app/src/routes/(main)/(public)/sign-up/+page.svelte b/code/app/src/routes/(main)/(public)/sign-up/+page.svelte index 9fa8dfa..58940ea 100644 --- a/code/app/src/routes/(main)/(public)/sign-up/+page.svelte +++ b/code/app/src/routes/(main)/(public)/sign-up/+page.svelte @@ -1,10 +1,10 @@ <script lang="ts"> - import { goto } from "$app/navigation"; - import type { CreateAccountPayload } from "$lib/api/account"; - import { Button, Input, Alert } from "$lib/components"; - import LL from "$lib/i18n/i18n-svelte"; - import { FormError } from "$lib/models/internal/FormError"; - import { AccountService } from "$lib/services/account-service"; + import {goto} from "$app/navigation"; + import type {CreateAccountPayload} from "$api/account"; + import {Button, Input, Alert} from "$components"; + import LL from "$i18n/i18n-svelte"; + import {FormError} from "$models/internal/FormError"; + import {AccountService} from "$services/account-service"; const formData = { username: { @@ -76,30 +76,30 @@ <div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> <div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> {#if showErrorAlert} - <Alert title={formError.title} message={formError.subtitle} type="error" class="mb-2" /> + <Alert title={formError.title} message={formError.subtitle} type="error" class="mb-2"/> {/if} <form class="space-y-6" on:submit|preventDefault={submit_form_async}> <Input - label={$LL.emailAddress()} - id="email" - name="email" - autocomplete="email" - required - type="email" - bind:value={formData.username.value} - errors={formData.username.errors} + label={$LL.emailAddress()} + id="email" + name="email" + autocomplete="email" + required + type="email" + bind:value={formData.username.value} + errors={formData.username.errors} /> <Input - label={$LL.password()} - id="password" - name="password" - required - type="password" - bind:value={formData.password.value} - errors={formData.password.errors} + label={$LL.password()} + id="password" + name="password" + required + type="password" + bind:value={formData.password.value} + errors={formData.password.errors} /> - <Button type="submit" text={$LL.submit()} {loading} fullWidth /> + <Button type="submit" text={$LL.submit()} {loading} fullWidth/> </form> </div> </div> diff --git a/code/app/src/routes/(main)/+layout.server.ts b/code/app/src/routes/(main)/+layout.server.ts index cd41734..086d1c0 100644 --- a/code/app/src/routes/(main)/+layout.server.ts +++ b/code/app/src/routes/(main)/+layout.server.ts @@ -1,27 +1,27 @@ -import { api_base, CookieNames } from "$lib/configuration"; -import { log_debug, log_error } from "$lib/logger"; -import { error, redirect } from "@sveltejs/kit"; -import { Temporal } from "temporal-polyfill"; -import type { LayoutServerLoad } from "./$types"; +import {api_base, CookieNames} from "$configuration"; +import {log_debug, log_error} from "$help/logger"; +import {error, redirect} from "@sveltejs/kit"; +import {Temporal} from "temporal-polyfill"; +import type {LayoutServerLoad} from "./$types"; -export const load: LayoutServerLoad = async ({ route, cookies, locals }) => { +export const load: LayoutServerLoad = async ({route, cookies, locals}) => { const isBaseRoute = route.id === "/(main)"; const isPublicRoute = (route.id?.startsWith("/(main)/(public)") || isBaseRoute) ?? true; const sessionIsValid = (await cached_result<Response>("sessionCheck", 120, () => fetch(api_base("_/valid-session"), { headers: { - Cookie: CookieNames.session + "=" + cookies.get(CookieNames.session) - } + Cookie: CookieNames.session + "=" + cookies.get(CookieNames.session), + }, }).catch((e) => { log_error(e); throw error(503, { - message: "We are experiencing a service disruption! Have patience while we resolve the issue." - }) + message: "We are experiencing a service disruption! Have patience while we resolve the issue.", + }); }))).ok; log_debug("Base Layout loaded", { sessionIsValid, isPublicRoute, - routeId: route.id + routeId: route.id, }); if (sessionIsValid && isPublicRoute) { @@ -31,17 +31,18 @@ export const load: LayoutServerLoad = async ({ route, cookies, locals }) => { } return { - locale: locals.locale - } -} + locale: locals.locale, + }; +}; let resultCache = {}; + async function cached_result<T>(key: string, staleAfterSeconds: number, code: any) { if (!resultCache[key]) { resultCache[key] = { l: 0, - c: undefined as T - } + c: undefined as T, + }; } const staleEpoch = ((resultCache[key]?.l ?? 0) + staleAfterSeconds); const isStale = staleEpoch < Temporal.Now.instant().epochSeconds; @@ -54,7 +55,7 @@ async function cached_result<T>(key: string, staleAfterSeconds: number, code: an cacheKey: key, isStale, cache: resultCache[key], - staleEpoch + staleEpoch, }); return resultCache[key].c as T; diff --git a/code/app/src/routes/(main)/+layout.svelte b/code/app/src/routes/(main)/+layout.svelte index 7d4ce05..2b96527 100644 --- a/code/app/src/routes/(main)/+layout.svelte +++ b/code/app/src/routes/(main)/+layout.svelte @@ -1,21 +1,21 @@ <script lang="ts"> import "../../app.pcss"; - import { setLocale } from "$lib/i18n/i18n-svelte"; - import { ExclamationTriangleIcon } from "$lib/components/icons"; - import type { LayoutData } from "./$types"; + import {setLocale} from "$i18n/i18n-svelte"; + import {ExclamationTriangleIcon} from "$components/icons"; + import type {LayoutData} from "./$types"; let online = true; export let data: LayoutData; setLocale(data.locale); </script> -<svelte:window bind:online /> +<svelte:window bind:online/> {#if !online} <div class="bg-yellow-50 relative z-50 p-4"> <div class="flex"> <div class="flex-shrink-0"> - <ExclamationTriangleIcon class="bg-yellow-50 text-yellow-500" /> + <ExclamationTriangleIcon class="bg-yellow-50 text-yellow-500"/> </div> <div class="ml-3"> <p class="text-sm text-yellow-700">You seem to be offline, please check your internet connection.</p> @@ -24,4 +24,4 @@ </div> {/if} -<slot /> +<slot/> diff --git a/code/app/src/routes/(main)/+layout.ts b/code/app/src/routes/(main)/+layout.ts index cf08d66..0509aaf 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 '$lib/i18n/i18n-types' -import { loadLocaleAsync } from '$lib/i18n/i18n-util.async' -import { setLocale } from '$lib/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 } }) => { - await loadLocaleAsync(locale) - setLocale(locale) - return { locale } -}
\ No newline at end of file +export const load: LayoutLoad<{ locale: Locales }> = async ({data: {locale}}) => { + await loadLocaleAsync(locale); + setLocale(locale); + return {locale}; +};
\ No newline at end of file diff --git a/code/app/src/routes/book/alerts/+page.svelte b/code/app/src/routes/book/alerts/+page.svelte index d008d85..ed4c92b 100644 --- a/code/app/src/routes/book/alerts/+page.svelte +++ b/code/app/src/routes/book/alerts/+page.svelte @@ -1,31 +1,31 @@ <script> - import Alert from "$lib/components/alert.svelte"; + import Alert from "$components/alert.svelte"; </script> <section> <h2>Info</h2> - <Alert type="info" message="This is message" title="This is title" /> + <Alert type="info" message="This is message" title="This is title"/> </section> <section> <h2>Warning</h2> - <Alert type="warning" message="This is message" title="This is title" /> + <Alert type="warning" message="This is message" title="This is title"/> </section> <section> <h2>Error</h2> - <Alert type="error" message="This is message" title="This is title" /> + <Alert type="error" message="This is message" title="This is title"/> </section> <section> <h2>Success</h2> - <Alert type="success" message="This is message" title="This is title" /> + <Alert type="success" message="This is message" title="This is title"/> </section> <section> <h2>Actions</h2> <Alert - type="info" - message="This is message" - title="This is title" - closeable - actions={[ + type="info" + message="This is message" + title="This is title" + closeable + actions={[ { id: "confirm", text: "Yes!", @@ -41,30 +41,30 @@ <section> <h2>Right link</h2> <Alert - on:rightLinkCliked={() => alert("Right link clicked")} - rightLinkText="Link or action" - title="Go here" - message="Hehe" - type="error" + on:rightLinkCliked={() => alert("Right link clicked")} + rightLinkText="Link or action" + title="Go here" + message="Hehe" + type="error" /> </section> <section> <h2>List</h2> <Alert - title="This is title" - listItems={["Message 1", "Message 2"]} - type="error" - message="This is bad dude" - closeable - closeableCooldown="60" - id="alert-1" - on:actrepeat={() => { + title="This is title" + listItems={["Message 1", "Message 2"]} + type="error" + message="This is bad dude" + closeable + closeableCooldown="60" + id="alert-1" + on:actrepeat={() => { alert("Repeat requested"); }} - actions={[{ id: "repeat", text: "Try again" }]} + actions={[{ id: "repeat", text: "Try again" }]} /> </section> <section> <h2>Closeable</h2> - <Alert message="This is message" closeable type="info" /> + <Alert message="This is message" closeable type="info"/> </section> diff --git a/code/app/src/routes/book/badges/+page.svelte b/code/app/src/routes/book/badges/+page.svelte index cd5120a..50ae61e 100644 --- a/code/app/src/routes/book/badges/+page.svelte +++ b/code/app/src/routes/book/badges/+page.svelte @@ -1,18 +1,19 @@ <script lang="ts"> - import Badge from "$lib/components/badge.svelte"; + import Badge from "$components/badge.svelte"; </script> <section> <h2>Variants</h2> - <Badge text="default" /> - <Badge type="blue" text="blue" /> - <Badge type="green" text="green" /> - <Badge type="red" text="red" /> - <Badge type="tame" text="tame" /> - <Badge type="yellow" text="yellow" /> - <Badge size="large" text="large" /> - <Badge text="with dot" withDot type="blue" /> - <Badge text="removable" removable id="badge-1" on:remove={(e) => alert("removed " + e.detail.id)} /> - <Badge text="with dot" size="large" withDot type="blue" /> - <Badge text="removable" removable size="large" id="badge-2" uppercase on:remove={(e) => alert("removed " + e.detail.id)} /> + <Badge text="default"/> + <Badge type="blue" text="blue"/> + <Badge type="green" text="green"/> + <Badge type="red" text="red"/> + <Badge type="tame" text="tame"/> + <Badge type="yellow" text="yellow"/> + <Badge size="large" text="large"/> + <Badge text="with dot" withDot type="blue"/> + <Badge text="removable" removable id="badge-1" on:remove={(e) => alert("removed " + e.detail.id)}/> + <Badge text="with dot" size="large" withDot type="blue"/> + <Badge text="removable" removable size="large" id="badge-2" uppercase + on:remove={(e) => alert("removed " + e.detail.id)}/> </section> diff --git a/code/app/src/routes/book/buttons/+page.svelte b/code/app/src/routes/book/buttons/+page.svelte index 19ba163..6668a64 100644 --- a/code/app/src/routes/book/buttons/+page.svelte +++ b/code/app/src/routes/book/buttons/+page.svelte @@ -1,23 +1,23 @@ <script> - import Button from "$lib/components/button.svelte"; + import Button from "$components/button.svelte"; </script> <section> <h2>Primary</h2> - <Button kind="primary" text="Small" size="sm" /> - <Button kind="primary" text="Medium/Default" /> - <Button kind="primary" text="Large" size="lg" /> - <Button kind="primary" text="Extra large" size="xl" /> + <Button kind="primary" text="Small" size="sm"/> + <Button kind="primary" text="Medium/Default"/> + <Button kind="primary" text="Large" size="lg"/> + <Button kind="primary" text="Extra large" size="xl"/> </section> <section> <h2>Secondary</h2> - <Button kind="secondary" text="Click me!" /> + <Button kind="secondary" text="Click me!"/> </section> <section> <h2>White</h2> - <Button kind="white" text="Click me!" /> + <Button kind="white" text="Click me!"/> </section> <section> <h2>Loading</h2> - <Button kind="primary" loading={true} text="Wait" /> + <Button kind="primary" loading={true} text="Wait"/> </section> diff --git a/code/app/src/routes/book/inputs/+page.svelte b/code/app/src/routes/book/inputs/+page.svelte index 7ca0d31..d5566ed 100644 --- a/code/app/src/routes/book/inputs/+page.svelte +++ b/code/app/src/routes/book/inputs/+page.svelte @@ -1,7 +1,6 @@ <script lang="ts"> - import { TextArea, Input, Combobox } from "$lib/components"; - import { DatabaseIcon } from "$lib/components/icons"; - import LL from "$lib/i18n/i18n-svelte"; + import {TextArea, Input, Combobox} from "$components"; + import {DatabaseIcon} from "$components/icons"; let value; let i = 0; @@ -16,7 +15,7 @@ i++; } - async function add({ name }) { + async function add({name}) { const copy = options; copy.push({ id: crypto.randomUUID(), @@ -28,48 +27,49 @@ <section> <h2>Combobox</h2> - <Combobox {options} label="Wiii" multiple createable on_create_async={add} /> + <Combobox {options} label="Wiii" multiple createable on_create_async={add}/> </section> <section> <h2>Default</h2> - <Input label="Input me" placeholder="Hello" bind:value /> + <Input label="Input me" placeholder="Hello" bind:value/> </section> <section> <h2>With icon</h2> - <Input label="Input me" placeholder="Hello" icon={DatabaseIcon} bind:value /> + <Input label="Input me" placeholder="Hello" icon={DatabaseIcon} bind:value/> </section> <section> <h2>With corner hint</h2> - <Input label="Input me ->" placeholder="Hello" cornerHint="Hint hint" bind:value /> + <Input label="Input me ->" placeholder="Hello" cornerHint="Hint hint" bind:value/> </section> <section> <h2>Disabled</h2> - <Input label="No" placeholder="Sorry" disabled bind:value /> + <Input label="No" placeholder="Sorry" disabled bind:value/> </section> <section> <h2>Errored</h2> - <Input label="No" placeholder="Sorry" errorText="That's not right" bind:value icon={DatabaseIcon} /> + <Input label="No" placeholder="Sorry" errorText="That's not right" bind:value icon={DatabaseIcon}/> </section> <section> <h2>Many errors</h2> - <Input label="No" placeholder="Sorry" errors={["That's not right", "Call help!", "Get it together"]} bind:value icon={DatabaseIcon} /> + <Input label="No" placeholder="Sorry" errors={["That's not right", "Call help!", "Get it together"]} bind:value + icon={DatabaseIcon}/> </section> <section> <h2>Help</h2> - <Input label="Go ahead" placeholder="Write here" helpText="Write above" bind:value /> + <Input label="Go ahead" placeholder="Write here" helpText="Write above" bind:value/> </section> <section> <h2>Addon</h2> - <Input label="Go ahead" placeholder="Write here" bind:value helpText="Write above" addon="To the right" /> + <Input label="Go ahead" placeholder="Write here" bind:value helpText="Write above" addon="To the right"/> </section> <section> <h2>Textarea</h2> - <TextArea bind:value errorText="oh no" label="Hi" /> + <TextArea bind:value errorText="oh no" label="Hi"/> </section> diff --git a/code/app/src/routes/book/toggles/+page.svelte b/code/app/src/routes/book/toggles/+page.svelte index 730e6f2..71c9298 100644 --- a/code/app/src/routes/book/toggles/+page.svelte +++ b/code/app/src/routes/book/toggles/+page.svelte @@ -1,27 +1,27 @@ <script> - import Switch from "$lib/components/switch.svelte"; + import Switch from "$components/switch.svelte"; </script> <section> <h2>Default</h2> - <Switch /> + <Switch/> </section> <section> <h2>Short</h2> - <Switch type="short" /> + <Switch type="short"/> </section> <section> <h2>Icon</h2> - <Switch type="icon" /> + <Switch type="icon"/> </section> <section> <h2>Label / Description</h2> <div class="max-w-md"> - <Switch label="Label" description="Some text" /> + <Switch label="Label" description="Some text"/> </div> </section> <section> <h2>Label / Description (right aligned)</h2> - <Switch label="Label" description="Some text" rightAlignedLabelDescription /> + <Switch label="Label" description="Some text" rightAlignedLabelDescription/> </section> diff --git a/code/app/src/lib/services/abstractions/IAccountService.ts b/code/app/src/services/abstractions/IAccountService.ts index 2beeb08..0645eb6 100644 --- a/code/app/src/lib/services/abstractions/IAccountService.ts +++ b/code/app/src/services/abstractions/IAccountService.ts @@ -1,21 +1,25 @@ -import type { KnownProblem } from "$lib/models/internal/KnownProblem" +import type {KnownProblem} from "$models/internal/KnownProblem"; +import type {Writable} from "svelte/store"; export interface IAccountService { - session: Session, + session: Writable<Session>, + login_async(payload: LoginPayload): Promise<LoginResponse>, + logout_async(): Promise<void>, + create_account_async(payload: CreateAccountPayload): Promise<CreateAccountResponse>, + delete_current_async(): Promise<DeleteAccountResponse>, + update_current_async(payload: UpdateAccountPayload): Promise<UpdateAccountResponse>, } export type Session = { - profile: { - username: string, - displayName: string, - id: string, - }, - lastChecked: number, + username: string, + displayName: string, + id: string, + _lastUpdated: number } export type LoginPayload = { diff --git a/code/app/src/lib/services/abstractions/IPasswordResetService.ts b/code/app/src/services/abstractions/IPasswordResetService.ts index b6f6671..59d2bc6 100644 --- a/code/app/src/lib/services/abstractions/IPasswordResetService.ts +++ b/code/app/src/services/abstractions/IPasswordResetService.ts @@ -1,4 +1,4 @@ -import type { KnownProblem } from "$lib/models/internal/KnownProblem" +import type { KnownProblem } from "$models/internal/KnownProblem" export interface IPasswordResetService { create_request_async(email: string): Promise<CreateRequestResponse>, diff --git a/code/app/src/lib/services/abstractions/ISettingsService.ts b/code/app/src/services/abstractions/ISettingsService.ts index 366e337..366e337 100644 --- a/code/app/src/lib/services/abstractions/ISettingsService.ts +++ b/code/app/src/services/abstractions/ISettingsService.ts diff --git a/code/app/src/services/account-service.ts b/code/app/src/services/account-service.ts new file mode 100644 index 0000000..9d45950 --- /dev/null +++ b/code/app/src/services/account-service.ts @@ -0,0 +1,116 @@ +import {http_delete_async, http_get_async, http_post_async} from "$api/_fetch"; +import {api_base, CookieNames, StorageKeys} from "$configuration"; +import {is_known_problem} from "$models/internal/KnownProblem"; +import {log_debug} from "$help/logger"; +import {StoreType, writable_persistent} from "$help/persistent-store"; +import {get} from "svelte/store"; +import type {Writable} from "svelte/store"; +import {Temporal} from "temporal-polyfill"; +import type { + CreateAccountPayload, + CreateAccountResponse, + DeleteAccountResponse, + IAccountService, + LoginPayload, + LoginResponse, + Session, + UpdateAccountPayload, + UpdateAccountResponse, +} from "./abstractions/IAccountService"; + +export class AccountService implements IAccountService { + session: Writable<Session>; + private sessionCooldown = 3600; + + constructor() { + this.session = writable_persistent({ + name: StorageKeys.session, + initialState: {} as Session, + options: { + store: StoreType.LOCAL, + }, + }); + this.refresh_session(); + } + + async refresh_session(forceRefresh: boolean = false): Promise<void> { + const currentValue = get(this.session); + const currentEpoch = Temporal.Now.instant().epochSeconds; + if (currentValue?._lastUpdated + this.sessionCooldown < currentEpoch) { + log_debug("Session is not stale yet", { + currentEpoch, + staleEpoch: currentValue?._lastUpdated + this.sessionCooldown, + }); + return; + } + const sessionResponse = await http_get_async(api_base("_/account/session")); + if (sessionResponse.ok) { + + } + } + + async end_session(callback: Function): Promise<void> { + await this.logout_async(); + this.session.set(null); + if (typeof callback === "function") callback(); + return; + } + + async login_async(payload: LoginPayload): Promise<LoginResponse> { + const response = await http_post_async(api_base("_/account/login"), payload); + if (response.ok) return {isLoggedIn: true}; + if (is_known_problem(response)) return { + isLoggedIn: false, + knownProblem: await response.json(), + }; + return { + isLoggedIn: false, + }; + } + + async logout_async(): Promise<void> { + 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; + } + + async create_account_async(payload: CreateAccountPayload): Promise<CreateAccountResponse> { + const response = await http_post_async(api_base("_/account/create"), payload); + if (response.ok) return {isCreated: true}; + if (is_known_problem(response)) return { + isCreated: false, + knownProblem: await response.json(), + }; + + return { + isCreated: false, + }; + } + + async delete_current_async(): Promise<DeleteAccountResponse> { + const response = await http_delete_async(api_base("_/account/delete")); + return { + isDeleted: response.ok, + }; + } + + async update_current_async(payload: UpdateAccountPayload): Promise<UpdateAccountResponse> { + const response = await http_post_async(api_base("_/account/update"), payload); + if (response.ok) return {isUpdated: true}; + if (is_known_problem(response)) return { + isUpdated: false, + knownProblem: await response.json(), + }; + + return { + isUpdated: false, + }; + } +}
\ No newline at end of file diff --git a/code/app/src/lib/services/password-reset-service.ts b/code/app/src/services/password-reset-service.ts index 650b5f7..ab3a953 100644 --- a/code/app/src/lib/services/password-reset-service.ts +++ b/code/app/src/services/password-reset-service.ts @@ -1,38 +1,45 @@ -import { http_get_async, http_post_async } from "$lib/api/_fetch"; -import { api_base } from "$lib/configuration"; -import { is_known_problem } from "$lib/models/internal/KnownProblem"; -import type { CreateRequestResponse, FulfillRequestResponse, IPasswordResetService, RequestIsValidResponse } from "./abstractions/IPasswordResetService"; +import {http_get_async, http_post_async} from "$api/_fetch"; +import {api_base} from "$configuration"; +import {is_known_problem} from "$models/internal/KnownProblem"; +import type { + CreateRequestResponse, + FulfillRequestResponse, + IPasswordResetService, + RequestIsValidResponse, +} from "./abstractions/IPasswordResetService"; export class PasswordResetService implements IPasswordResetService { async create_request_async(email: string): Promise<CreateRequestResponse> { - const response = await http_post_async(api_base("_/password-reset-request/create"), { email }); - if (response.ok) return { isCreated: true }; + const response = await http_post_async(api_base("_/password-reset-request/create"), {email}); + if (response.ok) return {isCreated: true}; if (is_known_problem(response)) return { isCreated: false, - knownProblem: await response.json() - } + knownProblem: await response.json(), + }; return { - isCreated: false - } + isCreated: false, + }; } + async fulfill_request_async(id: string, newPassword: string): Promise<FulfillRequestResponse> { - const response = await http_post_async(api_base("_/password-reset-request/fulfill"), { id: id, newPassword }); - if (response.ok) return { isFulfilled: true }; + const response = await http_post_async(api_base("_/password-reset-request/fulfill"), {id: id, newPassword}); + if (response.ok) return {isFulfilled: true}; if (is_known_problem(response)) return { isFulfilled: false, - knownProblem: await response.json() - } + knownProblem: await response.json(), + }; return { isFulfilled: false, - } + }; } + async request_is_valid_async(id: string): Promise<RequestIsValidResponse> { const response = await http_get_async(api_base("_/password-reset-request/is-valid?id=" + id)); const responseBody = await response.json() as { isValid: boolean }; return { - isValid: responseBody.isValid - } + isValid: responseBody.isValid, + }; } }
\ No newline at end of file diff --git a/code/app/src/lib/services/settings-service.ts b/code/app/src/services/settings-service.ts index 6b801ac..20053a9 100644 --- a/code/app/src/lib/services/settings-service.ts +++ b/code/app/src/services/settings-service.ts @@ -1,4 +1,4 @@ -import type { ISettingsService } from "./abstractions/ISettingsService"; +import type {ISettingsService} from "./abstractions/ISettingsService"; export class SettingsService implements ISettingsService { get_user_settings(): Promise<void> { diff --git a/code/app/svelte.config.js b/code/app/svelte.config.js index f226824..9f2272c 100644 --- a/code/app/svelte.config.js +++ b/code/app/svelte.config.js @@ -12,8 +12,15 @@ const config = { adapter: adapter(), alias: { "$actions": "src/actions", - "$lib": "src/lib", "$routes": "src/routes", + "$models": "src/models", + "$api": "src/api", + "$components": "src/components", + "$help": "src/help", + "$i18n": "src/i18n", + "$services": "src/services", + "$configuration": "src/configuration", + "$": "src", } }, }; |
