aboutsummaryrefslogtreecommitdiffstats
path: root/code/app/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'code/app/src/lib')
-rw-r--r--code/app/src/lib/api/_fetch.ts95
-rw-r--r--code/app/src/lib/api/account/index.ts39
-rw-r--r--code/app/src/lib/api/api-tokens/index.ts23
-rw-r--r--code/app/src/lib/api/projects/index.ts12
-rw-r--r--code/app/src/lib/colors.ts47
-rw-r--r--code/app/src/lib/components/alert.svelte268
-rw-r--r--code/app/src/lib/components/badge.svelte76
-rw-r--r--code/app/src/lib/components/button.svelte115
-rw-r--r--code/app/src/lib/components/checkbox.svelte29
-rw-r--r--code/app/src/lib/components/combobox.svelte450
-rw-r--r--code/app/src/lib/components/icons/adjustments.svelte14
-rw-r--r--code/app/src/lib/components/icons/bars-3-center-left.svelte15
-rw-r--r--code/app/src/lib/components/icons/calendar.svelte14
-rw-r--r--code/app/src/lib/components/icons/check-circle.svelte13
-rw-r--r--code/app/src/lib/components/icons/chevron-down.svelte7
-rw-r--r--code/app/src/lib/components/icons/chevron-up-down.svelte13
-rw-r--r--code/app/src/lib/components/icons/chevron-up.svelte7
-rw-r--r--code/app/src/lib/components/icons/database.svelte14
-rw-r--r--code/app/src/lib/components/icons/exclamation-circle.svelte13
-rw-r--r--code/app/src/lib/components/icons/exclamation-triangle.svelte13
-rw-r--r--code/app/src/lib/components/icons/folder-open.svelte14
-rw-r--r--code/app/src/lib/components/icons/funnel.svelte7
-rw-r--r--code/app/src/lib/components/icons/home.svelte14
-rw-r--r--code/app/src/lib/components/icons/index.ts47
-rw-r--r--code/app/src/lib/components/icons/information-circle.svelte13
-rw-r--r--code/app/src/lib/components/icons/magnifying-glass.svelte13
-rw-r--r--code/app/src/lib/components/icons/megaphone.svelte14
-rw-r--r--code/app/src/lib/components/icons/menu.svelte14
-rw-r--r--code/app/src/lib/components/icons/queue-list.svelte14
-rw-r--r--code/app/src/lib/components/icons/spinner.svelte20
-rw-r--r--code/app/src/lib/components/icons/x-circle.svelte13
-rw-r--r--code/app/src/lib/components/icons/x-mark.svelte11
-rw-r--r--code/app/src/lib/components/icons/x.svelte14
-rw-r--r--code/app/src/lib/components/index.ts23
-rw-r--r--code/app/src/lib/components/input.svelte113
-rw-r--r--code/app/src/lib/components/locale-switcher.svelte56
-rw-r--r--code/app/src/lib/components/project-status-badge.svelte24
-rw-r--r--code/app/src/lib/components/switch.svelte125
-rw-r--r--code/app/src/lib/components/textarea.svelte81
-rw-r--r--code/app/src/lib/configuration.ts60
-rw-r--r--code/app/src/lib/helpers.ts464
-rw-r--r--code/app/src/lib/i18n/en/app/index.ts7
-rw-r--r--code/app/src/lib/i18n/en/index.ts59
-rw-r--r--code/app/src/lib/i18n/formatters.ts13
-rw-r--r--code/app/src/lib/i18n/i18n-svelte.ts12
-rw-r--r--code/app/src/lib/i18n/i18n-types.ts429
-rw-r--r--code/app/src/lib/i18n/i18n-util.async.ts42
-rw-r--r--code/app/src/lib/i18n/i18n-util.sync.ts35
-rw-r--r--code/app/src/lib/i18n/i18n-util.ts41
-rw-r--r--code/app/src/lib/i18n/nb/app/index.ts7
-rw-r--r--code/app/src/lib/i18n/nb/index.ts51
-rw-r--r--code/app/src/lib/logger.ts86
-rw-r--r--code/app/src/lib/models/base/Customer.ts21
-rw-r--r--code/app/src/lib/models/base/CustomerContact.ts8
-rw-r--r--code/app/src/lib/models/base/CustomerEvent.ts6
-rw-r--r--code/app/src/lib/models/base/SessionData.ts5
-rw-r--r--code/app/src/lib/models/base/Tenant.ts8
-rw-r--r--code/app/src/lib/models/base/User.ts13
-rw-r--r--code/app/src/lib/models/base/UserRole.ts5
-rw-r--r--code/app/src/lib/models/internal/FormError.ts24
-rw-r--r--code/app/src/lib/models/internal/ISession.ts8
-rw-r--r--code/app/src/lib/models/internal/KnownProblem.ts10
-rw-r--r--code/app/src/lib/models/projects/Project.ts13
-rw-r--r--code/app/src/lib/models/projects/ProjectLabel.ts5
-rw-r--r--code/app/src/lib/models/projects/ProjectMember.ts10
-rw-r--r--code/app/src/lib/models/projects/ProjectMeta.ts7
-rw-r--r--code/app/src/lib/models/projects/ProjectRole.ts7
-rw-r--r--code/app/src/lib/models/projects/ProjectStatus.ts5
-rw-r--r--code/app/src/lib/models/work/WorkCategory.ts5
-rw-r--r--code/app/src/lib/models/work/WorkEntry.ts13
-rw-r--r--code/app/src/lib/models/work/WorkEntryQueryResponse.ts27
-rw-r--r--code/app/src/lib/models/work/WorkLabel.ts5
-rw-r--r--code/app/src/lib/models/work/WorkQuery.ts30
-rw-r--r--code/app/src/lib/persistent-store.ts102
-rw-r--r--code/app/src/lib/services/abstractions/IAccountService.ts54
-rw-r--r--code/app/src/lib/services/abstractions/IPasswordResetService.ts21
-rw-r--r--code/app/src/lib/services/abstractions/ISettingsService.ts3
-rw-r--r--code/app/src/lib/services/account-service.ts61
-rw-r--r--code/app/src/lib/services/password-reset-service.ts38
-rw-r--r--code/app/src/lib/services/settings-service.ts7
-rw-r--r--code/app/src/lib/session.ts69
81 files changed, 0 insertions, 3803 deletions
diff --git a/code/app/src/lib/api/_fetch.ts b/code/app/src/lib/api/_fetch.ts
deleted file mode 100644
index c29d262..0000000
--- a/code/app/src/lib/api/_fetch.ts
+++ /dev/null
@@ -1,95 +0,0 @@
-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";
-
-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 });
- 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 });
- 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 });
- if (!skip_401_check && await redirect_if_401_async(response)) throw new Error("Server returned 401");
- return response;
-}
-
-async function internal_fetch_async(request: InternalFetchRequest): Promise<Response> {
- if (!request.init) throw new Error("request.init is required");
- const fetch_request = new Request(request.url, request.init);
- let response: any;
-
- try {
- if (request.timeout && request.timeout > 500) {
- response = await Promise.race([
- fetch(fetch_request),
- new Promise((_, reject) => setTimeout(() => reject(new Error("Timeout")), request.timeout)),
- ]);
- } else {
- response = await fetch(fetch_request);
- }
- } catch (error: any) {
- log_error(error);
- if (error.message === "Timeout") {
- console.error("Request timed out");
- } else if (error.message === "Network request failed") {
- console.error("No internet connection");
- } else {
- throw error;
- }
- }
-
- return response;
-}
-
-async function redirect_if_401_async(response: Response): Promise<boolean> {
- if (response.status === 401) {
- const redirectUrl = `/sign-in?${signInPageMessageQueryKey}=${SignInPageMessage.LOGGED_OUT}`;
- clear_session_data();
- if (browser) {
- await goto(redirectUrl);
- } else {
- throw redirect(307, redirectUrl);
- }
- }
- return false;
-}
-
-function make_request_init(method: string, body?: any, signal?: AbortSignal): RequestInit {
- const init = {
- method,
- credentials: "include",
- signal,
- headers: {
- "X-TimeZone": Temporal.Now.timeZone().id,
- },
- } as RequestInit;
-
- if (body) {
- init.body = JSON.stringify(body);
- init.headers["Content-Type"] = "application/json;charset=UTF-8";
- }
-
- return init;
-}
-
-
-export type InternalFetchRequest = {
- url: string,
- init: RequestInit,
- timeout?: number
- retry_count?: number,
-} \ No newline at end of file
diff --git a/code/app/src/lib/api/account/index.ts b/code/app/src/lib/api/account/index.ts
deleted file mode 100644
index 6dbcdc8..0000000
--- a/code/app/src/lib/api/account/index.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import {api_base} from "$lib/configuration";
-import {http_delete_async, http_get_async, http_post_async} from "../_fetch";
-
-export const http_account = {
- login_async(payload: LoginPayload): Promise<Response> {
- return http_post_async(api_base("_/account/login"), payload);
- },
- logout_async(): Promise<Response> {
- return http_get_async(api_base("_/account/logout"));
- },
- delete_account_async(): Promise<Response> {
- return http_delete_async(api_base("_/account/delete"));
- },
- update_profile_async(payload: UpdateProfilePayload): Promise<Response> {
- return http_post_async(api_base("_/account/update"), payload);
- },
- create_account_async(payload: CreateAccountPayload): Promise<Response> {
- return http_post_async(api_base("_/account/create"), payload);
- },
- get_profile_async(suppress_401: boolean): Promise<Response> {
- return http_get_async(api_base("_/account"), 0, suppress_401);
- },
-};
-
-export interface CreateAccountPayload {
- username: string,
- password: string
-}
-
-export interface LoginPayload {
- username: string,
- password: string,
- persist: boolean
-}
-
-export interface UpdateProfilePayload {
- username?: string,
- password?: string,
-}
diff --git a/code/app/src/lib/api/api-tokens/index.ts b/code/app/src/lib/api/api-tokens/index.ts
deleted file mode 100644
index 77bfd91..0000000
--- a/code/app/src/lib/api/api-tokens/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import {http_delete_async, http_get_async, http_post_async} from "src/lib/api/_fetch";
-import {api_base} from "src/lib/configuration";
-import type {Temporal} from "temporal-polyfill";
-
-export const http_api_tokens = {
- create_token_async(payload: CreateTokenPayload): Promise<Response> {
- return http_post_async(api_base("v1/api-tokens/create"), payload);
- },
- delete_token_async(id: string): Promise<Response> {
- 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"))
- },
-};
-
-export type CreateTokenPayload = {
- expiryDate: Temporal.PlainDateTime,
- allowRead: boolean,
- allowCreate: boolean,
- allowUpdate: boolean,
- allowDelete: boolean
-} \ No newline at end of file
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/colors.ts b/code/app/src/lib/colors.ts
deleted file mode 100644
index 34c7992..0000000
--- a/code/app/src/lib/colors.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-export function generate_random_hex_color(skip_contrast_check = false) {
- let hex = __generate_random_hex_color();
- if (skip_contrast_check) return hex;
- while ((__calculate_contrast_ratio("#ffffff", hex) < 4.5) || (__calculate_contrast_ratio("#000000", hex) < 4.5)) {
- hex = __generate_random_hex_color();
- }
-
- return hex;
-}
-
-// Largely copied from chroma js api
-function __generate_random_hex_color(): string {
- let code = "#";
- for (let i = 0; i < 6; i++) {
- code += "0123456789abcdef".charAt(Math.floor(Math.random() * 16));
- }
- return code;
-}
-
-function __calculate_contrast_ratio(hex1: string, hex2: string): number {
- const rgb1 = __hex_to_rgb(hex1);
- const rgb2 = __hex_to_rgb(hex2);
- const l1 = __get_luminance(rgb1[0], rgb1[1], rgb1[2]);
- const l2 = __get_luminance(rgb2[0], rgb2[1], rgb2[2]);
- const result = l1 > l2 ? (l1 + 0.05) / (l2 + 0.05) : (l2 + 0.05) / (l1 + 0.05);
- return result;
-}
-
-function __hex_to_rgb(hex: string): number[] {
- if (!hex.match(/^#([A-Fa-f0-9]{6})$/)) return [];
- if (hex[0] === "#") hex = hex.substring(1, hex.length);
- return [parseInt(hex.substring(0, 2), 16), parseInt(hex.substring(2, 4), 16), parseInt(hex.substring(4, 6), 16)];
-}
-
-function __get_luminance(r: any, g: any, b: any) {
- // relative luminance
- // see http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
- r = __luminance_x(r);
- g = __luminance_x(g);
- b = __luminance_x(b);
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
-}
-
-function __luminance_x(x: any) {
- x /= 255;
- return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
-}
diff --git a/code/app/src/lib/components/alert.svelte b/code/app/src/lib/components/alert.svelte
deleted file mode 100644
index fd57105..0000000
--- a/code/app/src/lib/components/alert.svelte
+++ /dev/null
@@ -1,268 +0,0 @@
-<script lang="ts">
- import { random_string } from "$lib/helpers";
- 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";
-
- const dispatch = createEventDispatcher();
- const noCooldownSetting = "no-cooldown";
-
- let iconComponent: any;
- let colorClassPart = "";
-
- /**
- * 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.
- export let id = "alert--" + noCooldownSetting + "--" + random_string(4);
- /**
- * The title to communicate, value is optional
- */
- export let title = "";
- /**
- * The message to communicate, value is optional
- */
- export let message = "";
- /**
- * Changes the alerts color and icon.
- */
- export let type: "info" | "success" | "warning" | "error" = "info";
- /**
- * If true the alert can be removed from the DOM by clicking on a X icon on the upper right hand courner
- */
- export let closeable = false;
- /**
- * The amount of seconds that should go by before this alert is shown again, only works when a unique id is set.
- * Set to ~ if it should only be shown once per client (State stored in localestorage).
- **/
- export let closeableCooldown = "-1";
- /**
- * The text that is displayed on the right link
- */
- export let rightLinkText = "";
- /**
- * An array of list items displayed under the message or title
- */
- export let listItems: Array<string> = [];
- /**
- * An array of {id:string;text:string;color?:string}, where id is dispatched back as an svelte event with this syntax act$id (ex: on:actcancel).
- * Text is the button text
- * Color is the optional tailwind color to used, the value is used in classes like bg-$color-50.
- */
- export let actions: Array<{ id: string; text: string; color?: string }> = [];
- /**
- * This value is set on a plain anchor tag without any svelte routing,
- * listen to the on:rightLinkClick if you want to intercept the click without navigating
- */
- export let rightLinkHref = "javascript:void(0)";
- $: cooldownEnabled =
- id.indexOf(noCooldownSetting) === -1 && closeable && (closeableCooldown === "~" || parseInt(closeableCooldown) > 0);
- /**
- * Sets this alerts visibility state, when this is false it is removed from the dom using an {#if} block.
- */
- export let visible = closeableCooldown === "~" || parseInt(closeableCooldown) > 0 ? false : true;
-
- export let _pwKey: string | undefined = undefined;
-
- const cooldownStorageKey = "lastseen--" + id;
-
- $: switch (type) {
- case "info": {
- colorClassPart = "blue";
- iconComponent = InformationCircleIcon;
- break;
- }
- case "warning": {
- colorClassPart = "yellow";
- iconComponent = ExclamationTriangleIcon;
- break;
- }
- case "error": {
- colorClassPart = "red";
- iconComponent = XCircleIcon;
- break;
- }
- case "success": {
- colorClassPart = "green";
- iconComponent = CheckCircleIcon;
- break;
- }
- }
-
- function close() {
- visible = false;
- if (cooldownEnabled) {
- console.log("Cooldown enabled for " + id + ", " + closeableCooldown === "~" ? "with an endless cooldown" : "");
- localStorage.setItem(cooldownStorageKey, String(Temporal.Now.instant().epochSeconds));
- }
- }
-
- function rightLinkClicked() {
- dispatch("rightLinkCliked");
- }
-
- function actionClicked(name: string) {
- dispatch("act" + name);
- }
-
- // Manages the state of the alert if cooldown is enabled
- function run_cooldown() {
- if (!cooldownEnabled) {
- console.log("Alert cooldown is not enabled for " + id);
- return;
- }
- if (!localStorage.getItem(cooldownStorageKey)) {
- console.log("Alert " + id + " has not been seen yet, displaying");
- visible = true;
- return;
- }
- // if (!visible) {
- // console.log(
- // "Alert " + id + " is not visible, stopping cooldown change"
- // );
- // return;
- // }
- if (closeableCooldown === "~") {
- console.log("Alert " + id + " has an infinite cooldown, hiding");
- visible = false;
- return;
- }
-
- const lastSeen = Temporal.Instant.fromEpochSeconds(parseInt(localStorage.getItem(cooldownStorageKey) ?? "-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"
- );
- visible = true;
- } else {
- visible = false;
- }
- }
-
- onMount(() => {
- if (cooldownEnabled) {
- run_cooldown();
- }
-
- if (closeable && closeableCooldown && id.indexOf(noCooldownSetting) !== -1) {
- // TODO: This prints twice before shutting up as it should, in this example look at the only alert with closeableCooldown in alertsbook.
- // Looks like svelte mounts three times and that my id is only set on the third. Not sure it does at all after logging the id onMount.
- console.error("Alert cooldown does not work without specifying a unique id, related id: " + id);
- }
- });
-</script>
-
-{#if visible}
- <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" />
- </div>
- <div class="ml-3 text-sm w-full">
- {#if !rightLinkText}
- {#if title}
- <h3 class="font-medium text-{colorClassPart}-800">
- {title}
- </h3>
- {/if}
- {#if message}
- <div class="{title ? 'mt-2' : ''} text-{colorClassPart}-700 justify-start">
- <p>
- {@html message}
- </p>
- </div>
- {/if}
- {#if listItems?.length ?? 0}
- <ul class="list-disc space-y-1 pl-5 text-{colorClassPart}-700">
- {#each listItems as listItem}
- <li>{listItem}</li>
- {/each}
- </ul>
- {/if}
- {:else}
- <div class="flex-1 md:flex md:justify-between">
- <div>
- {#if title}
- <h3 class="font-medium text-{colorClassPart}-800">
- {title}
- </h3>
- {/if}
- {#if message}
- <div class="{title ? 'mt-2' : ''} text-{colorClassPart}-700 justify-start">
- <p>
- {@html message}
- </p>
- </div>
- {/if}
- {#if listItems?.length ?? 0}
- <ul class="list-disc space-y-1 pl-5 text-{colorClassPart}-700">
- {#each listItems as listItem}
- <li>{listItem}</li>
- {/each}
- </ul>
- {/if}
- </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"
- >
- {rightLinkText}
- <span aria-hidden="true"> &rarr;</span>
- </a>
- </p>
- </div>
- {/if}
- {#if actions?.length ?? 0}
- <div class="ml-2 mt-4">
- <div class="-mx-2 -my-1.5 flex gap-1">
- {#each actions as action}
- {@const color = action?.color ?? colorClassPart}
- <button
- 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
- hover:bg-{color}-100
- focus:outline-none focus:ring-2
- focus:ring-{color}-600
- focus:ring-offset-2
- focus:ring-offset-{color}-50"
- >
- {action.text}
- </button>
- {/each}
- </div>
- </div>
- {/if}
- </div>
- {#if closeable}
- <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"
- >
- <span class="sr-only">Dismiss</span>
- <XMarkIcon />
- </button>
- </div>
- </div>
- {/if}
- </div>
- </div>
-{/if}
diff --git a/code/app/src/lib/components/badge.svelte b/code/app/src/lib/components/badge.svelte
deleted file mode 100644
index 6ec48d5..0000000
--- a/code/app/src/lib/components/badge.svelte
+++ /dev/null
@@ -1,76 +0,0 @@
-<script lang="ts">
- import { createEventDispatcher } from "svelte";
-
- export let id: string | undefined = undefined;
- export let type: "default" | "red" | "yellow" | "green" | "blue" | "tame" = "default";
- export let text: string;
- export let size: "large" | "default" = "default";
- export let withDot: boolean = false;
- export let removable: boolean = false;
- export let uppercase: boolean = false;
- export let tabindex: string | undefined = undefined;
-
- let colorName = "gray";
- let sizeClass = "rounded px-2 py-0.5 text-xs";
- let dotSizeClass = "mr-1.5 h-2 w-2";
-
- const dispatch = createEventDispatcher();
-
- function handle_remove(event) {
- dispatch("remove", { event, id, text });
- }
-
- $: switch (type) {
- case "red":
- colorName = "red";
- break;
- case "yellow":
- colorName = "yellow";
- break;
- case "blue":
- colorName = "blue";
- break;
- case "green":
- colorName = "teal";
- break;
- case "default":
- case "tame":
- default:
- colorName = "gray";
- break;
- }
-
- $: switch (size) {
- case "large":
- sizeClass = "rounded-md px-2.5 py-0.5 text-sm";
- dotSizeClass = "-ml-0.5 mr-1.5 h-2 w-2";
- break;
- case "default":
- default:
- sizeClass = "rounded px-2 py-0.5 text-xs";
- dotSizeClass = "mr-1.5 h-2 w-2";
- break;
- }
-</script>
-
-<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" />
- </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"
- >
- <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" />
- </svg>
- </button>
- {/if}
-</span>
diff --git a/code/app/src/lib/components/button.svelte b/code/app/src/lib/components/button.svelte
deleted file mode 100644
index 49a9354..0000000
--- a/code/app/src/lib/components/button.svelte
+++ /dev/null
@@ -1,115 +0,0 @@
-<script context="module" lang="ts">
- export type ButtonKind = "primary" | "secondary" | "white" | "reset";
- export type ButtonSize = "sm" | "lg" | "md" | "xl";
-</script>
-
-<script lang="ts">
- import pwKey from "$actions/pwKey";
- import { SpinnerIcon } from "./icons";
-
- export let kind = "primary" as ButtonKind;
- export let size = "md" as ButtonSize;
- export let type: "button" | "submit" | "reset" = "button";
- export let id: string | undefined = undefined;
- export let tabindex: string | undefined = undefined;
- export let style: string | undefined = undefined;
- export let title: string | undefined = undefined;
- export let disabled: boolean | null = false;
- export let href: string | undefined = undefined;
- export let text: string;
- export let loading = false;
- export let fullWidth = false;
- export let _pwKey: string | undefined = undefined;
-
- let sizeClasses = "";
- let kindClasses = "";
- let spinnerTextClasses = "";
- let spinnerMarginClasses = "";
-
- $: shared_props = {
- type: type,
- id: id || null,
- title: title || null,
- disabled: disabled || loading || null,
- tabindex: tabindex || null,
- style: style || null,
- } as any;
-
- $: switch (size) {
- case "sm":
- sizeClasses = "px-2.5 py-1.5 text-xs";
- spinnerMarginClasses = "mr-2";
- break;
- case "md":
- sizeClasses = "px-3 py-2 text-sm";
- spinnerMarginClasses = "mr-2";
- break;
- case "lg":
- sizeClasses = "px-3 py-2 text-lg";
- spinnerMarginClasses = "mr-2";
- break;
- case "xl":
- sizeClasses = "px-6 py-3 text-xl";
- spinnerMarginClasses = "mr-2";
- break;
- }
-
- $: switch (kind) {
- case "secondary":
- kindClasses = "border-transparent text-teal-800 bg-teal-100 hover:bg-teal-200 focus:ring-teal-500";
- spinnerTextClasses = "teal-800";
- break;
- case "primary":
- kindClasses = "border-transparent text-teal-900 bg-teal-300 hover:bg-teal-400 focus:ring-teal-500";
- spinnerTextClasses = "text-teal-900";
- break;
- case "white":
- kindClasses = "border-gray-300 text-gray-700 bg-white hover:bg-gray-50 focus:ring-gray-400";
- spinnerTextClasses = "text-gray-700";
- break;
- case "reset":
- kindClasses = "reset outline-none ring-0 focus:ring-0 focus-visible:ring-0";
- break;
- }
-</script>
-
-{#if href}
- <a
- 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} />
- {/if}
- {text}
- </a>
-{:else}
- <button
- 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} />
- {/if}
- {text}
- </button>
-{/if}
-
-<style>
- .reset {
- border: 0px;
- outline: none;
- }
- .reset:focus {
- outline: none;
- }
-</style>
diff --git a/code/app/src/lib/components/checkbox.svelte b/code/app/src/lib/components/checkbox.svelte
deleted file mode 100644
index 12ebedb..0000000
--- a/code/app/src/lib/components/checkbox.svelte
+++ /dev/null
@@ -1,29 +0,0 @@
-<script lang="ts">
- import pwKey from "$actions/pwKey";
- import { random_string } from "$lib/helpers";
-
- export let label: string;
- export let id: string | undefined = "input__" + random_string(4);
- export let name: string | undefined = undefined;
- export let disabled: boolean | null = null;
- export let checked: boolean;
- export let required: boolean | null = null;
- export let _pwKey: string | undefined = undefined;
-</script>
-
-<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"
- />
- <label for={id} class="ml-2 block text-sm text-gray-900">
- {@html required ? "<span class='text-red-500'>*</span>" : ""}
- {label}
- </label>
-</div>
diff --git a/code/app/src/lib/components/combobox.svelte b/code/app/src/lib/components/combobox.svelte
deleted file mode 100644
index 4e7b1cd..0000000
--- a/code/app/src/lib/components/combobox.svelte
+++ /dev/null
@@ -1,450 +0,0 @@
-<script lang="ts" context="module">
- export type ComboboxOption = {
- id: string;
- name: string;
- selected?: boolean;
- };
-</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 Badge from "./badge.svelte";
- import Button from "./button.svelte";
- import LL from "$lib/i18n/i18n-svelte";
-
- export let id = "combobox-" + random_string(3);
- export let label: string | undefined = undefined;
- export let errorText: string | undefined = undefined;
- export let disabled: boolean | undefined = undefined;
- export let required: boolean | undefined = undefined;
- export let maxlength: number | undefined = undefined;
- export let placeholder: string = $LL.combobox.search();
- export let options: Array<ComboboxOption> | undefined = [];
- export let createable = false;
- export let loading = false;
- export let multiple = false;
- export let noResultsText: string = $LL.combobox.noRecordsFound();
- export let on_create_async = async ({ name: string }) => {};
-
- export const reset = () => methods.reset();
- export const select = (id: string) => methods.select_entry(id);
- export const deselect = (id: string) => methods.deselect_entry(id);
-
- const INTERNAL_ID = "INTERNAL__" + id;
-
- let optionsListId = id + "--options";
- let searchInputNode;
- let searchResults: Array<any> = [];
- let searchValue = "";
- let showCreationHint = false;
- let showDropdown = false;
- let inputHasFocus = false;
- let lastKeydownCode = "";
- let mouseIsOverDropdown = false;
- let mouseIsOverComponent = false;
-
- $: ariaErrorDescribedBy = id + "__" + "error";
- $: colorName = errorText ? "red" : "teal";
- $: attributes = {
- "aria-describedby": errorText ? ariaErrorDescribedBy : null,
- "aria-invalid": errorText ? "true" : null,
- disabled: disabled || null,
- required: required || null,
- maxlength: maxlength || null,
- id: id || null,
- placeholder: placeholder || null,
- } as any;
- $: hasSelection = options.some((c) => c.selected === true);
- $: if (searchValue.trim()) {
- showCreationHint = createable && options.every((c) => search.normalise_value(c.name) !== search.normalise_value(searchValue));
- } else {
- showCreationHint = false;
- options = methods.get_sorted_array(options);
- }
-
- function on_select(event) {
- const node = event.target.closest("[data-id]");
- if (!node) return;
- methods.select_entry(node.dataset.id);
- }
-
- const search = {
- normalise_value(value: string): string {
- if (!value) {
- return "";
- }
- return value.trim().toLowerCase();
- },
- do() {
- const query = search.normalise_value(searchValue);
-
- if (!query.trim()) {
- searchResults = [];
- return;
- }
-
- // @ts-ignore
- searchResults = go(query, options, {
- limit: 15,
- allowTypo: true,
- threshold: -10000,
- key: "name",
- });
- showDropdown = true;
- },
- on_input_focus() {
- showDropdown = true;
- inputHasFocus = true;
- },
- on_input_click() {
- showDropdown = true;
- inputHasFocus = true;
- },
- on_input_focusout() {
- inputHasFocus = false;
- if (lastKeydownCode !== "Tab" && (mouseIsOverDropdown || lastKeydownCode === "ArrowDown")) {
- return;
- }
- const selected = options.find((c) => c.selected === true);
- if (selected && !multiple) {
- searchValue = selected.name;
- }
- document.querySelector("#" + INTERNAL_ID + " ul li.focus")?.classList.remove("focus");
- showDropdown = false;
- },
- on_input_wrapper_focus(event) {
- if (event.code && event.code !== "Space" && event.code !== "Enter") return;
- if (!element_has_focus(searchInputNode)) searchInputNode.focus();
- showDropdown = true;
- },
- };
-
- const methods = {
- reset(focus_input = false) {
- searchValue = "";
- const copy = options;
- for (const entry of copy) {
- entry.selected = false;
- }
- options = methods.get_sorted_array(copy);
- if (focus_input) {
- searchInputNode?.focus();
- showDropdown = true;
- } else {
- showDropdown = false;
- }
- },
- async create_entry(name: string) {
- if (!name || !createable || loading) {
- console.log("Not sending creation event due to failed preconditions", { name, createable, loading });
- return;
- }
- try {
- await on_create_async({ name });
- searchValue = "";
- loading = false;
- } catch (e) {
- console.error(e);
- }
- },
- select_entry(entryId: string) {
- if (!entryId || loading) {
- console.log("Not selecting entry due to failed preconditions", {
- entryId,
- loading,
- });
- return;
- }
-
- const copy = options;
- for (const entry of options) {
- if (entry.id === entryId) {
- entry.selected = true;
- if (multiple) {
- searchValue = "";
- } else {
- searchValue = entry.name;
- }
- } else if (!multiple) {
- entry.selected = false;
- }
- }
- options = methods.get_sorted_array(copy);
- searchInputNode?.focus();
- searchResults = [];
- },
- deselect_entry(entryId: string) {
- if (!entryId || loading) {
- console.log("Not deselecting entry due to failed preconditions", {
- entryId,
- loading,
- });
- return;
- }
- console.log("Deselecting entry", entryId);
-
- const copy = options;
-
- for (const entry of copy) {
- if (entry.id === entryId) {
- entry.selected = false;
- }
- }
-
- options = methods.get_sorted_array(copy);
- searchInputNode?.focus();
- },
- get_sorted_array(options: Array<ComboboxOption>): Array<ComboboxOption> {
- if (!options) {
- return;
- }
- if (options.length < 1) {
- return [];
- }
- if (searchValue) {
- return options;
- }
-
- return options.sort((a, b) => search.normalise_value(a.name).localeCompare(search.normalise_value(b.name)));
- },
- };
-
- const windowEvents = {
- on_mousemove(event: any) {
- if (!event.target) return;
- mouseIsOverDropdown = event.target?.closest("#" + INTERNAL_ID + " .tongue") != null ?? false;
- mouseIsOverComponent = event.target?.closest("#" + INTERNAL_ID) != null ?? false;
- },
- on_click() {
- if (showDropdown && !mouseIsOverDropdown && !mouseIsOverComponent) {
- showDropdown = false;
- }
- },
- on_keydown(event: any) {
- lastKeydownCode = event.code;
- const enterPressed = event.code === "Enter";
- const backspacePressed = event.code === "Backspace";
- const arrowUpPressed = event.code === "ArrowUp";
- const spacePressed = event.code === "Space";
- const arrowDownPressed = event.code === "ArrowDown";
- const searchInputHasFocus = element_has_focus(searchInputNode);
- const focusedEntry = document.querySelector("#" + INTERNAL_ID + " ul li.focus") as HTMLLIElement;
-
- if (showDropdown && (enterPressed || arrowDownPressed || arrowUpPressed)) {
- event.preventDefault();
- }
-
- if (searchInputHasFocus && backspacePressed && !searchValue && options.length > 0) {
- if (options.filter((c) => c.selected === true).at(-1)?.id ?? false) {
- methods.deselect_entry(options.filter((c) => c.selected === true).at(-1)?.id ?? "");
- }
- return;
- }
-
- if (searchInputHasFocus && enterPressed && showCreationHint) {
- methods.create_entry(searchValue.trim());
- return;
- }
-
- if (searchInputHasFocus && !focusedEntry && arrowDownPressed) {
- const firstEntry = document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type");
- if (firstEntry) {
- firstEntry.classList.add("focus");
- return;
- }
- }
-
- if (focusedEntry && (arrowUpPressed || arrowDownPressed)) {
- if (arrowDownPressed) {
- if (focusedEntry.nextElementSibling) {
- focusedEntry.nextElementSibling.classList.add("focus");
- focusedEntry.nextElementSibling.scrollIntoView(false);
- } else {
- const firstLIEl = document.querySelector("#" + INTERNAL_ID + " ul li:first-of-type");
- firstLIEl.classList.add("focus");
- firstLIEl.scrollIntoView(false);
- }
- } else if (arrowUpPressed) {
- if (focusedEntry.previousElementSibling) {
- focusedEntry.previousElementSibling.classList.add("focus");
- focusedEntry.previousElementSibling.scrollIntoView(false);
- } else {
- const lastLIEl = document.querySelector("#" + INTERNAL_ID + " ul li:last-of-type");
- lastLIEl.classList.add("focus");
- lastLIEl.scrollIntoView(false);
- }
- }
- focusedEntry.classList.remove("focus");
- return;
- }
-
- if (focusedEntry && (spacePressed || enterPressed)) {
- methods.select_entry(focusedEntry.dataset.id);
- return;
- }
-
- if (lastKeydownCode === "Tab" && !searchInputHasFocus) {
- showDropdown = false;
- }
- },
- on_touchend(event) {
- windowEvents.on_mousemove(event);
- },
- };
-</script>
-
-<svelte:window
- 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}>
- {#if label}
- <label for={id} class="block text-sm font-medium text-gray-700">
- {label}
- {@html required ? "<span class='text-red-500'>*</span>" : ""}
- </label>
- {/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
- {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}
- />
- {/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"
- />
- {#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"
- >
- <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>
- {/if}
- </div>
- </div>
- {#if errorText}
- <p class="mt-2 text-sm text-red-600" id={ariaErrorDescribedBy}>
- {errorText}
- </p>
- {/if}
- <div
- 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"
- >
- <ul id={optionsListId} role="listbox" tabindex="-1">
- {#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"
- >
- {@html highlight(result, '<span class="font-bold">', "</span>")}
- </li>
- {/each}
- {:else if options.length > 0}
- {#each options as option}
- <!--
- Combobox option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.
- 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"
- >
- <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 />
- </span>
- {/if}
- </li>
- {/each}
- {:else}
- <slot name="no-records">
- <p class="px-2">{noResultsText}</p>
- {#if createable && !searchValue}
- <p class="px-2 text-gray-500">{$LL.combobox.createRecordHelpText()}</p>
- {/if}
- </slot>
- {/if}
- </ul>
- {#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())}
- />
- </div>
- {/if}
- </div>
- </div>
-</div>
-
-<style lang="postcss">
- .focus {
- @apply text-white bg-teal-300;
- }
-
- .item {
- @apply relative cursor-pointer select-none py-2 pl-3 pr-9 text-gray-900;
- }
-
- .item[aria-selected="true"] {
- @apply bg-teal-200;
- }
-</style>
diff --git a/code/app/src/lib/components/icons/adjustments.svelte b/code/app/src/lib/components/icons/adjustments.svelte
deleted file mode 100644
index 83bda27..0000000
--- a/code/app/src/lib/components/icons/adjustments.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- class="h-6 w-6 {$$restProps.class ?? ''}"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- stroke-width="2"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/bars-3-center-left.svelte b/code/app/src/lib/components/icons/bars-3-center-left.svelte
deleted file mode 100644
index 785ece3..0000000
--- a/code/app/src/lib/components/icons/bars-3-center-left.svelte
+++ /dev/null
@@ -1,15 +0,0 @@
-<svg
- class="h-6 w-6 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke-width="1.5"
- stroke="currentColor"
- aria-hidden="true"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M3.75 6.75h16.5M3.75 12H12m-8.25 5.25h16.5"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/calendar.svelte b/code/app/src/lib/components/icons/calendar.svelte
deleted file mode 100644
index e0053ee..0000000
--- a/code/app/src/lib/components/icons/calendar.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke-width="1.5"
- stroke="currentColor"
- class="w-6 h-6 {$$restProps.class ?? ''}"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5m-9-6h.008v.008H12v-.008zM12 15h.008v.008H12V15zm0 2.25h.008v.008H12v-.008zM9.75 15h.008v.008H9.75V15zm0 2.25h.008v.008H9.75v-.008zM7.5 15h.008v.008H7.5V15zm0 2.25h.008v.008H7.5v-.008zm6.75-4.5h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V15zm0 2.25h.008v.008h-.008v-.008zm2.25-4.5h.008v.008H16.5v-.008zm0 2.25h.008v.008H16.5V15z"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/check-circle.svelte b/code/app/src/lib/components/icons/check-circle.svelte
deleted file mode 100644
index e30778e..0000000
--- a/code/app/src/lib/components/icons/check-circle.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- fill-rule="evenodd"
- d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/chevron-down.svelte b/code/app/src/lib/components/icons/chevron-down.svelte
deleted file mode 100644
index 5b29ece..0000000
--- a/code/app/src/lib/components/icons/chevron-down.svelte
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg class="h-5 w-5 {$$restProps.class ?? ''}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
- <path
- fill-rule="evenodd"
- d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/chevron-up-down.svelte b/code/app/src/lib/components/icons/chevron-up-down.svelte
deleted file mode 100644
index c07aed5..0000000
--- a/code/app/src/lib/components/icons/chevron-up-down.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- fill-rule="evenodd"
- d="M10 3a.75.75 0 01.55.24l3.25 3.5a.75.75 0 11-1.1 1.02L10 4.852 7.3 7.76a.75.75 0 01-1.1-1.02l3.25-3.5A.75.75 0 0110 3zm-3.76 9.2a.75.75 0 011.06.04l2.7 2.908 2.7-2.908a.75.75 0 111.1 1.02l-3.25 3.5a.75.75 0 01-1.1 0l-3.25-3.5a.75.75 0 01.04-1.06z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/chevron-up.svelte b/code/app/src/lib/components/icons/chevron-up.svelte
deleted file mode 100644
index 289e71d..0000000
--- a/code/app/src/lib/components/icons/chevron-up.svelte
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
- <path
- fill-rule="evenodd"
- d="M14.77 12.79a.75.75 0 01-1.06-.02L10 8.832 6.29 12.77a.75.75 0 11-1.08-1.04l4.25-4.5a.75.75 0 011.08 0l4.25 4.5a.75.75 0 01-.02 1.06z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/database.svelte b/code/app/src/lib/components/icons/database.svelte
deleted file mode 100644
index 6ffdadb..0000000
--- a/code/app/src/lib/components/icons/database.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- class="h-6 w-6 {$$restProps.class ?? ''}"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- stroke-width="2"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/exclamation-circle.svelte b/code/app/src/lib/components/icons/exclamation-circle.svelte
deleted file mode 100644
index 2ce79b1..0000000
--- a/code/app/src/lib/components/icons/exclamation-circle.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- fill-rule="evenodd"
- d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/exclamation-triangle.svelte b/code/app/src/lib/components/icons/exclamation-triangle.svelte
deleted file mode 100644
index 8d807db..0000000
--- a/code/app/src/lib/components/icons/exclamation-triangle.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- fill-rule="evenodd"
- d="M8.485 3.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 3.495zM10 6a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 6zm0 9a1 1 0 100-2 1 1 0 000 2z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/folder-open.svelte b/code/app/src/lib/components/icons/folder-open.svelte
deleted file mode 100644
index 409c8e2..0000000
--- a/code/app/src/lib/components/icons/folder-open.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke-width="1.5"
- stroke="currentColor"
- class="w-6 h-6 {$$restProps.class ?? ''}"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/funnel.svelte b/code/app/src/lib/components/icons/funnel.svelte
deleted file mode 100644
index 7e9daeb..0000000
--- a/code/app/src/lib/components/icons/funnel.svelte
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5">
- <path
- fill-rule="evenodd"
- d="M2.628 1.601C5.028 1.206 7.49 1 10 1s4.973.206 7.372.601a.75.75 0 01.628.74v2.288a2.25 2.25 0 01-.659 1.59l-4.682 4.683a2.25 2.25 0 00-.659 1.59v3.037c0 .684-.31 1.33-.844 1.757l-1.937 1.55A.75.75 0 018 18.25v-5.757a2.25 2.25 0 00-.659-1.591L2.659 6.22A2.25 2.25 0 012 4.629V2.34a.75.75 0 01.628-.74z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/home.svelte b/code/app/src/lib/components/icons/home.svelte
deleted file mode 100644
index ee8305d..0000000
--- a/code/app/src/lib/components/icons/home.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- class="h-6 w-6 {$$restProps.class ?? ''}"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- stroke-width="2"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/index.ts b/code/app/src/lib/components/icons/index.ts
deleted file mode 100644
index eb5b439..0000000
--- a/code/app/src/lib/components/icons/index.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-import XIcon from "./x.svelte";
-import MenuIcon from "./menu.svelte";
-import AdjustmentsIcon from "./adjustments.svelte";
-import DatabaseIcon from "./database.svelte";
-import HomeIcon from "./home.svelte";
-import InformationCircleIcon from "./information-circle.svelte";
-import ExclamationTriangleIcon from "./exclamation-triangle.svelte";
-import XCircleIcon from "./x-circle.svelte";
-import CheckCircleIcon from "./check-circle.svelte";
-import XMarkIcon from "./x-mark.svelte";
-import SpinnerIcon from "./spinner.svelte";
-import ExclamationCircleIcon from "./exclamation-circle.svelte";
-import ChevronUpDownIcon from "./chevron-up-down.svelte";
-import MagnifyingGlassIcon from "./magnifying-glass.svelte";
-import Bars3CenterLeftIcon from "./bars-3-center-left.svelte";
-import CalendarIcon from "./calendar.svelte";
-import FolderOpenIcon from "./folder-open.svelte";
-import MegaphoneIcon from "./megaphone.svelte";
-import QueueListIcon from "./queue-list.svelte";
-import ChevronDownIcon from "./chevron-down.svelte";
-import ChevronUpIcon from "./chevron-up.svelte";
-import FunnelIcon from "./funnel.svelte";
-
-export {
- FunnelIcon,
- ChevronDownIcon,
- ChevronUpIcon,
- QueueListIcon,
- FolderOpenIcon,
- MegaphoneIcon,
- CalendarIcon,
- Bars3CenterLeftIcon,
- MagnifyingGlassIcon,
- ChevronUpDownIcon,
- XIcon,
- MenuIcon,
- HomeIcon,
- DatabaseIcon,
- AdjustmentsIcon,
- InformationCircleIcon,
- ExclamationTriangleIcon,
- ExclamationCircleIcon,
- XCircleIcon,
- CheckCircleIcon,
- XMarkIcon,
- SpinnerIcon
-} \ No newline at end of file
diff --git a/code/app/src/lib/components/icons/information-circle.svelte b/code/app/src/lib/components/icons/information-circle.svelte
deleted file mode 100644
index 68dbc60..0000000
--- a/code/app/src/lib/components/icons/information-circle.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- fill-rule="evenodd"
- d="M19 10.5a8.5 8.5 0 11-17 0 8.5 8.5 0 0117 0zM8.25 9.75A.75.75 0 019 9h.253a1.75 1.75 0 011.709 2.13l-.46 2.066a.25.25 0 00.245.304H11a.75.75 0 010 1.5h-.253a1.75 1.75 0 01-1.709-2.13l.46-2.066a.25.25 0 00-.245-.304H9a.75.75 0 01-.75-.75zM10 7a1 1 0 100-2 1 1 0 000 2z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/magnifying-glass.svelte b/code/app/src/lib/components/icons/magnifying-glass.svelte
deleted file mode 100644
index f8fdb6e..0000000
--- a/code/app/src/lib/components/icons/magnifying-glass.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- fill-rule="evenodd"
- d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/megaphone.svelte b/code/app/src/lib/components/icons/megaphone.svelte
deleted file mode 100644
index 7ada5f3..0000000
--- a/code/app/src/lib/components/icons/megaphone.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke-width="1.5"
- stroke="currentColor"
- class="w-6 h-6 {$$restProps.class ?? ''}"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 110-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 01-1.44-4.282m3.102.069a18.03 18.03 0 01-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 018.835 2.535M10.34 6.66a23.847 23.847 0 008.835-2.535m0 0A23.74 23.74 0 0018.795 3m.38 1.125a23.91 23.91 0 011.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 001.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 010 3.46"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/menu.svelte b/code/app/src/lib/components/icons/menu.svelte
deleted file mode 100644
index 471d85f..0000000
--- a/code/app/src/lib/components/icons/menu.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- class="h-6 w-6 {$$restProps.class ?? ''}"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- stroke-width="2"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M4 6h16M4 12h16M4 18h16"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/queue-list.svelte b/code/app/src/lib/components/icons/queue-list.svelte
deleted file mode 100644
index 6148394..0000000
--- a/code/app/src/lib/components/icons/queue-list.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
- stroke-width="1.5"
- stroke="currentColor"
- class="w-6 h-6 {$$restProps.class ?? ''}"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M3.75 12h16.5m-16.5 3.75h16.5M3.75 19.5h16.5M5.625 4.5h12.75a1.875 1.875 0 010 3.75H5.625a1.875 1.875 0 010-3.75z"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/spinner.svelte b/code/app/src/lib/components/icons/spinner.svelte
deleted file mode 100644
index 80cc57c..0000000
--- a/code/app/src/lib/components/icons/spinner.svelte
+++ /dev/null
@@ -1,20 +0,0 @@
-<svg
- class="-ml-1 mr-3 h-5 w-5 animate-spin {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- fill="none"
- viewBox="0 0 24 24"
->
- <circle
- class="opacity-25"
- cx="12"
- cy="12"
- r="10"
- stroke="currentColor"
- stroke-width="4"
- />
- <path
- class="opacity-75"
- fill="currentColor"
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/x-circle.svelte b/code/app/src/lib/components/icons/x-circle.svelte
deleted file mode 100644
index 3793b5a..0000000
--- a/code/app/src/lib/components/icons/x-circle.svelte
+++ /dev/null
@@ -1,13 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- fill-rule="evenodd"
- d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
- clip-rule="evenodd"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/x-mark.svelte b/code/app/src/lib/components/icons/x-mark.svelte
deleted file mode 100644
index fd1c6a1..0000000
--- a/code/app/src/lib/components/icons/x-mark.svelte
+++ /dev/null
@@ -1,11 +0,0 @@
-<svg
- class="h-5 w-5 {$$restProps.class ?? ''}"
- xmlns="http://www.w3.org/2000/svg"
- viewBox="0 0 20 20"
- fill="currentColor"
- aria-hidden="true"
->
- <path
- d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
- />
-</svg>
diff --git a/code/app/src/lib/components/icons/x.svelte b/code/app/src/lib/components/icons/x.svelte
deleted file mode 100644
index 6125ab8..0000000
--- a/code/app/src/lib/components/icons/x.svelte
+++ /dev/null
@@ -1,14 +0,0 @@
-<svg
- xmlns="http://www.w3.org/2000/svg"
- class="h-6 w-6 {$$restProps.class ?? ''}"
- fill="none"
- viewBox="0 0 24 24"
- stroke="currentColor"
- stroke-width="2"
->
- <path
- stroke-linecap="round"
- stroke-linejoin="round"
- d="M6 18L18 6M6 6l12 12"
- />
-</svg>
diff --git a/code/app/src/lib/components/index.ts b/code/app/src/lib/components/index.ts
deleted file mode 100644
index d6abd4c..0000000
--- a/code/app/src/lib/components/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import Alert from "./alert.svelte";
-import Button from "./button.svelte";
-import Checkbox from "./checkbox.svelte";
-import Input from "./input.svelte";
-import LocaleSwitcher from "./locale-switcher.svelte";
-import Switch from "./switch.svelte";
-import Badge from "./badge.svelte";
-import ProjectStatusBadge from "./project-status-badge.svelte";
-import TextArea from "./textarea.svelte";
-import Combobox from "./combobox.svelte";
-
-export {
- Badge,
- Combobox,
- TextArea,
- ProjectStatusBadge,
- Alert,
- Button,
- Checkbox,
- Input,
- LocaleSwitcher,
- Switch
-} \ No newline at end of file
diff --git a/code/app/src/lib/components/input.svelte b/code/app/src/lib/components/input.svelte
deleted file mode 100644
index 80b1543..0000000
--- a/code/app/src/lib/components/input.svelte
+++ /dev/null
@@ -1,113 +0,0 @@
-<script lang="ts">
- import pwKey from "$actions/pwKey";
- import { random_string } from "$lib/helpers";
- import { error } from "@sveltejs/kit";
- import { ExclamationCircleIcon } from "./icons";
-
- export let label: string | undefined = undefined;
- export let type: string = "text";
- export let autocomplete: string | undefined = undefined;
- export let required: boolean | undefined = undefined;
- export let id: string | undefined = "input__" + random_string(4);
- export let name: string | undefined = undefined;
- export let placeholder: string | undefined = undefined;
- export let helpText: string | undefined = undefined;
- export let errorText: string | undefined = undefined;
- export let errors: Array<string> | undefined = undefined;
- export let disabled = false;
- export let hideLabel = false;
- export let cornerHint: string | undefined = undefined;
- export let icon: any = undefined;
- export let addon: string | undefined = undefined;
- export let value: string | undefined;
- export let wrapperClass: string | undefined = undefined;
- export let _pwKey: string | undefined = undefined;
-
- $: ariaErrorDescribedBy = id + "__" + "error";
- $: attributes = {
- "aria-describedby": errorText || errors?.length ? ariaErrorDescribedBy : null,
- "aria-invalid": errorText || errors?.length ? "true" : null,
- disabled: disabled || null,
- autocomplete: autocomplete || null,
- required: required || null,
- } as any;
- $: hasBling = icon || addon || errorText;
- const defaultColorClass = "border-gray-300 focus:border-teal-500 focus:ring-teal-500";
- let colorClass = defaultColorClass;
- $: if (errorText) {
- colorClass = "placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500 text-red-900 pr-10 border-red-300";
- } else {
- colorClass = defaultColorClass;
- }
-
- function typeAction(node: HTMLInputElement) {
- node.type = type;
- }
-</script>
-
-<div class={wrapperClass}>
- {#if label && !cornerHint && !hideLabel}
- <label for={id} class={hideLabel ? "sr-only" : "block text-sm font-medium text-gray-700"}>
- {label}
- {@html required ? "<span class='text-red-500'>*</span>" : ""}
- </label>
- {:else if cornerHint && !hideLabel}
- <div class="flex justify-between">
- {#if label}
- <label for={id} class={hideLabel ? "sr-only" : "block text-sm font-medium text-gray-700"}>
- {label}
- {@html required ? "<span class='text-red-500'>*</span>" : ""}
- </label>
- {/if}
- <span class="text-sm text-gray-500">
- {cornerHint}
- </span>
- </div>
- {/if}
- <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"} />
- </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">
- <span class="text-gray-500 sm:text-sm">{addon}</span>
- </div>
- {/if}
- <input
- 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}
- />
- {#if errorText}
- <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3">
- <ExclamationCircleIcon class="text-red-500" />
- </div>
- {/if}
- </div>
- {#if helpText && !errorText}
- <p class="mt-2 text-sm text-gray-500">
- {helpText}
- </p>
- {/if}
- {#if errorText || errors?.length === 1}
- <p class="mt-2 text-sm text-red-600" id={ariaErrorDescribedBy}>
- {errorText ?? errors[0]}
- </p>
- {:else if errors && errors.length}
- <ul class="mt-2 list-disc" id={ariaErrorDescribedBy}>
- {#each errors as error}
- <li class="text-sm text-red-600">{error}</li>
- {/each}
- </ul>
- {/if}
-</div>
diff --git a/code/app/src/lib/components/locale-switcher.svelte b/code/app/src/lib/components/locale-switcher.svelte
deleted file mode 100644
index 3681bf5..0000000
--- a/code/app/src/lib/components/locale-switcher.svelte
+++ /dev/null
@@ -1,56 +0,0 @@
-<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 Cookies from "js-cookie";
-
- export let _pwKey: string | undefined = undefined;
- export let tabindex: number | undefined = undefined;
- let currentLocale = Cookies.get(CookieNames.locale);
-
- async function switch_locale(newLocale: Locales) {
- if (!newLocale || $locale === newLocale) return;
- await loadLocaleAsync(newLocale);
- setLocale(newLocale);
- document.querySelector("html")?.setAttribute("lang", newLocale);
- Cookies.set(CookieNames.locale, newLocale);
- currentLocale = newLocale;
- console.log("Switched to: " + newLocale);
- }
-
- function on_change(event: Event) {
- const target = event.target as HTMLSelectElement;
- switch_locale(target.options[target.selectedIndex].value as Locales);
- }
-
- $: if (browser) {
- switch_locale($page.params.lang as Locales);
- }
-
- function get_locale_name(iso: string) {
- switch (iso) {
- case "nb": {
- return "Norsk Bokmål";
- }
- case "en": {
- return "English";
- }
- }
- }
-</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"
->
- {#each locales as aLocale}
- <option value={aLocale} selected={aLocale === currentLocale}>{get_locale_name(aLocale)}</option>
- {/each}
-</select>
diff --git a/code/app/src/lib/components/project-status-badge.svelte b/code/app/src/lib/components/project-status-badge.svelte
deleted file mode 100644
index 5390344..0000000
--- a/code/app/src/lib/components/project-status-badge.svelte
+++ /dev/null
@@ -1,24 +0,0 @@
-<script lang="ts">
- import type { ProjectStatus } from "$lib/models/projects/ProjectStatus";
- import Badge from "./badge.svelte";
- export let status: string | ProjectStatus;
-
- let text = "";
- let type = "default" as any;
- $: switch (status) {
- case "idl":
- type = "tame";
- text = "IDLE";
- break;
- case "exp":
- type = "yellow";
- text = "EXPIRED";
- break;
- case "act":
- type = "green";
- text = "ACTIVE";
- break;
- }
-</script>
-
-<Badge {text} {type} uppercase />
diff --git a/code/app/src/lib/components/switch.svelte b/code/app/src/lib/components/switch.svelte
deleted file mode 100644
index 79f2d67..0000000
--- a/code/app/src/lib/components/switch.svelte
+++ /dev/null
@@ -1,125 +0,0 @@
-<script context="module" lang="ts">
- export type SwitchType = "short" | "icon" | "default";
-</script>
-
-<script lang="ts">
- import pwKey from "$actions/pwKey";
-
- export let enabled = false;
- export let type: SwitchType = "default";
- export let srText = "Use setting";
- export let label: string | undefined = undefined;
- export let description: string | undefined = undefined;
- export let rightAlignedLabelDescription = false;
- export let _pwKey: string | undefined = undefined;
-
- $: colorClass = enabled ? "bg-teal-600 focus:ring-teal-500" : "bg-gray-200 focus:ring-teal-500";
- $: translateClass = enabled ? "translate-x-5" : "translate-x-0";
- $: hasLabelOrDescription = label || description;
-
- function toggle() {
- enabled = !enabled;
- }
-</script>
-
-<div class="{hasLabelOrDescription ? 'flex items-center' : ''} {rightAlignedLabelDescription ? '' : 'justify-between'}">
- {#if hasLabelOrDescription && !rightAlignedLabelDescription}
- <span class="flex flex-grow flex-col">
- {#if label}
- <span class="text-sm font-medium text-gray-900">{label}</span>
- {/if}
- {#if description}
- <span class="text-sm text-gray-500">{description}</span>
- {/if}
- </span>
- {/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}
- >
- <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="{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"
- />
- </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}
- >
- <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"
- >
- <span
- 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"
- >
- <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"
- />
- </svg>
- </span>
- <span
- 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"
- >
- <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"
- />
- </svg>
- </span>
- </span>
- </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}
- >
- <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"
- />
- </button>
- {/if}
- {#if hasLabelOrDescription && rightAlignedLabelDescription}
- <span class="ml-3">
- {#if label}
- <span class="text-sm font-medium text-gray-900">{label}</span>
- {/if}
- {#if description}
- <span class="text-sm text-gray-500">{description}</span>
- {/if}
- </span>
- {/if}
-</div>
diff --git a/code/app/src/lib/components/textarea.svelte b/code/app/src/lib/components/textarea.svelte
deleted file mode 100644
index a3dd06a..0000000
--- a/code/app/src/lib/components/textarea.svelte
+++ /dev/null
@@ -1,81 +0,0 @@
-<script lang="ts">
- import { random_string } from "$lib/helpers";
-
- export let id = "textarea-" + random_string(4);
- export let disabled = false;
- export let rows = 2;
- export let cols = 0;
- export let name = "";
- export let placeholder = "";
- export let value;
- export let label = "";
- export let required = false;
- export let errorText = "";
- export let errors: Array<string> | undefined = undefined;
-
- $: ariaErrorDescribedBy = id + "__" + "error";
- $: attributes = {
- "aria-describedby": errorText || errors?.length ? ariaErrorDescribedBy : null,
- "aria-invalid": errorText || errors?.length ? "true" : null,
- rows: rows || null,
- cols: cols || null,
- name: name || null,
- id: id || null,
- disabled: disabled || null,
- required: required || null,
- } as any;
-
- let textareaElement;
- let scrollHeight = 0;
- const defaultColorClass = "border-gray-300 focus:border-teal-500 focus:ring-teal-500";
- let colorClass = defaultColorClass;
-
- $: if (errorText) {
- colorClass = "placeholder-red-300 focus:border-red-500 focus:outline-none focus:ring-red-500 text-red-900 pr-10 border-red-300";
- } else {
- colorClass = defaultColorClass;
- }
-
- $: if (textareaElement) {
- scrollHeight = textareaElement.scrollHeight;
- }
-
- function on_input(event) {
- event.target.style.height = "auto";
- event.target.style.height = this.scrollHeight + "px";
- }
-</script>
-
-<div>
- {#if label}
- <label for={id} class="block text-sm font-medium text-gray-700">
- {label}
- {@html required ? "<span class='text-red-500'>*</span>" : ""}
- </label>
- {/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"
- />
- {#if errorText || errors?.length === 1}
- <p class="mt-2 text-sm text-red-600" id={ariaErrorDescribedBy}>
- {errorText ?? errors[0]}
- </p>
- {:else if errors && errors.length}
- <ul class="mt-2 list-disc" id={ariaErrorDescribedBy}>
- {#each errors as error}
- <li class="text-sm text-red-600">{error}</li>
- {/each}
- </ul>
- {/if}
- </div>
-</div>
diff --git a/code/app/src/lib/configuration.ts b/code/app/src/lib/configuration.ts
deleted file mode 100644
index 8debef6..0000000
--- a/code/app/src/lib/configuration.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-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 {
- return (is_development() ? DEV_API_ADDRESS : API_ADDRESS) + (path !== "" ? "/" + path : "");
-}
-
-export function is_development(): boolean {
- return import.meta.env.DEV;
-}
-
-export function is_testing(): boolean {
- return import.meta.env.VITE_TESTING;
-}
-
-export function is_debug(): boolean {
- return localStorage.getItem(StorageKeys.debug) !== "true";
-}
-
-export const CookieNames = {
- theme: "go_theme",
- locale: "go_locale",
- 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
- }
- }
-}
-
-export interface TestContext {
- user: {
- username: string,
- password: string
- }
-}
-
-export const QueryKeys = {
- labels: "labels",
- categories: "categories",
- entries: "entries",
-};
-
-export const StorageKeys = {
- session: "sessionData",
- theme: "theme",
- debug: "debug",
- categories: "categories",
- labels: "labels",
- entries: "entries",
- stopwatch: "stopwatchState",
- logLevel: "logLevel"
-}; \ No newline at end of file
diff --git a/code/app/src/lib/helpers.ts b/code/app/src/lib/helpers.ts
deleted file mode 100644
index 4584de7..0000000
--- a/code/app/src/lib/helpers.ts
+++ /dev/null
@@ -1,464 +0,0 @@
-import { browser } from "$app/environment";
-import type { WorkEntry } from "$lib/models/work/WorkEntry";
-import { log_info } from "$lib/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);
-export const GUID_REGEX = new RegExp(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
-export const NORWEGIAN_PHONE_NUMBER_REGEX = new RegExp(/(0047|\+47|47)?\d{8,12}/);
-
-export function get_default_sorted(unsorted: Array<WorkEntry>): Array<WorkEntry> {
- if (unsorted.length < 1) return unsorted;
- const byStart = unsorted.sort((a, b) => {
- return Temporal.Instant.compare(Temporal.Instant.from(b.start), Temporal.Instant.from(a.start));
- });
-
- return byStart.sort((a, b) => {
- return Temporal.Instant.compare(Temporal.Instant.from(b.stop), Temporal.Instant.from(a.stop));
- });
-}
-
-export function get_element_by_pw_key(key: string): HTMLElement | null {
- return document.querySelector("[pw-key='" + key + "']");
-}
-
-export function get_pw_key_selector(key: string): string {
- return "[pw-key='" + key + "']";
-}
-
-export function is_email(value: string): boolean {
- return EMAIL_REGEX.test(String(value).toLowerCase());
-}
-
-export function is_url(value: string): boolean {
- return URL_REGEX.test(String(value).toLowerCase());
-}
-
-export function is_norwegian_phone_number(value: string): boolean {
- if (value.length < 8 || value.length > 12) {
- return false;
- }
- return NORWEGIAN_PHONE_NUMBER_REGEX.test(String(value));
-}
-
-export function is_guid(value: string): boolean {
- if (!value) {
- return false;
- }
- if (value[0] === "{") {
- value = value.substring(1, value.length - 1);
- }
- return GUID_REGEX.test(value);
-}
-
-export function is_empty_object(obj: object): boolean {
- return obj !== void 0 && Object.keys(obj).length > 0;
-}
-
-export function merge_obj_arr<T>(a: Array<T>, b: Array<T>, props: Array<string>): Array<T> {
- let start = 0;
- let merge = [];
-
- while (start < a.length) {
-
- if (a[start] === b[start]) {
- //pushing the merged objects into array
- merge.push({ ...a[start], ...b[start] });
- }
- //incrementing start value
- start = start + 1;
- }
- return merge;
-}
-
-export function set_favicon(url: string) {
- // Find the current favicon element
- const favicon = document.querySelector("link[rel=\"icon\"]") as HTMLLinkElement;
- if (favicon) {
- // Update the new link
- favicon.href = url;
- } else {
- // Create new `link`
- const link = document.createElement("link");
- link.rel = "icon";
- link.href = url;
-
- // Append to the `head` element
- 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);
-}
-
-export function set_emoji_favicon(emoji: string) {
- // Create a canvas element
- const canvas = document.createElement("canvas");
- canvas.height = 64;
- canvas.width = 64;
-
- // Get the canvas context
- const context = canvas.getContext("2d") as CanvasRenderingContext2D;
- context.font = "64px serif";
- context.fillText(emoji, 0, 64);
-
- // Get the custom URL
- const url = canvas.toDataURL();
-
- // Update the favicon
- set_favicon(url);
-}
-
-
-// https://stackoverflow.com/a/48400665/11961742
-export function seconds_to_hour_minute_string(seconds: number, hourChar = "h", minuteChar = "m") {
- const hours = Math.floor(seconds / (60 * 60));
- seconds -= hours * (60 * 60);
- const minutes = Math.floor(seconds / 60);
- return hours + "h" + minutes + "m";
-}
-
-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 };
-}
-
-export function get_query_string(params: any = {}): string {
- const map = Object.keys(params).reduce((arr: Array<string>, key: string) => {
- if (params[key] !== undefined) {
- return arr.concat(`${key}=${encodeURIComponent(params[key])}`);
- }
- return arr;
- }, [] as any);
-
- if (map.length) {
- return `?${map.join("&")}`;
- }
-
- return "";
-}
-
-export function make_url(url: string, params: object): string {
- return `${url}${get_query_string(params)}`;
-}
-
-export function noop() {
-}
-
-export async function run_async(functionToRun: Function): Promise<any> {
- return new Promise((greatSuccess, graveFailure) => {
- try {
- greatSuccess(functionToRun());
- } catch (exception) {
- graveFailure(exception);
- }
- });
-}
-
-// https://stackoverflow.com/a/45215694/11961742
-export function get_selected_options(domElement: HTMLSelectElement): Array<string> {
- const ret = [];
-
- // fast but not universally supported
- if (domElement.selectedOptions !== undefined) {
- for (let i = 0; i < domElement.selectedOptions.length; i++) {
- ret.push(domElement.selectedOptions[i].value);
- }
-
- // compatible, but can be painfully slow
- } else {
- for (let i = 0; i < domElement.options.length; i++) {
- if (domElement.options[i].selected) {
- ret.push(domElement.options[i].value);
- }
- }
- }
- return ret;
-}
-
-export function random_string(length: number): string {
- if (!length) {
- throw new Error("length is undefined");
- }
- let result = "";
- const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
- const charactersLength = characters.length;
- for (let i = 0; i < length; i++) {
- result += characters.charAt(Math.floor(Math.random() * charactersLength));
- }
- return result;
-}
-
-interface CreateElementOptions {
- name: string,
- properties?: object,
- children?: Array<HTMLElement | Function | Node>
-}
-
-export function create_element_from_object(elementOptions: CreateElementOptions): HTMLElement {
- return create_element(elementOptions.name, elementOptions.properties, elementOptions.children);
-}
-
-export function create_element(name: string, properties?: object, children?: Array<HTMLElement | any>): HTMLElement {
- if (!name || name.length < 1) {
- throw new Error("name is required");
- }
- const node = document.createElement(name);
- if (properties) {
- for (const [key, value] of Object.entries(properties)) {
- // @ts-ignore
- node[key] = value;
- }
- }
-
- if (children && children.length > 0) {
- let actualChildren = children;
- if (typeof children === "function") {
- // @ts-ignore
- actualChildren = children();
- }
- for (const child of actualChildren) {
- node.appendChild(child as Node);
- }
- }
- return node;
-}
-
-export function get_element_position(element: HTMLElement | any) {
- if (!element) return { x: 0, y: 0 };
- let x = 0;
- let y = 0;
- while (true) {
- x += element.offsetLeft;
- y += element.offsetTop;
- if (element.offsetParent === null) {
- break;
- }
- element = element.offsetParent;
- }
- return { x, y };
-}
-
-export function restrict_input_to_numbers(element: HTMLElement, specials: Array<string> = [], mergeSpecialsWithDefaults: boolean = false): void {
- if (element) {
- element.addEventListener("keydown", (e) => {
- const defaultSpecials = ["Backspace", "ArrowLeft", "ArrowRight", "Tab"];
- let keys = specials.length > 0 ? specials : defaultSpecials;
- if (mergeSpecialsWithDefaults && specials) {
- keys = [...specials, ...defaultSpecials];
- }
- if (keys.indexOf(e.key) !== -1) {
- return;
- }
- if (isNaN(parseInt(e.key))) {
- e.preventDefault();
- }
- });
- }
-}
-
-export function element_has_focus(element: HTMLElement): boolean {
- return element === document.activeElement;
-}
-
-export function move_focus(element: HTMLElement): void {
- if (!element) {
- element = document.getElementsByTagName("body")[0];
- }
- element.focus();
- // @ts-ignore
- if (!element_has_focus(element)) {
- element.setAttribute("tabindex", "-1");
- element.focus();
- }
-}
-
-export function get_url_parameter(name: string): string {
- // @ts-ignore
- return new RegExp("[?&]" + name + "=([^&#]*)")?.exec(window.location.href)[1];
-}
-
-export function update_url_parameter(param: string, newVal: string): void {
- let newAdditionalURL = "";
- let tempArray = location.href.split("?");
- const baseURL = tempArray[0];
- const additionalURL = tempArray[1];
- let temp = "";
- if (additionalURL) {
- tempArray = additionalURL.split("&");
- for (let i = 0; i < tempArray.length; i++) {
- if (tempArray[i].split("=")[0] !== param) {
- newAdditionalURL += temp + tempArray[i];
- temp = "&";
- }
- }
- }
- const rows_txt = temp + "" + param + "=" + newVal;
- const newUrl = baseURL + "?" + newAdditionalURL + rows_txt;
- window.history.replaceState("", "", newUrl);
-}
-
-
-export function get_style_string(rules: CSSRuleList) {
- let styleString = "";
- for (const [key, value] of Object.entries(rules)) {
- styleString += key + ":" + value + ";";
- }
- return styleString;
-}
-
-export function parse_iso_local(s: string) {
- const b = s.split(/\D/);
- //@ts-ignore
- return new Date(b[0], b[1] - 1, b[2], b[3], b[4], b[5]);
-}
-
-export function resolve_references(json: any) {
- if (!json) return;
- if (typeof json === "string") {
- json = JSON.parse(json ?? "{}");
- }
- const byid = {}, refs = [];
- json = function recurse(obj, prop, parent) {
- if (typeof obj !== "object" || !obj) {
- return obj;
- }
- if (Object.prototype.toString.call(obj) === "[object Array]") {
- for (let i = 0; i < obj.length; i++) {
- if (typeof obj[i] !== "object" || !obj[i]) {
- continue;
- } else if ("$ref" in obj[i]) {
- // @ts-ignore
- obj[i] = recurse(obj[i], i, obj);
- } else {
- obj[i] = recurse(obj[i], prop, obj);
- }
- }
- return obj;
- }
- if ("$ref" in obj) {
- let ref = obj.$ref;
- if (ref in byid) {
- // @ts-ignore
- return byid[ref];
- }
- refs.push([parent, prop, ref]);
- return;
- } else if ("$id" in obj) {
- let id = obj.$id;
- delete obj.$id;
- if ("$values" in obj) {
- obj = obj.$values.map(recurse);
- } else {
- for (let prop2 in obj) {
- // @ts-ignore
- obj[prop2] = recurse(obj[prop2], prop2, obj);
- }
- }
- // @ts-ignore
- byid[id] = obj;
- }
- return obj;
- }(json);
- for (let i = 0; i < refs.length; i++) {
- let ref = refs[i];
- // @ts-ignore
- ref[0][ref[1]] = byid[ref[2]];
- }
- return json;
-}
-
-export function get_random_int(min: number, max: number): number {
- min = Math.ceil(min);
- max = Math.floor(max);
- return Math.floor(Math.random() * (max - min + 1)) + min;
-}
-
-export function to_readable_bytes(bytes: number): string {
- const s = ["bytes", "kB", "MB", "GB", "TB", "PB"];
- const e = Math.floor(Math.log(bytes) / Math.log(1024));
- return (bytes / Math.pow(1024, e)).toFixed(2) + " " + s[e];
-}
-
-export function can_use_dom(): boolean {
- return !!(typeof window !== "undefined" && window.document && window.document.createElement);
-}
-
-export function session_storage_remove_regex(regex: RegExp): void {
- if (!browser) {
- log_info("sessionStorage is not available in non-browser contexts");
- return;
- }
- let n = sessionStorage.length;
- while (n--) {
- const key = sessionStorage.key(n);
- if (key && regex.test(key)) {
- sessionStorage.removeItem(key);
- }
- }
-}
-
-export function local_storage_remove_regex(regex: RegExp): void {
- if (!browser) {
- log_info("sessionStorage is not available in non-browser contexts");
- return;
- }
- let n = localStorage.length;
- while (n--) {
- const key = localStorage.key(n);
- if (key && regex.test(key)) {
- localStorage.removeItem(key);
- }
- }
-}
-
-export function session_storage_set_json(key: string, value: object): void {
- if (!browser) {
- console.warn("sessionStorage is not available in non-browser contexts");
- return;
- }
- sessionStorage.setItem(key, JSON.stringify(value));
-}
-
-export function session_storage_get_json(key: string): object {
- if (!browser) {
- console.warn("sessionStorage is not available in non-browser contexts");
- return {};
- }
- return JSON.parse(sessionStorage.getItem(key) ?? "{}");
-}
-
-export function local_storage_set_json(key: string, value: object): void {
- if (!browser) {
- console.warn("sessionStorage is not available in non-browser contexts");
- return;
- }
- localStorage.setItem(key, JSON.stringify(value));
-}
-
-export function local_storage_get_json(key: string): object {
- if (!browser) {
- console.warn("sessionStorage is not available in non-browser contexts");
- return {};
- }
- return JSON.parse(localStorage.getItem(key) ?? "{}");
-}
-
-export function get_hash_code(value: string): number | undefined {
- let hash = 0;
- if (value.length === 0) {
- return;
- }
- for (let i = 0; i < value.length; i++) {
- const char = value.charCodeAt(i);
- hash = (hash << 5) - hash + char;
- hash |= 0;
- }
- return hash;
-}
diff --git a/code/app/src/lib/i18n/en/app/index.ts b/code/app/src/lib/i18n/en/app/index.ts
deleted file mode 100644
index 7ccfc97..0000000
--- a/code/app/src/lib/i18n/en/app/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { BaseTranslation } from '../../i18n-types'
-
-const en_app: BaseTranslation = {
- members: "Members",
-}
-
-export default en_app \ No newline at end of file
diff --git a/code/app/src/lib/i18n/en/index.ts b/code/app/src/lib/i18n/en/index.ts
deleted file mode 100644
index fbf5423..0000000
--- a/code/app/src/lib/i18n/en/index.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import type { BaseTranslation } from "../i18n-types";
-
-const en: BaseTranslation = {
- or: "Or",
- name: "Name",
- emailAddress: "Email address",
- password: "Password",
- pageNotFound: "Page not found",
- noInternet: "It seems like your device does not have a internet connection, please check your connection.",
- reset: "Reset",
- of: "{0} of {1}",
- isRequired: "{0} is required",
- submit: "Submit",
- success: "Success",
- tryAgainSoon: "Try again soon",
- createANewAccount: "Create a new account",
- unexpectedError: "An unexpected error occured",
- notFound: "Not found",
- documentation: "Documentation",
- tos: "Terms of service",
- privacyPolicy: "Privacy policy",
- signIntoYourAccount: "Sign into your account",
- combobox: {
- search: "Search",
- noRecordsFound: "No records found",
- createRecordHelpText: "Create a record by typing the name in the search bar and pressing enter",
- createRecordButtonText: "Press enter or click here to create {0}"
- },
- signInPage: {
- notMyComputer: "This is not my computer",
- resetPassword: "Reset password",
- yourPasswordIsUpdated: "Your password is updated",
- signIn: "Sign In",
- yourNewPasswordIsApplied: "Your new password is applied",
- signInBelow: "Sign in below",
- yourAccountIsDisabled: "Your account is disabled",
- contactYourAdminIfDisabled: "Contact your administrator if this feels wrong",
- youHaveReachedInactivityLimit: "You've reached the hidden inactivity limit",
- feelFreeToSignInAgain: "Feel free to sign in again"
- },
- signUpPage: {
- createYourNewAccount: "Create your new account",
- },
- resetPasswordPage: {
- setANewPassword: "Set a new password",
- expired: "Expired",
- requestHasExpired: "Your request has expired",
- requestANewReset: "Request a new reset",
- invalidRequestTitle: "Your request is invalid",
- invalidRequestMessage: "This could be due to it being expired, nonexsistent or something else",
- newPassword: "New password",
- requestSentMessage: "If we find your email address in our systems, you will receive an email with instructions on how to set a new password for your account.",
- requestAPasswordReset: "Request a password reset",
- requestNotFound: "Your request was not found",
- submitANewRequestBelow: "Submit a new reset request below"
- }
-};
-
-export default en;
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/i18n/i18n-svelte.ts b/code/app/src/lib/i18n/i18n-svelte.ts
deleted file mode 100644
index 6cdffb3..0000000
--- a/code/app/src/lib/i18n/i18n-svelte.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
-/* eslint-disable */
-
-import { initI18nSvelte } from 'typesafe-i18n/svelte'
-import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types'
-import { loadedFormatters, loadedLocales } from './i18n-util'
-
-const { locale, LL, setLocale } = initI18nSvelte<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
-
-export { locale, LL, setLocale }
-
-export default LL
diff --git a/code/app/src/lib/i18n/i18n-types.ts b/code/app/src/lib/i18n/i18n-types.ts
deleted file mode 100644
index cf968d7..0000000
--- a/code/app/src/lib/i18n/i18n-types.ts
+++ /dev/null
@@ -1,429 +0,0 @@
-// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
-/* eslint-disable */
-import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n'
-
-export type BaseTranslation = BaseTranslationType & DisallowNamespaces
-export type BaseLocale = 'en'
-
-export type Locales =
- | 'en'
- | 'nb'
-
-export type Translation = RootTranslation & DisallowNamespaces
-
-export type Translations = RootTranslation &
-{
- app: NamespaceAppTranslation
-}
-
-type RootTranslation = {
- /**
- * O​r
- */
- or: string
- /**
- * N​a​m​e
- */
- name: string
- /**
- * E​m​a​i​l​ ​a​d​d​r​e​s​s
- */
- emailAddress: string
- /**
- * P​a​s​s​w​o​r​d
- */
- password: string
- /**
- * P​a​g​e​ ​n​o​t​ ​f​o​u​n​d
- */
- pageNotFound: string
- /**
- * I​t​ ​s​e​e​m​s​ ​l​i​k​e​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​d​o​e​s​ ​n​o​t​ ​h​a​v​e​ ​a​ ​i​n​t​e​r​n​e​t​ ​c​o​n​n​e​c​t​i​o​n​,​ ​p​l​e​a​s​e​ ​c​h​e​c​k​ ​y​o​u​r​ ​c​o​n​n​e​c​t​i​o​n​.
- */
- noInternet: string
- /**
- * R​e​s​e​t
- */
- reset: string
- /**
- * {​0​}​ ​o​f​ ​{​1​}
- * @param {unknown} 0
- * @param {unknown} 1
- */
- of: RequiredParams<'0' | '1'>
- /**
- * {​0​}​ ​i​s​ ​r​e​q​u​i​r​e​d
- * @param {unknown} 0
- */
- isRequired: RequiredParams<'0'>
- /**
- * S​u​b​m​i​t
- */
- submit: string
- /**
- * S​u​c​c​e​s​s
- */
- success: string
- /**
- * T​r​y​ ​a​g​a​i​n​ ​s​o​o​n
- */
- tryAgainSoon: string
- /**
- * C​r​e​a​t​e​ ​a​ ​n​e​w​ ​a​c​c​o​u​n​t
- */
- createANewAccount: string
- /**
- * A​n​ ​u​n​e​x​p​e​c​t​e​d​ ​e​r​r​o​r​ ​o​c​c​u​r​e​d
- */
- unexpectedError: string
- /**
- * N​o​t​ ​f​o​u​n​d
- */
- notFound: string
- /**
- * D​o​c​u​m​e​n​t​a​t​i​o​n
- */
- documentation: string
- /**
- * T​e​r​m​s​ ​o​f​ ​s​e​r​v​i​c​e
- */
- tos: string
- /**
- * P​r​i​v​a​c​y​ ​p​o​l​i​c​y
- */
- privacyPolicy: string
- /**
- * S​i​g​n​ ​i​n​t​o​ ​y​o​u​r​ ​a​c​c​o​u​n​t
- */
- signIntoYourAccount: string
- combobox: {
- /**
- * S​e​a​r​c​h
- */
- search: string
- /**
- * N​o​ ​r​e​c​o​r​d​s​ ​f​o​u​n​d
- */
- noRecordsFound: string
- /**
- * C​r​e​a​t​e​ ​a​ ​r​e​c​o​r​d​ ​b​y​ ​t​y​p​i​n​g​ ​t​h​e​ ​n​a​m​e​ ​i​n​ ​t​h​e​ ​s​e​a​r​c​h​ ​b​a​r​ ​a​n​d​ ​p​r​e​s​s​i​n​g​ ​e​n​t​e​r
- */
- createRecordHelpText: string
- /**
- * P​r​e​s​s​ ​e​n​t​e​r​ ​o​r​ ​c​l​i​c​k​ ​h​e​r​e​ ​t​o​ ​c​r​e​a​t​e​ ​{​0​}
- * @param {unknown} 0
- */
- createRecordButtonText: RequiredParams<'0'>
- }
- signInPage: {
- /**
- * T​h​i​s​ ​i​s​ ​n​o​t​ ​m​y​ ​c​o​m​p​u​t​e​r
- */
- notMyComputer: string
- /**
- * R​e​s​e​t​ ​p​a​s​s​w​o​r​d
- */
- resetPassword: string
- /**
- * Y​o​u​r​ ​p​a​s​s​w​o​r​d​ ​i​s​ ​u​p​d​a​t​e​d
- */
- yourPasswordIsUpdated: string
- /**
- * S​i​g​n​ ​I​n
- */
- signIn: string
- /**
- * Y​o​u​r​ ​n​e​w​ ​p​a​s​s​w​o​r​d​ ​i​s​ ​a​p​p​l​i​e​d
- */
- yourNewPasswordIsApplied: string
- /**
- * S​i​g​n​ ​i​n​ ​b​e​l​o​w
- */
- signInBelow: string
- /**
- * Y​o​u​r​ ​a​c​c​o​u​n​t​ ​i​s​ ​d​i​s​a​b​l​e​d
- */
- yourAccountIsDisabled: string
- /**
- * C​o​n​t​a​c​t​ ​y​o​u​r​ ​a​d​m​i​n​i​s​t​r​a​t​o​r​ ​i​f​ ​t​h​i​s​ ​f​e​e​l​s​ ​w​r​o​n​g
- */
- contactYourAdminIfDisabled: string
- /**
- * Y​o​u​'​v​e​ ​r​e​a​c​h​e​d​ ​t​h​e​ ​h​i​d​d​e​n​ ​i​n​a​c​t​i​v​i​t​y​ ​l​i​m​i​t
- */
- youHaveReachedInactivityLimit: string
- /**
- * F​e​e​l​ ​f​r​e​e​ ​t​o​ ​s​i​g​n​ ​i​n​ ​a​g​a​i​n
- */
- feelFreeToSignInAgain: string
- }
- signUpPage: {
- /**
- * C​r​e​a​t​e​ ​y​o​u​r​ ​n​e​w​ ​a​c​c​o​u​n​t
- */
- createYourNewAccount: string
- }
- resetPasswordPage: {
- /**
- * S​e​t​ ​a​ ​n​e​w​ ​p​a​s​s​w​o​r​d
- */
- setANewPassword: string
- /**
- * E​x​p​i​r​e​d
- */
- expired: string
- /**
- * Y​o​u​r​ ​r​e​q​u​e​s​t​ ​h​a​s​ ​e​x​p​i​r​e​d
- */
- requestHasExpired: string
- /**
- * R​e​q​u​e​s​t​ ​a​ ​n​e​w​ ​r​e​s​e​t
- */
- requestANewReset: string
- /**
- * Y​o​u​r​ ​r​e​q​u​e​s​t​ ​i​s​ ​i​n​v​a​l​i​d
- */
- invalidRequestTitle: string
- /**
- * T​h​i​s​ ​c​o​u​l​d​ ​b​e​ ​d​u​e​ ​t​o​ ​i​t​ ​b​e​i​n​g​ ​e​x​p​i​r​e​d​,​ ​n​o​n​e​x​s​i​s​t​e​n​t​ ​o​r​ ​s​o​m​e​t​h​i​n​g​ ​e​l​s​e
- */
- invalidRequestMessage: string
- /**
- * N​e​w​ ​p​a​s​s​w​o​r​d
- */
- newPassword: string
- /**
- * I​f​ ​w​e​ ​f​i​n​d​ ​y​o​u​r​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s​ ​i​n​ ​o​u​r​ ​s​y​s​t​e​m​s​,​ ​y​o​u​ ​w​i​l​l​ ​r​e​c​e​i​v​e​ ​a​n​ ​e​m​a​i​l​ ​w​i​t​h​ ​i​n​s​t​r​u​c​t​i​o​n​s​ ​o​n​ ​h​o​w​ ​t​o​ ​s​e​t​ ​a​ ​n​e​w​ ​p​a​s​s​w​o​r​d​ ​f​o​r​ ​y​o​u​r​ ​a​c​c​o​u​n​t​.
- */
- requestSentMessage: string
- /**
- * R​e​q​u​e​s​t​ ​a​ ​p​a​s​s​w​o​r​d​ ​r​e​s​e​t
- */
- requestAPasswordReset: string
- /**
- * Y​o​u​r​ ​r​e​q​u​e​s​t​ ​w​a​s​ ​n​o​t​ ​f​o​u​n​d
- */
- requestNotFound: string
- /**
- * S​u​b​m​i​t​ ​a​ ​n​e​w​ ​r​e​s​e​t​ ​r​e​q​u​e​s​t​ ​b​e​l​o​w
- */
- submitANewRequestBelow: string
- }
-}
-
-export type NamespaceAppTranslation = {
- /**
- * M​e​m​b​e​r​s
- */
- members: string
-}
-
-export type Namespaces =
- | 'app'
-
-type DisallowNamespaces = {
- /**
- * reserved for 'app'-namespace\
- * you need to use the `./app/index.ts` file instead
- */
- app?: "[typesafe-i18n] reserved for 'app'-namespace. You need to use the `./app/index.ts` file instead."
-}
-
-export type TranslationFunctions = {
- /**
- * Or
- */
- or: () => LocalizedString
- /**
- * Name
- */
- name: () => LocalizedString
- /**
- * Email address
- */
- emailAddress: () => LocalizedString
- /**
- * Password
- */
- password: () => LocalizedString
- /**
- * Page not found
- */
- pageNotFound: () => LocalizedString
- /**
- * It seems like your device does not have a internet connection, please check your connection.
- */
- noInternet: () => LocalizedString
- /**
- * Reset
- */
- reset: () => LocalizedString
- /**
- * {0} of {1}
- */
- of: (arg0: unknown, arg1: unknown) => LocalizedString
- /**
- * {0} is required
- */
- isRequired: (arg0: unknown) => LocalizedString
- /**
- * Submit
- */
- submit: () => LocalizedString
- /**
- * Success
- */
- success: () => LocalizedString
- /**
- * Try again soon
- */
- tryAgainSoon: () => LocalizedString
- /**
- * Create a new account
- */
- createANewAccount: () => LocalizedString
- /**
- * An unexpected error occured
- */
- unexpectedError: () => LocalizedString
- /**
- * Not found
- */
- notFound: () => LocalizedString
- /**
- * Documentation
- */
- documentation: () => LocalizedString
- /**
- * Terms of service
- */
- tos: () => LocalizedString
- /**
- * Privacy policy
- */
- privacyPolicy: () => LocalizedString
- /**
- * Sign into your account
- */
- signIntoYourAccount: () => LocalizedString
- combobox: {
- /**
- * Search
- */
- search: () => LocalizedString
- /**
- * No records found
- */
- noRecordsFound: () => LocalizedString
- /**
- * Create a record by typing the name in the search bar and pressing enter
- */
- createRecordHelpText: () => LocalizedString
- /**
- * Press enter or click here to create {0}
- */
- createRecordButtonText: (arg0: unknown) => LocalizedString
- }
- signInPage: {
- /**
- * This is not my computer
- */
- notMyComputer: () => LocalizedString
- /**
- * Reset password
- */
- resetPassword: () => LocalizedString
- /**
- * Your password is updated
- */
- yourPasswordIsUpdated: () => LocalizedString
- /**
- * Sign In
- */
- signIn: () => LocalizedString
- /**
- * Your new password is applied
- */
- yourNewPasswordIsApplied: () => LocalizedString
- /**
- * Sign in below
- */
- signInBelow: () => LocalizedString
- /**
- * Your account is disabled
- */
- yourAccountIsDisabled: () => LocalizedString
- /**
- * Contact your administrator if this feels wrong
- */
- contactYourAdminIfDisabled: () => LocalizedString
- /**
- * You've reached the hidden inactivity limit
- */
- youHaveReachedInactivityLimit: () => LocalizedString
- /**
- * Feel free to sign in again
- */
- feelFreeToSignInAgain: () => LocalizedString
- }
- signUpPage: {
- /**
- * Create your new account
- */
- createYourNewAccount: () => LocalizedString
- }
- resetPasswordPage: {
- /**
- * Set a new password
- */
- setANewPassword: () => LocalizedString
- /**
- * Expired
- */
- expired: () => LocalizedString
- /**
- * Your request has expired
- */
- requestHasExpired: () => LocalizedString
- /**
- * Request a new reset
- */
- requestANewReset: () => LocalizedString
- /**
- * Your request is invalid
- */
- invalidRequestTitle: () => LocalizedString
- /**
- * This could be due to it being expired, nonexsistent or something else
- */
- invalidRequestMessage: () => LocalizedString
- /**
- * New password
- */
- newPassword: () => LocalizedString
- /**
- * If we find your email address in our systems, you will receive an email with instructions on how to set a new password for your account.
- */
- requestSentMessage: () => LocalizedString
- /**
- * Request a password reset
- */
- requestAPasswordReset: () => LocalizedString
- /**
- * Your request was not found
- */
- requestNotFound: () => LocalizedString
- /**
- * Submit a new reset request below
- */
- submitANewRequestBelow: () => LocalizedString
- }
- app: {
- /**
- * Members
- */
- members: () => LocalizedString
- }
-}
-
-export type Formatters = {}
diff --git a/code/app/src/lib/i18n/i18n-util.async.ts b/code/app/src/lib/i18n/i18n-util.async.ts
deleted file mode 100644
index 2e6717e..0000000
--- a/code/app/src/lib/i18n/i18n-util.async.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
-/* eslint-disable */
-
-import { initFormatters } from './formatters'
-import type { Locales, Namespaces, Translations } from './i18n-types'
-import { loadedFormatters, loadedLocales, locales } from './i18n-util'
-
-const localeTranslationLoaders = {
- en: () => import('./en'),
- nb: () => import('./nb'),
-}
-
-const localeNamespaceLoaders = {
- en: {
- app: () => import('./en/app')
- },
- nb: {
- app: () => import('./nb/app')
- }
-}
-
-const updateDictionary = (locale: Locales, dictionary: Partial<Translations>): Translations =>
- loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary }
-
-export const importLocaleAsync = async (locale: Locales): Promise<Translations> =>
- (await localeTranslationLoaders[locale]()).default as unknown as Translations
-
-export const loadLocaleAsync = async (locale: Locales): Promise<void> => {
- updateDictionary(locale, await importLocaleAsync(locale))
- loadFormatters(locale)
-}
-
-export const loadAllLocalesAsync = (): Promise<void[]> => Promise.all(locales.map(loadLocaleAsync))
-
-export const loadFormatters = (locale: Locales): void =>
- void (loadedFormatters[locale] = initFormatters(locale))
-
-export const importNamespaceAsync = async<Namespace extends Namespaces>(locale: Locales, namespace: Namespace) =>
- (await localeNamespaceLoaders[locale][namespace]()).default as unknown as Translations[Namespace]
-
-export const loadNamespaceAsync = async <Namespace extends Namespaces>(locale: Locales, namespace: Namespace): Promise<void> =>
- void updateDictionary(locale, { [namespace]: await importNamespaceAsync(locale, namespace )})
diff --git a/code/app/src/lib/i18n/i18n-util.sync.ts b/code/app/src/lib/i18n/i18n-util.sync.ts
deleted file mode 100644
index 8144fdc..0000000
--- a/code/app/src/lib/i18n/i18n-util.sync.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
-/* eslint-disable */
-
-import { initFormatters } from './formatters'
-import type { Locales, Translations } from './i18n-types'
-import { loadedFormatters, loadedLocales, locales } from './i18n-util'
-
-import en from './en'
-import nb from './nb'
-
-import en_app from './en/app'
-import nb_app from './nb/app'
-
-const localeTranslations = {
- en: {
- ...en,
- app: en_app
- },
- nb: {
- ...nb,
- app: nb_app
- },
-}
-
-export const loadLocale = (locale: Locales): void => {
- if (loadedLocales[locale]) return
-
- loadedLocales[locale] = localeTranslations[locale] as unknown as Translations
- loadFormatters(locale)
-}
-
-export const loadAllLocales = (): void => locales.forEach(loadLocale)
-
-export const loadFormatters = (locale: Locales): void =>
- void (loadedFormatters[locale] = initFormatters(locale))
diff --git a/code/app/src/lib/i18n/i18n-util.ts b/code/app/src/lib/i18n/i18n-util.ts
deleted file mode 100644
index 12feb33..0000000
--- a/code/app/src/lib/i18n/i18n-util.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
-/* eslint-disable */
-
-import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n'
-import type { LocaleDetector } from 'typesafe-i18n/detectors'
-import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n'
-import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors'
-import type { Formatters, Locales, Namespaces, Translations, TranslationFunctions } from './i18n-types'
-
-export const baseLocale: Locales = 'en'
-
-export const locales: Locales[] = [
- 'en',
- 'nb'
-]
-
-export const namespaces: Namespaces[] = [
- 'app'
-]
-
-export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales)
-
-export const isNamespace = (namespace: string): namespace is Namespaces => namespaces.includes(namespace as Namespaces)
-
-export const loadedLocales: Record<Locales, Translations> = {} as Record<Locales, Translations>
-
-export const loadedFormatters: Record<Locales, Formatters> = {} as Record<Locales, Formatters>
-
-export const i18nString = (locale: Locales): TranslateByString => initI18nString<Locales, Formatters>(locale, loadedFormatters[locale])
-
-export const i18nObject = (locale: Locales): TranslationFunctions =>
- initI18nObject<Locales, Translations, TranslationFunctions, Formatters>(
- locale,
- loadedLocales[locale],
- loadedFormatters[locale]
- )
-
-export const i18n = (): LocaleTranslationFunctions<Locales, Translations, TranslationFunctions> =>
- initI18n<Locales, Translations, TranslationFunctions, Formatters>(loadedLocales, loadedFormatters)
-
-export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn<Locales>(baseLocale, locales, ...detectors)
diff --git a/code/app/src/lib/i18n/nb/app/index.ts b/code/app/src/lib/i18n/nb/app/index.ts
deleted file mode 100644
index 6bf9ba6..0000000
--- a/code/app/src/lib/i18n/nb/app/index.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { NamespaceAppTranslation } from '../../i18n-types'
-
-const nb_app: NamespaceAppTranslation = {
- members: "Medlemmer"
-}
-
-export default nb_app
diff --git a/code/app/src/lib/i18n/nb/index.ts b/code/app/src/lib/i18n/nb/index.ts
deleted file mode 100644
index ef67504..0000000
--- a/code/app/src/lib/i18n/nb/index.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import type { Translation } from "../i18n-types";
-
-const nb: Translation = {
- or: "Eller",
- name: "Navn",
- emailAddress: "E-postadresse",
- password: "Passord",
- pageNotFound: "Fant ikke siden",
- noInternet: "Det ser ut som at du ikke tilkoblet internettet, sjekk tilkoblingen din for å fortsette",
- reset: "Tilbakestill",
- of: "{0} av {1}",
- isRequired: "{0} er påkrevd",
- submit: "Send",
- success: "Suksess",
- tryAgainSoon: "Prøv igjen snart",
- createANewAccount: "Lag en ny konto",
- unexpectedError: "En uventet feil oppstod",
- notFound: "Ikke funnet",
- documentation: "Dokumentasjon",
- tos: "Vilkår",
- privacyPolicy: "Personvernerklæring",
- signIntoYourAccount: "Logg inn med din konto",
- signInPage: {
- notMyComputer: "Dette er ikke min datamaskin",
- resetPassword: "Tilbakestill passord",
- yourPasswordIsUpdated: "Ditt passord er oppdater",
- signIn: "Logg inn",
- yourNewPasswordIsApplied: "Ditt nye passord er satt",
- signInBelow: "Logg inn nedenfor",
- yourAccountIsDisabled: "Din konto er deaktivert",
- contactYourAdminIfDisabled: "Ta kontakt med din administrator hvis dette føles feil",
- youHaveReachedInactivityLimit: "Du har nådd den hemmelige inaktivitetsgrensen",
- feelFreeToSignInAgain: "Logg gjerne inn igjen"
- },
- signUpPage: {
- createYourNewAccount: "Opprett din nye konto",
- },
- resetPasswordPage: {
- setANewPassword: "Skriv et nytt passord",
- expired: "Utgått",
- requestHasExpired: "Din forespørsel er utgått",
- requestANewReset: "Spør om en ny tilbakestillingslenke",
- newPassword: "Nytt passord",
- requestSentMessage: "Hvis vi finner e-postadressen din i våre systemer, vil du få en e-post med instrukser for å sette ditt nye passord.",
- requestAPasswordReset: "Forespør tilbakestilling av ditt passord",
- requestNotFound: "Din forespørsel ble ikke funnet",
- submitANewRequestBelow: "Spør om en ny tilbakestillingslenke nedenfor"
- }
-}
-
-export default nb; \ No newline at end of file
diff --git a/code/app/src/lib/logger.ts b/code/app/src/lib/logger.ts
deleted file mode 100644
index 831694c..0000000
--- a/code/app/src/lib/logger.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import { browser, dev } from "$app/environment";
-import { StorageKeys } from "$lib/configuration";
-import pino from "pino";
-
-const pinoConfig = dev ? {
- transport: {
- target: "pino-pretty",
- }
-} : {};
-
-const pinoLogger = pino(pinoConfig);
-
-function browser_log_level(): number {
- if (browser) return LogLevel.to_number(sessionStorage.getItem(StorageKeys.logLevel), LogLevel.INFO);
- throw new Error("Called browser api in server");
-}
-
-function server_log_level(): number {
- if (!browser) return LogLevel.to_number(import.meta.env.VITE_LOG_LEVEL, LogLevel.ERROR);
- throw new Error("Called server api in browser");
-}
-
-export const LogLevel = {
- DEBUG: 0,
- INFO: 1,
- ERROR: 2,
- SILENT: 3,
- to_string(levelInt: number): string {
- switch (levelInt) {
- case 0:
- return "DEBUG";
- case 1:
- return "INFO";
- case 2:
- return "ERROR";
- case 3:
- return "SILENT";
- default:
- throw new Error("Log level int is unknown");
- }
- },
- to_number(levelString?: string | null, fallback?: number): number {
- if (!levelString && fallback) return fallback;
- else if (!levelString && !fallback) throw new Error("levelString was empty, and no fallback was specified");
- switch (levelString?.toUpperCase()) {
- case "DEBUG":
- return 0;
- case "INFO":
- return 1;
- case "ERROR":
- return 2;
- case "SILENT":
- return 3;
- default:
- if (!fallback) throw new Error("Log level string is unknown");
- else return fallback;
- }
- },
-};
-
-export function log_debug(message: string, ...additional: any[]): void {
- if (browser && browser_log_level() <= LogLevel.DEBUG) {
- pinoLogger.debug(message, additional);
- }
- if (!browser && server_log_level() <= LogLevel.DEBUG) {
- pinoLogger.debug(message, additional);
- }
-}
-
-export function log_info(message: string, ...additional: any[]): void {
- if (browser && browser_log_level() <= LogLevel.INFO) {
- pinoLogger.info(message, additional);
- }
- if (!browser && server_log_level() <= LogLevel.INFO) {
- pinoLogger.info(message, additional);
- }
-}
-
-export function log_error(message: any, ...additional: any[]): void {
- if (browser && browser_log_level() <= LogLevel.ERROR) {
- pinoLogger.error(message, additional);
- }
- if (!browser && server_log_level() <= LogLevel.ERROR) {
- pinoLogger.error(message, additional);
- }
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/base/Customer.ts b/code/app/src/lib/models/base/Customer.ts
deleted file mode 100644
index e44ebb6..0000000
--- a/code/app/src/lib/models/base/Customer.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { CustomerContact } from "./CustomerContact"
-import type { User } from "./User"
-
-export type Customer = {
- /**
- * Guid id for customer
- */
- id: string,
- /**
- * The name of the company
- */
- name: string,
- /**
- * Responsible contact in the current tenant
- */
- tenantContact: User,
- /**
- * The customers main contact
- */
- mainContact: CustomerContact,
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/base/CustomerContact.ts b/code/app/src/lib/models/base/CustomerContact.ts
deleted file mode 100644
index e8abea5..0000000
--- a/code/app/src/lib/models/base/CustomerContact.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export type CustomerContact = {
- firstName: string,
- lastname: string,
- email: string,
- phone: string,
- workTitle: string,
- note: string
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/base/CustomerEvent.ts b/code/app/src/lib/models/base/CustomerEvent.ts
deleted file mode 100644
index af86511..0000000
--- a/code/app/src/lib/models/base/CustomerEvent.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export type CustomerEvent = {
- /**
- * A descriptive name for the occured event
- */
- name: string,
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/base/SessionData.ts b/code/app/src/lib/models/base/SessionData.ts
deleted file mode 100644
index 015cbf3..0000000
--- a/code/app/src/lib/models/base/SessionData.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type SessionData = {
- id: string,
- username: string,
- displayName: string,
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/base/Tenant.ts b/code/app/src/lib/models/base/Tenant.ts
deleted file mode 100644
index 983122b..0000000
--- a/code/app/src/lib/models/base/Tenant.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import type { User } from "./User"
-
-export type Tenant = {
- id: string,
- name: string,
- description: string,
- masterUser: User,
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/base/User.ts b/code/app/src/lib/models/base/User.ts
deleted file mode 100644
index 371c38e..0000000
--- a/code/app/src/lib/models/base/User.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { UserRole } from "./UserRole"
-
-export type User = {
- /**
- * Guid id for user
- */
- id: string,
- firstName: string,
- lastName: string,
- role: UserRole,
- username: string,
- email: string
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/base/UserRole.ts b/code/app/src/lib/models/base/UserRole.ts
deleted file mode 100644
index ec32852..0000000
--- a/code/app/src/lib/models/base/UserRole.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export enum UserRole {
- REGULAR = "reg",
- ADMINISTRATOR = "adm",
- OWNER = "own"
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/internal/FormError.ts b/code/app/src/lib/models/internal/FormError.ts
deleted file mode 100644
index f6d8978..0000000
--- a/code/app/src/lib/models/internal/FormError.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import type { KnownProblem } from "./KnownProblem";
-
-export class FormError {
- title: string;
- subtitle: string;
- constructor(title: string = "", subtitle: string = "") {
- this.title = title;
- this.title = subtitle;
- }
-
- set(title: string = "", subtitle: string = "") {
- this.title = title;
- this.subtitle = subtitle;
- }
-
- set_from_known_problem(knownProblem: KnownProblem) {
- this.title = knownProblem.title ?? "";
- this.subtitle = knownProblem.subtitle ?? "";
- }
-
- has_error() {
- return this.title?.length > 0 || this.subtitle?.length > 0;
- }
-} \ No newline at end of file
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/internal/KnownProblem.ts b/code/app/src/lib/models/internal/KnownProblem.ts
deleted file mode 100644
index b6923d9..0000000
--- a/code/app/src/lib/models/internal/KnownProblem.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export type KnownProblem = {
- title: string,
- subtitle: string,
- errors: Record<string, string[]>,
- traceId: string,
-}
-
-export function is_known_problem(response: Response): boolean {
- return response.headers.has("X-IsKnownProblem");
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/projects/Project.ts b/code/app/src/lib/models/projects/Project.ts
deleted file mode 100644
index f265e67..0000000
--- a/code/app/src/lib/models/projects/Project.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { Temporal } from "temporal-polyfill"
-import type { ProjectMember } from "./ProjectMember"
-import type { ProjectStatus } from "./ProjectStatus"
-
-export type Project = {
- id: string,
- name: string,
- description?: string,
- start: Temporal.PlainDate,
- stop?: Temporal.PlainDate,
- members: Array<ProjectMember>,
- status: ProjectStatus
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/projects/ProjectLabel.ts b/code/app/src/lib/models/projects/ProjectLabel.ts
deleted file mode 100644
index 59aa9d5..0000000
--- a/code/app/src/lib/models/projects/ProjectLabel.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type ProjectLabel = {
- id: string,
- name: string,
- color: string
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/projects/ProjectMember.ts b/code/app/src/lib/models/projects/ProjectMember.ts
deleted file mode 100644
index de348ef..0000000
--- a/code/app/src/lib/models/projects/ProjectMember.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import type { ProjectRole } from "./ProjectRole"
-
-export type ProjectMember = {
- id: string,
- name: string,
- role: ProjectRole,
- email: string,
- userId?: string,
- customerId?: string
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/projects/ProjectMeta.ts b/code/app/src/lib/models/projects/ProjectMeta.ts
deleted file mode 100644
index c583b47..0000000
--- a/code/app/src/lib/models/projects/ProjectMeta.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { Temporal } from "temporal-polyfill"
-import type { User } from "../base/User"
-
-export type ProjectMeta = {
- created: Temporal.PlainDateTime,
- createdBy: User,
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/projects/ProjectRole.ts b/code/app/src/lib/models/projects/ProjectRole.ts
deleted file mode 100644
index 0fa2347..0000000
--- a/code/app/src/lib/models/projects/ProjectRole.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export enum ProjectRole {
- EXTERNAL = "ext",
- INTERNAL = "int",
- RESOURCE = "res",
- MANAGER = "man",
- OWNER = "own"
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/projects/ProjectStatus.ts b/code/app/src/lib/models/projects/ProjectStatus.ts
deleted file mode 100644
index 2df4b88..0000000
--- a/code/app/src/lib/models/projects/ProjectStatus.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export enum ProjectStatus {
- ACTIVE = "act",
- EXPIRED = "exp",
- IDLE = "idl"
-} \ No newline at end of file
diff --git a/code/app/src/lib/models/work/WorkCategory.ts b/code/app/src/lib/models/work/WorkCategory.ts
deleted file mode 100644
index 7dd85d5..0000000
--- a/code/app/src/lib/models/work/WorkCategory.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type WorkCategory = {
- id: string,
- name: string,
- color: string
-}
diff --git a/code/app/src/lib/models/work/WorkEntry.ts b/code/app/src/lib/models/work/WorkEntry.ts
deleted file mode 100644
index 2108b88..0000000
--- a/code/app/src/lib/models/work/WorkEntry.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { WorkLabel } from "./WorkLabel";
-import type { WorkCategory } from "./WorkCategory";
-import type { Project } from "../projects/Project";
-
-export type WorkEntry = {
- id: string,
- start: string,
- stop: string,
- description: string,
- labels?: Array<WorkLabel>,
- category?: WorkCategory,
- project?: Project
-}
diff --git a/code/app/src/lib/models/work/WorkEntryQueryResponse.ts b/code/app/src/lib/models/work/WorkEntryQueryResponse.ts
deleted file mode 100644
index a6974f1..0000000
--- a/code/app/src/lib/models/work/WorkEntryQueryResponse.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type { WorkCategory } from "./WorkCategory";
-import type { WorkLabel } from "./WorkLabel";
-import type { Temporal } from "temporal-polyfill";
-
-export interface WorkEntryQueryResponse {
- duration: WorkEntryQueryDuration,
- categories?: Array<WorkCategory>,
- labels?: Array<WorkLabel>,
- dateRange?: WorkEntryQueryDateRange,
- specificDate?: Temporal.PlainDateTime
- page: number,
- pageSize: number
-}
-
-export interface WorkEntryQueryDateRange {
- from: Temporal.PlainDateTime,
- to: Temporal.PlainDateTime
-}
-
-export enum WorkEntryQueryDuration {
- TODAY = 0,
- THIS_WEEK = 1,
- THIS_MONTH = 2,
- THIS_YEAR = 3,
- SPECIFIC_DATE = 4,
- DATE_RANGE = 5,
-}
diff --git a/code/app/src/lib/models/work/WorkLabel.ts b/code/app/src/lib/models/work/WorkLabel.ts
deleted file mode 100644
index f7e2795..0000000
--- a/code/app/src/lib/models/work/WorkLabel.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export interface WorkLabel {
- id?: string,
- name?: string,
- color?: string
-}
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/persistent-store.ts b/code/app/src/lib/persistent-store.ts
deleted file mode 100644
index 922f3ab..0000000
--- a/code/app/src/lib/persistent-store.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { writable as _writable, readable as _readable, } from "svelte/store";
-import type { Writable, Readable, StartStopNotifier } from "svelte/store";
-
-enum StoreType {
- SESSION = 0,
- LOCAL = 1
-}
-
-interface StoreOptions {
- store?: StoreType;
-}
-
-const default_store_options = {
- store: StoreType.SESSION
-} as StoreOptions;
-
-interface WritableStore<T> {
- name: string,
- initialState: T,
- options?: StoreOptions
-}
-
-interface ReadableStore<T> {
- name: string,
- initialState: T,
- callback: StartStopNotifier<any>,
- options?: StoreOptions
-}
-
-function get_store(type: StoreType): Storage {
- switch (type) {
- case StoreType.SESSION:
- return window.sessionStorage;
- case StoreType.LOCAL:
- return window.localStorage;
- }
-}
-
-function prepared_store_value(value: any): string {
- try {
- return JSON.stringify(value);
- } catch (e) {
- console.error(e);
- return "__INVALID__";
- }
-}
-
-function get_store_value<T>(options: WritableStore<T> | ReadableStore<T>): any {
- try {
- const storage = get_store(options.options.store);
- const value = storage.getItem(options.name);
- if (!value) return false;
- return JSON.parse(value);
- } catch (e) {
- console.error(e);
- return { __INVALID__: true };
- }
-}
-
-function hydrate<T>(store: Writable<T>, options: WritableStore<T> | ReadableStore<T>): void {
- const value = get_store_value<T>(options);
- if (value && store.set) store.set(value);
-}
-
-function subscribe<T>(store: Writable<T> | Readable<T>, options: WritableStore<T> | ReadableStore<T>): void {
- const storage = get_store(options.options.store);
- if (!store.subscribe) return;
- store.subscribe((state: any) => {
- storage.setItem(options.name, prepared_store_value(state));
- });
-}
-
-function writable_persistent<T>(options: WritableStore<T>): Writable<T> {
- if (options.options === undefined) options.options = default_store_options;
- console.log("Creating writable store with options: ", options);
- const store = _writable<T>(options.initialState);
- hydrate(store, options);
- subscribe(store, options);
- return store;
-}
-
-function readable_persistent<T>(options: ReadableStore<T>): Readable<T> {
- if (options.options === undefined) options.options = default_store_options;
- console.log("Creating readable store with options: ", options);
- const store = _readable<T>(options.initialState, options.callback);
- // hydrate(store, options);
- subscribe(store, options);
- return store;
-}
-
-export {
- writable_persistent,
- readable_persistent,
- StoreType
-};
-
-export type {
- WritableStore,
- ReadableStore,
- StoreOptions
-};
-
diff --git a/code/app/src/lib/services/abstractions/IAccountService.ts b/code/app/src/lib/services/abstractions/IAccountService.ts
deleted file mode 100644
index 2beeb08..0000000
--- a/code/app/src/lib/services/abstractions/IAccountService.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import type { KnownProblem } from "$lib/models/internal/KnownProblem"
-
-export interface IAccountService {
- session: 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,
-}
-
-export type LoginPayload = {
- username: string,
- password: string,
- persist: boolean
-}
-
-export type LoginResponse = {
- isLoggedIn: boolean,
- knownProblem?: KnownProblem
-}
-
-export type CreateAccountPayload = {
- username: string,
- password: string,
-}
-
-export type CreateAccountResponse = {
- isCreated: boolean,
- knownProblem?: KnownProblem
-}
-
-export type DeleteAccountResponse = {
- isDeleted: boolean
-}
-
-export type UpdateAccountPayload = {
- username: string,
- password: string
-}
-
-export type UpdateAccountResponse = {
- isUpdated: boolean,
- knownProblem?: KnownProblem
-} \ No newline at end of file
diff --git a/code/app/src/lib/services/abstractions/IPasswordResetService.ts b/code/app/src/lib/services/abstractions/IPasswordResetService.ts
deleted file mode 100644
index b6f6671..0000000
--- a/code/app/src/lib/services/abstractions/IPasswordResetService.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import type { KnownProblem } from "$lib/models/internal/KnownProblem"
-
-export interface IPasswordResetService {
- create_request_async(email: string): Promise<CreateRequestResponse>,
- fulfill_request_async(id: string, newPassword: string): Promise<FulfillRequestResponse>,
- request_is_valid_async(id: string): Promise<RequestIsValidResponse>
-}
-
-export type RequestIsValidResponse = {
- isValid: boolean
-}
-
-export type FulfillRequestResponse = {
- isFulfilled: boolean,
- knownProblem?: KnownProblem
-}
-
-export type CreateRequestResponse = {
- isCreated: boolean,
- knownProblem?: KnownProblem
-} \ No newline at end of file
diff --git a/code/app/src/lib/services/abstractions/ISettingsService.ts b/code/app/src/lib/services/abstractions/ISettingsService.ts
deleted file mode 100644
index 366e337..0000000
--- a/code/app/src/lib/services/abstractions/ISettingsService.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export interface ISettingsService {
- get_user_settings(): Promise<void>,
-} \ No newline at end of file
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/services/password-reset-service.ts b/code/app/src/lib/services/password-reset-service.ts
deleted file mode 100644
index 650b5f7..0000000
--- a/code/app/src/lib/services/password-reset-service.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-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";
-
-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 };
- if (is_known_problem(response)) return {
- isCreated: false,
- knownProblem: await response.json()
- }
-
- return {
- 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 };
- if (is_known_problem(response)) return {
- isFulfilled: false,
- 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
- }
- }
-} \ No newline at end of file
diff --git a/code/app/src/lib/services/settings-service.ts b/code/app/src/lib/services/settings-service.ts
deleted file mode 100644
index 6b801ac..0000000
--- a/code/app/src/lib/services/settings-service.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { ISettingsService } from "./abstractions/ISettingsService";
-
-export class SettingsService implements ISettingsService {
- get_user_settings(): Promise<void> {
- throw new Error("Method not implemented.");
- }
-} \ 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;
-}