From 18ad968657886c5af420190de00d26a151df73af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:03:30 +0000 Subject: [PATCH 01/15] Bump @humanfs/node from 0.16.6 to 0.16.8 Bumps [@humanfs/node](https://github.com/humanwhocodes/humanfs/tree/HEAD/packages/node) from 0.16.6 to 0.16.8. - [Release notes](https://github.com/humanwhocodes/humanfs/releases) - [Changelog](https://github.com/humanwhocodes/humanfs/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/humanwhocodes/humanfs/commits/node-v0.16.8/packages/node) --- updated-dependencies: - dependency-name: "@humanfs/node" dependency-version: 0.16.8 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d07139..3ceba46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1526,41 +1526,41 @@ "license": "MIT" }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", - "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.3.0" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, - "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", - "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { From fe05c55ffbe07a7e780cef332d39e7d8cc81f3bc Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Fri, 4 Sep 2026 14:53:29 +0200 Subject: [PATCH 02/15] Resolve missing staff crypto situations -wip --- package-lock.json | 8 +- package.json | 2 +- project.inlang/messages/de.json | 7 ++ project.inlang/messages/en.json | 7 ++ .../responsive-dialog.svelte | 20 +++- .../(components)/MissingStaffCrypto.svelte | 100 ++++++++++++++++++ src/routes/(pages)/dashboard/+layout.svelte | 4 + .../(components)/AppointmentPreview.svelte | 13 ++- 8 files changed, 149 insertions(+), 12 deletions(-) create mode 100644 src/routes/(pages)/dashboard/(components)/MissingStaffCrypto.svelte diff --git a/package-lock.json b/package-lock.json index 4d07139..05eb401 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "@types/dotenv": "^6.1.1", "@types/node": "^24", "@types/nodemailer": "^6.4.17", - "bits-ui": "^2.15.4", + "bits-ui": "^2.19.0", "clsx": "^2.1.1", "drizzle-kit": "^0.31.1", "eslint": "^9.29.0", @@ -3803,9 +3803,9 @@ "license": "MIT" }, "node_modules/bits-ui": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.18.1.tgz", - "integrity": "sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.19.0.tgz", + "integrity": "sha512-dHDm5Jw2NZJgLRvHAwPeuR4BnZij+AqTIq33OqVfo+qiMXontoC/yIvGT5TZaviv7OuBdJ2QUy2JPQ2yKBvF6w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 37271e0..57daa4e 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@types/dotenv": "^6.1.1", "@types/node": "^24", "@types/nodemailer": "^6.4.17", - "bits-ui": "^2.15.4", + "bits-ui": "^2.19.0", "clsx": "^2.1.1", "drizzle-kit": "^0.31.1", "eslint": "^9.29.0", diff --git a/project.inlang/messages/de.json b/project.inlang/messages/de.json index d56ac29..034f902 100644 --- a/project.inlang/messages/de.json +++ b/project.inlang/messages/de.json @@ -458,6 +458,13 @@ } ], "error": "Fehler beim Senden" + }, + "missingCryptoKeys": { + "title": "Kryptografische Schlüssel fehlen", + "description": "Dieser Tab enthält nicht die kryptografischen Schlüssel zum Entschlüsseln und Verschlüsseln von Daten. Lade die Schlüssel, um Termine/Daten anzuzeigen und hinzuzufügen.", + "cancel": "Ich will nur Einstellungen ändern", + "success": "Kryptografische Schlüssel geladen", + "error": "Fehler beim Laden der kryptografischen Schlüssel" } }, "agents": { diff --git a/project.inlang/messages/en.json b/project.inlang/messages/en.json index e4b5ab8..4a32832 100644 --- a/project.inlang/messages/en.json +++ b/project.inlang/messages/en.json @@ -466,6 +466,13 @@ } ], "error": "Failed to send reminders" + }, + "missingCryptoKeys": { + "title": "Cryptography Keys Missing", + "description": "This tab is missing the cryptographic keys to decrypt and encrypt data. Load keys to view and add appointments/data.", + "cancel": "I just want to change settings", + "success": "Cryptography keys loaded", + "error": "Failed to load cryptography keys" } }, "agents": { diff --git a/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte b/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte index 4da3c65..21fde88 100644 --- a/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte +++ b/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte @@ -52,6 +52,7 @@ triggerHidden = false, triggerVariant = "default", isActionLoading = false, + isDismissable = true, actions, children, }: HTMLAttributes & { @@ -62,6 +63,7 @@ description?: string; triggerVariant?: ButtonVariant; isActionLoading?: boolean; + isDismissable?: boolean; actions?: ListItemAction[]; } = $props(); @@ -153,9 +155,13 @@ 0 && "[&>button:last-child]:hidden", // hides default close button + // hides default close button + actions && actions.length > 0 ? "[&>button:last-child]:hidden" : "", )} onOpenAutoFocus={(e) => e.preventDefault()} + escapeKeydownBehavior={isDismissable === false ? "ignore" : "close"} + interactOutsideBehavior={isDismissable === false ? "ignore" : "close"} + showCloseButton={isDismissable === true} >
@@ -183,7 +189,7 @@ {:else} - + {#if !triggerHidden} {#if typeof triggerLabel === "string"} @@ -196,6 +202,8 @@ e.preventDefault()} + escapeKeydownBehavior={isDismissable === false ? "ignore" : "close"} + interactOutsideBehavior={isDismissable === false ? "ignore" : "close"} >
@@ -213,9 +221,11 @@ {@render children?.()} - - {m.cancel()} - + {#if isDismissable !== false} + + {m.cancel()} + + {/if} {/if} diff --git a/src/routes/(pages)/dashboard/(components)/MissingStaffCrypto.svelte b/src/routes/(pages)/dashboard/(components)/MissingStaffCrypto.svelte new file mode 100644 index 0000000..e1724dd --- /dev/null +++ b/src/routes/(pages)/dashboard/(components)/MissingStaffCrypto.svelte @@ -0,0 +1,100 @@ + + + +
+ + +
+
diff --git a/src/routes/(pages)/dashboard/+layout.svelte b/src/routes/(pages)/dashboard/+layout.svelte index afb2486..9ff1275 100644 --- a/src/routes/(pages)/dashboard/+layout.svelte +++ b/src/routes/(pages)/dashboard/+layout.svelte @@ -9,6 +9,8 @@ import { staff } from "$lib/stores/staff"; import { QueryClient, QueryClientProvider } from "@tanstack/svelte-query"; import { browser } from "$app/environment"; + import MissingStaffCrypto from "./(components)/MissingStaffCrypto.svelte"; + import { openDialog } from "$lib/components/ui/responsive-dialog"; let { data, children }: LayoutProps = $props(); @@ -80,6 +82,7 @@ if (success) { console.log("✅ Staff crypto initialized successfully"); } else { + openDialog("missing-staff-crypto"); console.log("ℹ️ Staff crypto not initialized (no session data or error)"); } } @@ -88,4 +91,5 @@ {@render children()} + diff --git a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte index 1799c0f..7bbb8f7 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte @@ -9,6 +9,7 @@ import { Button } from "$lib/components/ui/button"; import { m } from "$i18n/messages"; import { cn } from "$lib/utils"; + import { auth } from "$lib/stores/auth"; let { item, @@ -29,7 +30,7 @@ missingKeyShare: "Missing key share", }; - const decrypt = async () => { + const decrypt = async (retry = true) => { if (!item.appointment) { console.error("Unable to decrypt appointment data - no appointment data in item.", item.id); error = "Missing data"; @@ -52,7 +53,7 @@ // Wait for crypto to be initialized (max 5 seconds) if (!$staffCrypto.isAuthenticated || !$staffCrypto.crypto) { - const maxWaitTime = 5000; // 5 seconds + const maxWaitTime = 2000; const startTime = Date.now(); while (!$staffCrypto.isAuthenticated && Date.now() - startTime < maxWaitTime) { @@ -60,6 +61,14 @@ } if (!$staffCrypto.isAuthenticated || !$staffCrypto.crypto) { + const user = $auth.user; + if (retry && user && user.tenantId) { + console.warn("Staff crypto not initialized, retrying decryption..."); + // TODO: This should only be done once, not in every instance + await staffCrypto.authenticate(user.id, user.tenantId); + decrypt(false); + return; + } error = "Crypto not initialized"; console.error("Staff crypto not initialized after waiting"); return; From 575845acadafcb1ea6b85bfecadc384fa746ff02 Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Tue, 8 Sep 2026 08:58:59 +0200 Subject: [PATCH 03/15] Auto-decrypt appointments after added keys + removing arbitrary key expiry + slow down login to give browser time to write cookies --- src/lib/client/appointment-crypto.ts | 14 --------- src/lib/stores/staff-crypto.ts | 31 ++++++++++++++++++- .../(components)/AppointmentPreview.svelte | 16 +++++++--- .../(components)/MoveAppointment.svelte | 1 - src/routes/(pages)/login/login-form.svelte | 4 +++ 5 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/lib/client/appointment-crypto.ts b/src/lib/client/appointment-crypto.ts index 045fddb..da2857f 100644 --- a/src/lib/client/appointment-crypto.ts +++ b/src/lib/client/appointment-crypto.ts @@ -153,9 +153,7 @@ export class UnifiedAppointmentCrypto { // Staff-specific properties private staffKeyPair: StaffKeyPair | null = null; private staffId: string | null = null; - private tenantId: string | null = null; private staffAuthenticated: boolean = false; - private keyExpiry: number | null = null; // Shared crypto utilities private kyberCrypto: KyberCrypto = new KyberCrypto(); @@ -786,9 +784,7 @@ export class UnifiedAppointmentCrypto { }; this.staffId = staffId; - this.tenantId = tenantId; this.staffAuthenticated = true; - this.keyExpiry = Date.now() + 10 * 60 * 1000; // 10 minutes console.log("✅ Staff authentication successful with PRF"); } catch (error) { @@ -820,10 +816,6 @@ export class UnifiedAppointmentCrypto { throw new Error("Staff not authenticated"); } - if (this.keyExpiry && Date.now() > this.keyExpiry) { - throw new Error("Staff session expired - please authenticate again"); - } - try { // Parse the staffKeyShare which now contains: encapsulatedSecret || iv || encryptedTunnelKey const staffKeyShareBytes = this.hexToUint8Array(encryptedData.staffKeyShare); @@ -958,9 +950,7 @@ export class UnifiedAppointmentCrypto { logoutStaff(): void { this.staffKeyPair = null; this.staffId = null; - this.tenantId = null; this.staffAuthenticated = false; - this.keyExpiry = null; } /** @@ -1727,10 +1717,6 @@ export class UnifiedAppointmentCrypto { throw new Error("Staff not authenticated"); } - if (this.keyExpiry && Date.now() > this.keyExpiry) { - throw new Error("Staff session expired - please authenticate again"); - } - try { // Parse the staffKeyShare which now contains: encapsulatedSecret || iv || encryptedTunnelKey const staffKeyShareBytes = this.hexToUint8Array(staffKeyShare); diff --git a/src/lib/stores/staff-crypto.ts b/src/lib/stores/staff-crypto.ts index e05de96..bd5ebb6 100644 --- a/src/lib/stores/staff-crypto.ts +++ b/src/lib/stores/staff-crypto.ts @@ -1,9 +1,10 @@ -import { writable } from "svelte/store"; +import { get, writable } from "svelte/store"; import { UnifiedAppointmentCrypto } from "$lib/client/appointment-crypto"; import { auth } from "./auth"; interface StaffCryptoState { crypto: UnifiedAppointmentCrypto | null; + isAuthenticating: boolean; isAuthenticated: boolean; error: string | null; } @@ -11,6 +12,7 @@ interface StaffCryptoState { const createStaffCryptoStore = () => { const store = writable({ crypto: null, + isAuthenticating: false, isAuthenticated: false, error: null, }); @@ -37,11 +39,18 @@ const createStaffCryptoStore = () => { */ async authenticate(staffId: string, tenantId: string): Promise { try { + store.set({ + ...get(store), + isAuthenticating: true, + error: null, + }); + const crypto = new UnifiedAppointmentCrypto(); await crypto.authenticateStaff(staffId, tenantId); store.set({ crypto, + isAuthenticating: false, isAuthenticated: true, error: null, }); @@ -51,6 +60,7 @@ const createStaffCryptoStore = () => { const errorMessage = error instanceof Error ? error.message : "Authentication failed"; store.set({ crypto: null, + isAuthenticating: false, isAuthenticated: false, error: errorMessage, }); @@ -59,6 +69,24 @@ const createStaffCryptoStore = () => { } }, + async authenticateAndWait(staffId: string, tenantId: string): Promise { + const isAuthenticating = get(store).isAuthenticating; + if (isAuthenticating) { + // Wait for the ongoing authentication to complete + return new Promise((resolve) => { + const unsubscribe = store.subscribe((state) => { + if (!state.isAuthenticating) { + unsubscribe(); + resolve(state.isAuthenticated); + } + }); + }); + } else { + // Start a new authentication process + return await this.authenticate(staffId, tenantId); + } + }, + /** * Clear the staff crypto state and auth data */ @@ -66,6 +94,7 @@ const createStaffCryptoStore = () => { auth.clearPasskeyAuthData(); store.set({ crypto: null, + isAuthenticating: false, isAuthenticated: false, error: null, }); diff --git a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte index 7bbb8f7..4c58a0d 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte @@ -4,7 +4,6 @@ import { staffCrypto } from "$lib/stores/staff-crypto"; import { type TCalendarItem } from "$lib/types/calendar"; import Loader2Icon from "@lucide/svelte/icons/loader-2"; - import { onMount } from "svelte"; import { calendarStore } from "$lib/stores/calendar"; import { Button } from "$lib/components/ui/button"; import { m } from "$i18n/messages"; @@ -22,8 +21,14 @@ let decrypted = $state(); let error = $state(); - onMount(() => { - decrypt(); + $effect(() => { + if (!decrypted) { + if ($staffCrypto.isAuthenticated) { + decrypt(); + } else { + error = "Keys missing"; + } + } }); const errors = { @@ -31,6 +36,8 @@ }; const decrypt = async (retry = true) => { + error = undefined; + if (!item.appointment) { console.error("Unable to decrypt appointment data - no appointment data in item.", item.id); error = "Missing data"; @@ -64,8 +71,7 @@ const user = $auth.user; if (retry && user && user.tenantId) { console.warn("Staff crypto not initialized, retrying decryption..."); - // TODO: This should only be done once, not in every instance - await staffCrypto.authenticate(user.id, user.tenantId); + await staffCrypto.authenticateAndWait(user.id, user.tenantId); decrypt(false); return; } diff --git a/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte b/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte index 5133afe..9aaa403 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte @@ -74,7 +74,6 @@ }; onMount(() => { - console.log("item.start", item.start); if (mode.agentId && item.availableAgents) { const availableAgents = item.availableAgents.map((it) => it.id); if (availableAgents.includes(mode.agentId)) { diff --git a/src/routes/(pages)/login/login-form.svelte b/src/routes/(pages)/login/login-form.svelte index 036eacc..c9877e6 100644 --- a/src/routes/(pages)/login/login-form.svelte +++ b/src/routes/(pages)/login/login-form.svelte @@ -43,6 +43,10 @@ onResult: async (event) => { if (event.result.type === "success") { auth.setUser(event.result.data?.user); + + // Wait for cookies to be set before navigating to dashboard + await new Promise((resolve) => setTimeout(resolve, 100)); + await goto(resolve(ROUTES.DASHBOARD.MAIN)); } else { if ($formData.type === "passkey" && $formData.id === "") { From f550250917627b950647a588d35947ee48d96591 Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Tue, 8 Sep 2026 09:07:53 +0200 Subject: [PATCH 04/15] Feature flags are now an object. --- src/lib/const/tenants.ts | 4 ++-- src/lib/types/tenant.ts | 3 +++ src/lib/utils/tenants.ts | 11 ++++++++++- .../add-tenant-form/add-tenant-form.svelte | 4 ++-- .../edit-tenant-form/edit-tenant-form.svelte | 4 ++-- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/lib/const/tenants.ts b/src/lib/const/tenants.ts index 5484034..455a29f 100644 --- a/src/lib/const/tenants.ts +++ b/src/lib/const/tenants.ts @@ -1,5 +1,5 @@ export const SETUP_STATES_LIST = ["SETTINGS", "AGENTS", "CHANNELS", "STAFF", "READY"]; -export const TENANT_FEATURE_FLAGS = [ +export const TENANT_FEATURE_FLAGS = { // List of supported features flags -]; +} as const; diff --git a/src/lib/types/tenant.ts b/src/lib/types/tenant.ts index a2f95fd..cb42cd3 100644 --- a/src/lib/types/tenant.ts +++ b/src/lib/types/tenant.ts @@ -1,4 +1,5 @@ import type { SelectTenant } from "$lib/server/db/central-schema"; +import { TENANT_FEATURE_FLAGS } from "$lib/const/tenants"; export type TTenant = Pick< SelectTenant, @@ -28,3 +29,5 @@ export type TTenantSettings = Omit => { const response = await fetch("/api/admin/tenant", { @@ -23,3 +25,10 @@ export const isSetupStateDone = (tenant: TTenant, step: TTenant["setupState"]): const stepIndex = SETUP_STATES_LIST.indexOf(step); return curStateIndex > stepIndex; }; + +export const hasFeatureFlag = (flag: TTenantFeatureFlag): boolean => { + const curTenant = get(tenants).currentTenant; + if (!curTenant) return false; + + return curTenant.features.includes(flag) || false; +}; diff --git a/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte b/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte index 056ead5..113ffc6 100644 --- a/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte +++ b/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte @@ -83,12 +83,12 @@ {$formData.features.length > 0 ? $formData.features - .map((id) => TENANT_FEATURE_FLAGS.find((x) => x === id)) + .map((id) => Object.keys(TENANT_FEATURE_FLAGS).find((x) => x === id)) .join(", ") : m["tenants.add.features.placeholder"]()} - {#each TENANT_FEATURE_FLAGS as feature (feature)} + {#each Object.keys(TENANT_FEATURE_FLAGS) as feature (feature)} {feature} {/each} diff --git a/src/routes/(pages)/dashboard/tenants/(components)/edit-tenant-form/edit-tenant-form.svelte b/src/routes/(pages)/dashboard/tenants/(components)/edit-tenant-form/edit-tenant-form.svelte index 9ad0623..68204b5 100644 --- a/src/routes/(pages)/dashboard/tenants/(components)/edit-tenant-form/edit-tenant-form.svelte +++ b/src/routes/(pages)/dashboard/tenants/(components)/edit-tenant-form/edit-tenant-form.svelte @@ -66,12 +66,12 @@ {$formData.features.length > 0 ? $formData.features - .map((id) => TENANT_FEATURE_FLAGS.find((x) => x === id)) + .map((id) => Object.keys(TENANT_FEATURE_FLAGS).find((x) => x === id)) .join(", ") : m["tenants.add.features.placeholder"]()} - {#each TENANT_FEATURE_FLAGS as feature (feature)} + {#each Object.keys(TENANT_FEATURE_FLAGS) as feature (feature)} {feature} {/each} From d6facc7aba8b1bfeda2d3392b47d61aabbf0a7cf Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Wed, 9 Sep 2026 20:10:22 +0200 Subject: [PATCH 05/15] Review changes --- .../components/notification-item.svelte | 11 +++++++++-- .../calendar/(components)/AppointmentPreview.svelte | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte b/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte index 0dbdc95..925e7b4 100644 --- a/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte +++ b/src/lib/components/layouts/sidebar-layout/components/notification-item.svelte @@ -52,10 +52,10 @@ } }; - const decryptPayload = async () => { + const decryptPayload = async (retry = true) => { if (item.metaData?.encryptedPayload) { if (!$staffCrypto.isAuthenticated || !$staffCrypto.crypto) { - const maxWaitTime = 5000; // 5 seconds + const maxWaitTime = 5000; const startTime = Date.now(); while (!$staffCrypto.isAuthenticated && Date.now() - startTime < maxWaitTime) { @@ -63,6 +63,13 @@ } if (!$staffCrypto.isAuthenticated || !$staffCrypto.crypto) { + const user = $auth.user; + if (retry && user && user.tenantId) { + console.warn("Staff crypto not initialized, retrying notification item decryption..."); + await staffCrypto.authenticateAndWait(user.id, user.tenantId); + decryptPayload(false); + return; + } console.error("Staff crypto not initialized after waiting"); return; } diff --git a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte index 4c58a0d..49e912d 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/AppointmentPreview.svelte @@ -60,7 +60,7 @@ // Wait for crypto to be initialized (max 5 seconds) if (!$staffCrypto.isAuthenticated || !$staffCrypto.crypto) { - const maxWaitTime = 2000; + const maxWaitTime = 5000; const startTime = Date.now(); while (!$staffCrypto.isAuthenticated && Date.now() - startTime < maxWaitTime) { From cfcb892205c5db5ac7badde951db78e8e0be59d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 04:13:50 +0000 Subject: [PATCH 06/15] Bump @vitest/mocker and vitest Bumps [@vitest/mocker](https://github.com/vitest-dev/vitest/tree/HEAD/packages/mocker) to 4.1.11 and updates ancestor dependency [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest). These dependencies need to be updated together. Updates `@vitest/mocker` from 4.1.9 to 4.1.11 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/mocker) Updates `vitest` from 4.1.9 to 4.1.11 - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.11/packages/vitest) --- updated-dependencies: - dependency-name: "@vitest/mocker" dependency-version: 4.1.11 dependency-type: indirect - dependency-name: vitest dependency-version: 4.1.11 dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- package-lock.json | 96 +++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d07139..4bf97df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,7 +71,7 @@ "typescript-eslint": "^8.34.1", "vaul-svelte": "^1.0.0-next.7", "vite": "^6.4.2", - "vitest": "^4.1.9" + "vitest": "^4.1.11" } }, "node_modules/@adobe/css-tools": { @@ -3500,16 +3500,16 @@ } }, "node_modules/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -3518,13 +3518,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.9", + "@vitest/spy": "4.1.11", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -3545,9 +3545,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { @@ -3558,13 +3558,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.11", "pathe": "^2.0.3" }, "funding": { @@ -3572,14 +3572,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -3588,9 +3588,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", "funding": { @@ -3598,13 +3598,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", + "@vitest/pretty-format": "4.1.11", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -7610,9 +7610,9 @@ } }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -8535,19 +8535,19 @@ } }, "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -8575,12 +8575,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", + "@vitest/browser-playwright": "4.1.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/package.json b/package.json index 37271e0..256f8cb 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "typescript-eslint": "^8.34.1", "vaul-svelte": "^1.0.0-next.7", "vite": "^6.4.2", - "vitest": "^4.1.9" + "vitest": "^4.1.11" }, "dependencies": { "@noble/hashes": "^1.8.0", From f1722827c0660eeebdf1012bd3f41ed44014e115 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 10 Sep 2026 18:43:17 +0000 Subject: [PATCH 07/15] Bump joi from 17.13.4 to 17.13.7 Bumps [joi](https://github.com/hapijs/joi) from 17.13.4 to 17.13.7. - [Commits](https://github.com/hapijs/joi/compare/v17.13.4...v17.13.7) --- updated-dependencies: - dependency-name: joi dependency-version: 17.13.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4d07139..16a4a2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5431,9 +5431,9 @@ } }, "node_modules/joi": { - "version": "17.13.4", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", - "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", + "version": "17.13.7", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.7.tgz", + "integrity": "sha512-MF80Dm5Y2veNy8QWVx9Bj3ui4mo7+VPSPsR1M+oaHXV0Gx6zGX9a2F+OZG3Blby9tOlzU9Rs5FUimlEhbKtfnQ==", "dev": true, "license": "BSD-3-Clause", "optional": true, From af38a088922eb23d107df9a1ca0f001c1f8d5cf6 Mon Sep 17 00:00:00 2001 From: Karl Ludwig Weise Date: Thu, 10 Sep 2026 22:19:16 +0200 Subject: [PATCH 08/15] Added inactivity modal with proper logout --- project.inlang/messages/de.json | 5 + project.inlang/messages/en.json | 5 + .../responsive-dialog.svelte | 136 ++++++++++-------- src/lib/stores/auth.ts | 29 +++- .../(components)/EmailReminder.svelte | 3 + .../(components)/InactivityWarning.svelte | 33 +++++ src/routes/(pages)/dashboard/+layout.svelte | 16 +++ .../add-absence-form/add-absence-form.svelte | 6 +- .../delete-absence-form.svelte | 2 + .../edit-absence-form.svelte | 18 +-- .../change-passphrase-form.svelte | 1 + .../account-settings-form.svelte | 1 + .../add-passkey-form/add-passkey-form.svelte | 3 + .../delete-passkey-form.svelte | 2 + .../edit-passkey-form.svelte | 2 + .../add-agent-form/add-agent-form.svelte | 6 +- .../delete-agent-form.svelte | 2 + .../edit-agent-form/edit-agent-form.svelte | 6 +- .../calendar/(components)/Appointment.svelte | 8 ++ .../(components)/CalendarFilters.svelte | 5 + .../(components)/CalendarHeader.svelte | 5 + .../(components)/MoveAppointment.svelte | 4 +- .../add-appointment/AddAppointment.svelte | 3 + .../add-channel-form/add-channel-form.svelte | 2 + .../delete-channel-form.svelte | 6 +- .../edit-channel-form.svelte | 2 + .../pause-channel-form.svelte | 2 + .../edit-settings-form.svelte | 4 +- .../add-staff-member-form.svelte | 2 + .../delete-staff-member-form.svelte | 4 +- .../edit-staff-member-form.svelte | 2 + .../grant-access-form.svelte | 1 + .../resend-invite-form.svelte | 2 + .../add-tenant-form/add-tenant-form.svelte | 8 +- .../delete-tenant-form.svelte | 1 + .../edit-tenant-form/edit-tenant-form.svelte | 2 + src/routes/(pages)/login/login-form.svelte | 2 +- 37 files changed, 255 insertions(+), 86 deletions(-) create mode 100644 src/routes/(pages)/dashboard/(components)/InactivityWarning.svelte diff --git a/project.inlang/messages/de.json b/project.inlang/messages/de.json index 034f902..8bbdb6d 100644 --- a/project.inlang/messages/de.json +++ b/project.inlang/messages/de.json @@ -465,6 +465,11 @@ "cancel": "Ich will nur Einstellungen ändern", "success": "Kryptografische Schlüssel geladen", "error": "Fehler beim Laden der kryptografischen Schlüssel" + }, + "inactiveSession": { + "title": "Bist du noch da?", + "description": "Du wirst in Kürze automatisch abgemeldet.", + "cancel": "Ich bin noch da" } }, "agents": { diff --git a/project.inlang/messages/en.json b/project.inlang/messages/en.json index 4a32832..16e6970 100644 --- a/project.inlang/messages/en.json +++ b/project.inlang/messages/en.json @@ -473,6 +473,11 @@ "cancel": "I just want to change settings", "success": "Cryptography keys loaded", "error": "Failed to load cryptography keys" + }, + "inactiveSession": { + "title": "Are you still here?", + "description": "You will be automatically logged out soon.", + "cancel": "I am still here" } }, "agents": { diff --git a/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte b/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte index 21fde88..b90e4b5 100644 --- a/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte +++ b/src/lib/components/ui/responsive-dialog/responsive-dialog.svelte @@ -152,41 +152,46 @@ {/if} {/if} - 0 ? "[&>button:last-child]:hidden" : "", - )} - onOpenAutoFocus={(e) => e.preventDefault()} - escapeKeydownBehavior={isDismissable === false ? "ignore" : "close"} - interactOutsideBehavior={isDismissable === false ? "ignore" : "close"} - showCloseButton={isDismissable === true} - > - -
- {title} - {#if description} - - {description} - - {/if} -
- {#if actions && actions.length > 0} -
- {@render actionsSnippet?.()} - - - + + + 0 ? "[&>button:last-child]:hidden" : "", + )} + onOpenAutoFocus={(e) => e.preventDefault()} + escapeKeydownBehavior={isDismissable === false ? "ignore" : "close"} + interactOutsideBehavior={isDismissable === false ? "ignore" : "close"} + showCloseButton={isDismissable === true} + > + +
+ {title} + {#if description} + + {description} + + {/if}
- {/if} -
- -
- {@render children?.()} -
-
-
+ {#if actions && actions.length > 0} +
+ {@render actionsSnippet?.()} + + + +
+ {/if} + + +
+ {@render children?.()} +
+
+ +
{:else} @@ -199,33 +204,40 @@ {/if} {/if} - e.preventDefault()} - escapeKeydownBehavior={isDismissable === false ? "ignore" : "close"} - interactOutsideBehavior={isDismissable === false ? "ignore" : "close"} - > - -
- {title} - {#if description} - - {description} - - {/if} -
-
- {@render actionsSnippet?.()} -
-
- - {@render children?.()} - - {#if isDismissable !== false} - - {m.cancel()} - - {/if} -
+ + + e.preventDefault()} + escapeKeydownBehavior={isDismissable === false ? "ignore" : "close"} + interactOutsideBehavior={isDismissable === false ? "ignore" : "close"} + > + +
+ {title} + {#if description} + + {description} + + {/if} +
+
+ {@render actionsSnippet?.()} +
+
+ + {@render children?.()} + + {#if isDismissable !== false} + + {m.cancel()} + + {/if} +
+
{/if} diff --git a/src/lib/stores/auth.ts b/src/lib/stores/auth.ts index cffbb43..859d853 100644 --- a/src/lib/stores/auth.ts +++ b/src/lib/stores/auth.ts @@ -1,7 +1,10 @@ import { browser } from "$app/environment"; +import { goto } from "$app/navigation"; +import { resolve } from "$app/paths"; import type { SupportedLocale } from "$lib/const/locales"; +import { ROUTES } from "$lib/const/routes"; import type { UserRole } from "$lib/server/auth/authorization-service"; -import { writable } from "svelte/store"; +import { get, writable } from "svelte/store"; export interface PasskeyAuthData { authenticatorData: string; @@ -15,6 +18,8 @@ export interface AuthState { refreshPromise: Promise | null; user?: AuthStateUser; passkeyAuthData?: PasskeyAuthData; + lastActive?: Date | undefined; + isInactive?: boolean; } export type AuthStateUser = { @@ -31,6 +36,7 @@ function createAuthStore() { const store = writable({ isAuthenticated: false, refreshPromise: null, + isInactive: false, }); return { @@ -64,6 +70,7 @@ function createAuthStore() { refreshPromise: null, user: undefined, passkeyAuthData: undefined, + lastActive: undefined, }); }, setPasskeyAuthData: (data: PasskeyAuthData) => { @@ -98,6 +105,26 @@ function createAuthStore() { unsubscribe(); return authState!.user?.tenantId || null; }, + refreshLastActive: () => { + store.update((state) => ({ ...state, lastActive: new Date(), isInactive: false })); + }, + checkLastActive: () => { + const lastActive = get(store).lastActive; + if (!lastActive) return; + + const now = new Date(); + const diff = now.getTime() - lastActive.getTime(); + + // Logs user out after 15 minutes of inactivity + if (diff > 15 * 60 * 1000) { + goto(resolve(ROUTES.LOGOUT)); + } + + // Triggers inactivity modal after 10 minutes + if (diff > 10 * 60 * 1000) { + store.update((state) => ({ ...state, isInactive: true })); + } + }, waitForRefresh: async () => { let authState: AuthState; const unsubscribe = store.subscribe((state) => { diff --git a/src/routes/(pages)/dashboard/(components)/EmailReminder.svelte b/src/routes/(pages)/dashboard/(components)/EmailReminder.svelte index 0eb9f17..20523c6 100644 --- a/src/routes/(pages)/dashboard/(components)/EmailReminder.svelte +++ b/src/routes/(pages)/dashboard/(components)/EmailReminder.svelte @@ -10,6 +10,7 @@ import { onMount } from "svelte"; import { getNextAppointments, sendAppointmentReminders } from "./utils"; import { m } from "$i18n/messages"; + import { auth } from "$lib/stores/auth"; let status: "init" | "loading" | "sending" | "success" | "error" = $state("init"); let appointments: TEmailAppointmentReminder[] = $state([]); @@ -27,6 +28,8 @@ // Also gives staff crypto time to load, if not already loaded await new Promise((resolve) => setTimeout(resolve, 1000)); + await auth.waitForRefresh(); + const allAppointments = await getNextAppointments(tenantId); const decryptPromises = allAppointments.map(async (appointment) => { if ( diff --git a/src/routes/(pages)/dashboard/(components)/InactivityWarning.svelte b/src/routes/(pages)/dashboard/(components)/InactivityWarning.svelte new file mode 100644 index 0000000..60fe63f --- /dev/null +++ b/src/routes/(pages)/dashboard/(components)/InactivityWarning.svelte @@ -0,0 +1,33 @@ + + + +
+ + +
+
diff --git a/src/routes/(pages)/dashboard/+layout.svelte b/src/routes/(pages)/dashboard/+layout.svelte index 9ff1275..44b937b 100644 --- a/src/routes/(pages)/dashboard/+layout.svelte +++ b/src/routes/(pages)/dashboard/+layout.svelte @@ -9,8 +9,11 @@ import { staff } from "$lib/stores/staff"; import { QueryClient, QueryClientProvider } from "@tanstack/svelte-query"; import { browser } from "$app/environment"; + import { page } from "$app/state"; import MissingStaffCrypto from "./(components)/MissingStaffCrypto.svelte"; import { openDialog } from "$lib/components/ui/responsive-dialog"; + import { clock } from "$lib/stores/time"; + import InactivityWarning from "./(components)/InactivityWarning.svelte"; let { data, children }: LayoutProps = $props(); @@ -61,6 +64,18 @@ }; }); + $effect(() => { + if (page.url.pathname) { + auth.refreshLastActive(); + } + }); + + $effect(() => { + if ($clock) { + auth.checkLastActive(); + } + }); + const updateStores = async () => { staff.load(); notifications.load(); @@ -92,4 +107,5 @@ {@render children()} + diff --git a/src/routes/(pages)/dashboard/absences/(components)/add-absence-form/add-absence-form.svelte b/src/routes/(pages)/dashboard/absences/(components)/add-absence-form/add-absence-form.svelte index e075f6e..87f67c9 100644 --- a/src/routes/(pages)/dashboard/absences/(components)/add-absence-form/add-absence-form.svelte +++ b/src/routes/(pages)/dashboard/absences/(components)/add-absence-form/add-absence-form.svelte @@ -6,8 +6,11 @@ import { InputDateTime } from "$lib/components/ui/input-date-time"; import RadioCards from "$lib/components/ui/radio-cards/radio-cards.svelte"; import * as Select from "$lib/components/ui/select"; + import { times } from "$lib/components/ui/slot-template/utils"; import { Text } from "$lib/components/ui/typography"; import { agents as agentsStore } from "$lib/stores/agents"; + import { auth } from "$lib/stores/auth"; + import { cn } from "$lib/utils"; import { getDefaultEndTime, getDefaultStartTime, @@ -24,8 +27,6 @@ import type { AbsenceType } from "../types"; import { reasons, types } from "../utils"; import { formSchema } from "./schema"; - import { cn } from "$lib/utils"; - import { times } from "$lib/components/ui/slot-template/utils"; let { done }: { done: () => void } = $props(); @@ -46,6 +47,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["absences.add.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/absences/(components)/delete-absence-form/delete-absence-form.svelte b/src/routes/(pages)/dashboard/absences/(components)/delete-absence-form/delete-absence-form.svelte index 2ee5784..ec8a33f 100644 --- a/src/routes/(pages)/dashboard/absences/(components)/delete-absence-form/delete-absence-form.svelte +++ b/src/routes/(pages)/dashboard/absences/(components)/delete-absence-form/delete-absence-form.svelte @@ -4,6 +4,7 @@ import { Input } from "$lib/components/ui/input"; import { Text } from "$lib/components/ui/typography"; import { agents as agentsStore } from "$lib/stores/agents"; + import { auth } from "$lib/stores/auth"; import type { TAbsence } from "$lib/types/absence"; import { toast } from "svelte-sonner"; import { superForm } from "sveltekit-superforms"; @@ -20,6 +21,7 @@ { validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["absences.delete.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/absences/(components)/edit-absence-form/edit-absence-form.svelte b/src/routes/(pages)/dashboard/absences/(components)/edit-absence-form/edit-absence-form.svelte index f52c617..261c148 100644 --- a/src/routes/(pages)/dashboard/absences/(components)/edit-absence-form/edit-absence-form.svelte +++ b/src/routes/(pages)/dashboard/absences/(components)/edit-absence-form/edit-absence-form.svelte @@ -4,14 +4,13 @@ import * as Form from "$lib/components/ui/form"; import { Input } from "$lib/components/ui/input"; import { InputDateTime } from "$lib/components/ui/input-date-time"; + import { RadioCards } from "$lib/components/ui/radio-cards"; import * as Select from "$lib/components/ui/select"; + import { times } from "$lib/components/ui/slot-template/utils"; import { agents as agentsStore } from "$lib/stores/agents"; + import { auth } from "$lib/stores/auth"; import type { TAbsence } from "$lib/types/absence"; - import { toast } from "svelte-sonner"; - import { superForm } from "sveltekit-superforms"; - import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; - import { formSchema } from "."; - import { reasons, types } from "../utils"; + import { cn } from "$lib/utils"; import { timeLocalWithoutOffsetToUTC, timeUTCToLocalWithoutOffset, @@ -19,10 +18,12 @@ toWeekdaysLabel, weekdays, } from "$lib/utils/datetime"; + import { toast } from "svelte-sonner"; import { SvelteDate } from "svelte/reactivity"; - import { times } from "$lib/components/ui/slot-template/utils"; - import { RadioCards } from "$lib/components/ui/radio-cards"; - import { cn } from "$lib/utils"; + import { superForm } from "sveltekit-superforms"; + import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; + import { formSchema } from "."; + import { reasons, types } from "../utils"; let { entity, done }: { entity: TAbsence; done: () => void } = $props(); @@ -45,6 +46,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["absences.edit.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/account/change-passphrase/(components)/change-passphrase-form/change-passphrase-form.svelte b/src/routes/(pages)/dashboard/account/change-passphrase/(components)/change-passphrase-form/change-passphrase-form.svelte index 2546ae6..4262d7d 100644 --- a/src/routes/(pages)/dashboard/account/change-passphrase/(components)/change-passphrase-form/change-passphrase-form.svelte +++ b/src/routes/(pages)/dashboard/account/change-passphrase/(components)/change-passphrase-form/change-passphrase-form.svelte @@ -19,6 +19,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["account.change-passphrase.success"]()); } else if (event.result.type === "failure") { diff --git a/src/routes/(pages)/dashboard/account/general/(components)/account-settings-form/account-settings-form.svelte b/src/routes/(pages)/dashboard/account/general/(components)/account-settings-form/account-settings-form.svelte index 2c213db..6ff0a08 100644 --- a/src/routes/(pages)/dashboard/account/general/(components)/account-settings-form/account-settings-form.svelte +++ b/src/routes/(pages)/dashboard/account/general/(components)/account-settings-form/account-settings-form.svelte @@ -21,6 +21,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["account.general.success"]()); } else if (event.result.type === "failure") { diff --git a/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte b/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte index ab55ad4..530ad5e 100644 --- a/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte +++ b/src/routes/(pages)/dashboard/account/passkeys/(components)/add-passkey-form/add-passkey-form.svelte @@ -38,6 +38,9 @@ }, { validators: zodClient(formSchema), + onResult: () => { + auth.refreshLastActive(); + }, onChange: (event) => { if (event.paths.includes("email")) { setProperPasskeyState(); diff --git a/src/routes/(pages)/dashboard/account/passkeys/(components)/delete-passkey-form/delete-passkey-form.svelte b/src/routes/(pages)/dashboard/account/passkeys/(components)/delete-passkey-form/delete-passkey-form.svelte index e1a5d9f..877cfc9 100644 --- a/src/routes/(pages)/dashboard/account/passkeys/(components)/delete-passkey-form/delete-passkey-form.svelte +++ b/src/routes/(pages)/dashboard/account/passkeys/(components)/delete-passkey-form/delete-passkey-form.svelte @@ -5,6 +5,7 @@ import { Input } from "$lib/components/ui/input"; import { TranslationWithComponent } from "$lib/components/ui/translation-with-component"; import { Text } from "$lib/components/ui/typography"; + import { auth } from "$lib/stores/auth"; import { toast } from "svelte-sonner"; import { superForm } from "sveltekit-superforms"; import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; @@ -29,6 +30,7 @@ ), ), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["account.passkeys.delete.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/account/passkeys/(components)/edit-passkey-form/edit-passkey-form.svelte b/src/routes/(pages)/dashboard/account/passkeys/(components)/edit-passkey-form/edit-passkey-form.svelte index 0c64b61..4d70f01 100644 --- a/src/routes/(pages)/dashboard/account/passkeys/(components)/edit-passkey-form/edit-passkey-form.svelte +++ b/src/routes/(pages)/dashboard/account/passkeys/(components)/edit-passkey-form/edit-passkey-form.svelte @@ -2,6 +2,7 @@ import { m } from "$i18n/messages.js"; import * as Form from "$lib/components/ui/form"; import { Input } from "$lib/components/ui/input"; + import { auth } from "$lib/stores/auth"; import type { RedactedPasskeyHydrated } from "$lib/types/passkeys"; import { untrack } from "svelte"; import { toast } from "svelte-sonner"; @@ -20,6 +21,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["account.passkeys.edit.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/agents/(components)/add-agent-form/add-agent-form.svelte b/src/routes/(pages)/dashboard/agents/(components)/add-agent-form/add-agent-form.svelte index 27a92e9..b26d7df 100644 --- a/src/routes/(pages)/dashboard/agents/(components)/add-agent-form/add-agent-form.svelte +++ b/src/routes/(pages)/dashboard/agents/(components)/add-agent-form/add-agent-form.svelte @@ -5,13 +5,14 @@ import { InputCroppedImageBlob } from "$lib/components/ui/input-cropped-image-blob"; import { LanguageTabs } from "$lib/components/ui/language-tabs"; import { Textarea } from "$lib/components/ui/textarea"; + import { auth } from "$lib/stores/auth"; + import { tenants } from "$lib/stores/tenants"; import ItemIcon from "@lucide/svelte/icons/user-star"; import { toast } from "svelte-sonner"; + import { get } from "svelte/store"; import { superForm } from "sveltekit-superforms"; import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { formSchema } from "."; - import { tenants } from "$lib/stores/tenants"; - import { get } from "svelte/store"; let { done }: { done: () => void } = $props(); @@ -29,6 +30,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["agents.add.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/agents/(components)/delete-agent-form/delete-agent-form.svelte b/src/routes/(pages)/dashboard/agents/(components)/delete-agent-form/delete-agent-form.svelte index fdc2b4f..b923114 100644 --- a/src/routes/(pages)/dashboard/agents/(components)/delete-agent-form/delete-agent-form.svelte +++ b/src/routes/(pages)/dashboard/agents/(components)/delete-agent-form/delete-agent-form.svelte @@ -11,6 +11,7 @@ import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { z } from "zod"; import { formSchema } from "."; + import { auth } from "$lib/stores/auth"; let { entity, done }: { entity: TAgent; done: () => void } = $props(); @@ -30,6 +31,7 @@ ), ), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["agents.delete.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/agents/(components)/edit-agent-form/edit-agent-form.svelte b/src/routes/(pages)/dashboard/agents/(components)/edit-agent-form/edit-agent-form.svelte index a78218f..23c5a72 100644 --- a/src/routes/(pages)/dashboard/agents/(components)/edit-agent-form/edit-agent-form.svelte +++ b/src/routes/(pages)/dashboard/agents/(components)/edit-agent-form/edit-agent-form.svelte @@ -5,14 +5,15 @@ import { InputCroppedImageBlob } from "$lib/components/ui/input-cropped-image-blob"; import { LanguageTabs } from "$lib/components/ui/language-tabs"; import { Textarea } from "$lib/components/ui/textarea"; + import { auth } from "$lib/stores/auth"; + import { tenants } from "$lib/stores/tenants"; import type { TAgent } from "$lib/types/agent"; import ItemIcon from "@lucide/svelte/icons/user-star"; import { toast } from "svelte-sonner"; + import { get } from "svelte/store"; import { superForm } from "sveltekit-superforms"; import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { formSchema } from "."; - import { tenants } from "$lib/stores/tenants"; - import { get } from "svelte/store"; let { entity, done }: { entity: TAgent; done: () => void } = $props(); @@ -33,6 +34,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["agents.edit.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/calendar/(components)/Appointment.svelte b/src/routes/(pages)/dashboard/calendar/(components)/Appointment.svelte index 1d43e3e..c9bd1fb 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/Appointment.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/Appointment.svelte @@ -6,11 +6,13 @@ import { ResponsiveDialog } from "$lib/components/ui/responsive-dialog"; import { type SupportedLocale } from "$lib/const/locales"; import { agents as agentsStore } from "$lib/stores/agents"; + import { auth } from "$lib/stores/auth"; import { type CurAppointmentItem } from "$lib/stores/calendar"; import { channels as channelsStore } from "$lib/stores/channels"; import type { TAppointmentFilter, TCalendarMode } from "$lib/types/calendar"; import { getCurrentTranlslation } from "$lib/utils/localizations"; import { CalendarPlus, Move, Trash2 } from "@lucide/svelte"; + import { onMount } from "svelte"; import { toast } from "svelte-sonner"; import { cancelAppointment, confirmAppointment, denyAppointment } from "./utils"; @@ -41,6 +43,7 @@ let isDeleting = $state(false); const denyItem = async () => { + auth.refreshLastActive(); const proceed = confirm( `${m["calendar.notificationHint"]()} ${m["calendar.denyAppointment.confirm"]()}`, ); @@ -64,6 +67,7 @@ }; const confirmItem = async () => { + auth.refreshLastActive(); isConfirming = true; const success = await confirmAppointment({ tenant: tenantId, @@ -80,6 +84,10 @@ } isConfirming = false; }; + + onMount(() => { + auth.refreshLastActive(); + }); !x.archived)); const zoom = (direction: number) => { + auth.refreshLastActive(); const currentIndex = CALENDAR_ZOOM_STEPS.indexOf(scale); if (currentIndex === -1) return; const nextIndex = currentIndex + direction; @@ -60,11 +62,13 @@ }; const changeView = (newView: CalendarView) => { + auth.refreshLastActive(); view = newView; document.cookie = `calendarView=${newView}; path=/; max-age=${60 * 60 * 24 * 365}; SameSite=Strict`; }; const clearFilters = () => { + auth.refreshLastActive(); shownAppointments = "all"; shownChannels = []; shownAgents = []; @@ -239,6 +243,7 @@ {shownAgents} {shownChannels} onSelectDay={(v: DateValue | undefined) => { + auth.refreshLastActive(); sidebarStore.setCalendarExpanded(!sidebar.isCalendarExpanded); if (view === "week-workdays" && v && isWeekend(v, getLocale())) { changeView("week"); diff --git a/src/routes/(pages)/dashboard/calendar/(components)/CalendarHeader.svelte b/src/routes/(pages)/dashboard/calendar/(components)/CalendarHeader.svelte index d0f0c71..8bb3a00 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/CalendarHeader.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/CalendarHeader.svelte @@ -3,6 +3,7 @@ import { getLocale } from "$i18n/runtime"; import { Button, buttonVariants } from "$lib/components/ui/button"; import * as Popover from "$lib/components/ui/popover/index.js"; + import { auth } from "$lib/stores/auth"; import type { TAppointmentFilter, TCalendarMode } from "$lib/types/calendar"; import { cn } from "$lib/utils"; import { @@ -37,6 +38,7 @@ let isWeekView = $derived(view !== "day"); const prev = () => { + auth.refreshLastActive(); const nextDate = new CalendarDate( selectedDate.year, selectedDate.month, @@ -48,6 +50,7 @@ }; const next = () => { + auth.refreshLastActive(); const nextDate = new CalendarDate(selectedDate.year, selectedDate.month, selectedDate.day).add({ days: isWeekView ? 7 : 1, }); @@ -55,10 +58,12 @@ }; const setToToday = () => { + auth.refreshLastActive(); selectedDate = today(getLocalTimeZone()); }; const onSelectDay = (v: DateValue | undefined) => { + auth.refreshLastActive(); open = false; if (view === "week-workdays" && v && isWeekend(v, getLocale())) { view = "week"; diff --git a/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte b/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte index 9aaa403..6f83696 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/MoveAppointment.svelte @@ -4,6 +4,7 @@ import Button from "$lib/components/ui/button/button.svelte"; import { ResponsiveDialog } from "$lib/components/ui/responsive-dialog"; import { Text } from "$lib/components/ui/typography"; + import { auth } from "$lib/stores/auth"; import { channels as channelsStore } from "$lib/stores/channels"; import type { TCalendarModeMove, TCalendarSlot } from "$lib/types/calendar"; import { utcToLocalWithoutDST } from "$lib/utils/datetime"; @@ -66,14 +67,15 @@ }); if (success) { toast.success(m["calendar.moveAppointment.success"]()); - isSubmitting = false; updateCalendar(); } else { toast.error(m["calendar.moveAppointment.error"]()); } + isSubmitting = false; }; onMount(() => { + auth.refreshLastActive(); if (mode.agentId && item.availableAgents) { const availableAgents = item.availableAgents.map((it) => it.id); if (availableAgents.includes(mode.agentId)) { diff --git a/src/routes/(pages)/dashboard/calendar/(components)/add-appointment/AddAppointment.svelte b/src/routes/(pages)/dashboard/calendar/(components)/add-appointment/AddAppointment.svelte index dad442b..64165f4 100644 --- a/src/routes/(pages)/dashboard/calendar/(components)/add-appointment/AddAppointment.svelte +++ b/src/routes/(pages)/dashboard/calendar/(components)/add-appointment/AddAppointment.svelte @@ -16,6 +16,7 @@ import SelectAgent from "./SelectAgent.svelte"; import Summary from "./Summary.svelte"; import type { TAddAppointment, TAddAppointmentStep } from "./types"; + import { auth } from "$lib/stores/auth"; let { tenantId, @@ -46,6 +47,7 @@ let isSubmitting = $state(false); const proceed = (data: TAddAppointment) => { + auth.refreshLastActive(); switch (true) { case !data.agentId: { newAppointment = { @@ -77,6 +79,7 @@ }; const addAppointment = async () => { + auth.refreshLastActive(); if ( newAppointment.name && newAppointment.agentId && diff --git a/src/routes/(pages)/dashboard/channels/(components)/add-channel-form/add-channel-form.svelte b/src/routes/(pages)/dashboard/channels/(components)/add-channel-form/add-channel-form.svelte index e4c6d4a..7a52c52 100644 --- a/src/routes/(pages)/dashboard/channels/(components)/add-channel-form/add-channel-form.svelte +++ b/src/routes/(pages)/dashboard/channels/(components)/add-channel-form/add-channel-form.svelte @@ -10,6 +10,7 @@ import { Textarea } from "$lib/components/ui/textarea"; import { Headline, Text } from "$lib/components/ui/typography"; import { agents as agentsStore } from "$lib/stores/agents"; + import { auth } from "$lib/stores/auth"; import { staff as staffStore } from "$lib/stores/staff"; import { tenants } from "$lib/stores/tenants"; import { toast } from "svelte-sonner"; @@ -44,6 +45,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["channels.add.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/channels/(components)/delete-channel-form/delete-channel-form.svelte b/src/routes/(pages)/dashboard/channels/(components)/delete-channel-form/delete-channel-form.svelte index 13d4aef..697e5fd 100644 --- a/src/routes/(pages)/dashboard/channels/(components)/delete-channel-form/delete-channel-form.svelte +++ b/src/routes/(pages)/dashboard/channels/(components)/delete-channel-form/delete-channel-form.svelte @@ -5,13 +5,14 @@ import { Input } from "$lib/components/ui/input"; import { TranslationWithComponent } from "$lib/components/ui/translation-with-component"; import { Text } from "$lib/components/ui/typography"; + import { auth } from "$lib/stores/auth"; + import type { TChannelWithFullAgents } from "$lib/types/channel"; + import { getCurrentTranlslation } from "$lib/utils/localizations"; import { toast } from "svelte-sonner"; import { superForm } from "sveltekit-superforms"; import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { z } from "zod"; import { formSchema } from "."; - import type { TChannelWithFullAgents } from "$lib/types/channel"; - import { getCurrentTranlslation } from "$lib/utils/localizations"; let { entity, done }: { entity: TChannelWithFullAgents; done: () => void } = $props(); @@ -31,6 +32,7 @@ ), ), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["channels.delete.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/channels/(components)/edit-channel-form/edit-channel-form.svelte b/src/routes/(pages)/dashboard/channels/(components)/edit-channel-form/edit-channel-form.svelte index edfa9cb..5ba6ca8 100644 --- a/src/routes/(pages)/dashboard/channels/(components)/edit-channel-form/edit-channel-form.svelte +++ b/src/routes/(pages)/dashboard/channels/(components)/edit-channel-form/edit-channel-form.svelte @@ -19,6 +19,7 @@ import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { formSchema } from "."; import { DEFAULT_SLOT_TEMPLATE } from "../utils"; + import { auth } from "$lib/stores/auth"; let { entity, done }: { entity: TChannelWithFullAgents; done: () => void } = $props(); const agents = $derived($agentsStore.agents ?? []); @@ -47,6 +48,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["channels.edit.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/channels/(components)/pause-channel-form/pause-channel-form.svelte b/src/routes/(pages)/dashboard/channels/(components)/pause-channel-form/pause-channel-form.svelte index 4fe3c03..012e945 100644 --- a/src/routes/(pages)/dashboard/channels/(components)/pause-channel-form/pause-channel-form.svelte +++ b/src/routes/(pages)/dashboard/channels/(components)/pause-channel-form/pause-channel-form.svelte @@ -12,6 +12,7 @@ import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { formSchema } from "."; import { Checkbox } from "$lib/components/ui/checkbox"; + import { auth } from "$lib/stores/auth"; let { entity, done }: { entity: TChannelWithFullAgents; done: () => void } = $props(); @@ -21,6 +22,7 @@ { validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success( $formData.pause ? m["channels.pause.success"]() : m["channels.unpause.success"](), diff --git a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form.svelte b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form.svelte index b76b9e6..9c76566 100644 --- a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form.svelte +++ b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form.svelte @@ -9,13 +9,14 @@ import * as Select from "$lib/components/ui/select"; import { Textarea } from "$lib/components/ui/textarea"; import { supportedLocales, translatedLocales } from "$lib/const/locales"; + import { auth } from "$lib/stores/auth"; + import { tenants } from "$lib/stores/tenants"; import type { TTenantSettings } from "$lib/types/tenant"; import DefaultOrgIcon from "@lucide/svelte/icons/landmark"; import { toast } from "svelte-sonner"; import { superForm } from "sveltekit-superforms"; import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { formSchema } from "./schema"; - import { tenants } from "$lib/stores/tenants"; let { entity }: { entity: TTenantSettings } = $props(); @@ -57,6 +58,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { tenants.reload(); toast.success(m["tenants.edit.success"]()); diff --git a/src/routes/(pages)/dashboard/staff/(components)/add-staff-member-form/add-staff-member-form.svelte b/src/routes/(pages)/dashboard/staff/(components)/add-staff-member-form/add-staff-member-form.svelte index 4aafff3..72efb7d 100644 --- a/src/routes/(pages)/dashboard/staff/(components)/add-staff-member-form/add-staff-member-form.svelte +++ b/src/routes/(pages)/dashboard/staff/(components)/add-staff-member-form/add-staff-member-form.svelte @@ -4,6 +4,7 @@ import { Input } from "$lib/components/ui/input"; import * as Select from "$lib/components/ui/select"; import { supportedLocales, translatedLocales } from "$lib/const/locales"; + import { auth } from "$lib/stores/auth"; import { tenants } from "$lib/stores/tenants"; import type { TStaff } from "$lib/types/users"; import { toast } from "svelte-sonner"; @@ -29,6 +30,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["staff.add.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/staff/(components)/delete-staff-member-form/delete-staff-member-form.svelte b/src/routes/(pages)/dashboard/staff/(components)/delete-staff-member-form/delete-staff-member-form.svelte index 57ee3b8..04c6789 100644 --- a/src/routes/(pages)/dashboard/staff/(components)/delete-staff-member-form/delete-staff-member-form.svelte +++ b/src/routes/(pages)/dashboard/staff/(components)/delete-staff-member-form/delete-staff-member-form.svelte @@ -5,12 +5,13 @@ import { Input } from "$lib/components/ui/input"; import { TranslationWithComponent } from "$lib/components/ui/translation-with-component"; import { Text } from "$lib/components/ui/typography"; + import { auth } from "$lib/stores/auth"; + import type { TStaff } from "$lib/types/users"; import { toast } from "svelte-sonner"; import { superForm } from "sveltekit-superforms"; import { zod4Client as zodClient } from "sveltekit-superforms/adapters"; import { z } from "zod"; import { formSchema } from "."; - import type { TStaff } from "$lib/types/users"; let { entity, done }: { entity: TStaff; done: () => void } = $props(); @@ -30,6 +31,7 @@ ), ), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["staff.delete.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/staff/(components)/edit-staff-member-form/edit-staff-member-form.svelte b/src/routes/(pages)/dashboard/staff/(components)/edit-staff-member-form/edit-staff-member-form.svelte index d5aba0f..4c3ae79 100644 --- a/src/routes/(pages)/dashboard/staff/(components)/edit-staff-member-form/edit-staff-member-form.svelte +++ b/src/routes/(pages)/dashboard/staff/(components)/edit-staff-member-form/edit-staff-member-form.svelte @@ -10,6 +10,7 @@ import { formSchema } from "."; import RolePermissions from "../role-permissions.svelte"; import { roles } from "../utils"; + import { auth } from "$lib/stores/auth"; let { entity, done }: { entity: TStaff; done: () => void } = $props(); @@ -27,6 +28,7 @@ dataType: "json", validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["staff.edit.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/staff/(components)/grant-access-form/grant-access-form.svelte b/src/routes/(pages)/dashboard/staff/(components)/grant-access-form/grant-access-form.svelte index 1bb30db..e48ac6c 100644 --- a/src/routes/(pages)/dashboard/staff/(components)/grant-access-form/grant-access-form.svelte +++ b/src/routes/(pages)/dashboard/staff/(components)/grant-access-form/grant-access-form.svelte @@ -23,6 +23,7 @@ let tunnels: ClientTunnelResponse[] = $state([]); const grantAccess = async () => { + auth.refreshLastActive(); if (!tenantId || !$staffCrypto.crypto) return; const cryptoClient = $staffCrypto.crypto; diff --git a/src/routes/(pages)/dashboard/staff/(components)/resend-invite-form/resend-invite-form.svelte b/src/routes/(pages)/dashboard/staff/(components)/resend-invite-form/resend-invite-form.svelte index 3e1817a..775e458 100644 --- a/src/routes/(pages)/dashboard/staff/(components)/resend-invite-form/resend-invite-form.svelte +++ b/src/routes/(pages)/dashboard/staff/(components)/resend-invite-form/resend-invite-form.svelte @@ -5,6 +5,7 @@ import { Input } from "$lib/components/ui/input"; import { TranslationWithComponent } from "$lib/components/ui/translation-with-component"; import { Text } from "$lib/components/ui/typography"; + import { auth } from "$lib/stores/auth"; import type { TStaff } from "$lib/types/users"; import { toast } from "svelte-sonner"; import { superForm } from "sveltekit-superforms"; @@ -19,6 +20,7 @@ { validators: zodClient(formSchema), onResult: async (event) => { + auth.refreshLastActive(); if (event.result.type === "success") { toast.success(m["staff.resendInvite.success"]()); done(); diff --git a/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte b/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte index 056ead5..6ff51a1 100644 --- a/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte +++ b/src/routes/(pages)/dashboard/tenants/(components)/add-tenant-form/add-tenant-form.svelte @@ -1,16 +1,17 @@