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