diff --git a/package-lock.json b/package-lock.json
index 593a46a..f8c6c95 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5348,9 +5348,9 @@
}
},
"node_modules/joi": {
- "version": "17.13.7",
- "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.7.tgz",
- "integrity": "sha512-MF80Dm5Y2veNy8QWVx9Bj3ui4mo7+VPSPsR1M+oaHXV0Gx6zGX9a2F+OZG3Blby9tOlzU9Rs5FUimlEhbKtfnQ==",
+ "version": "17.13.8",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.8.tgz",
+ "integrity": "sha512-iPKOGmiRw1jxf/JOPwxmCcUQAOdF359mdzYiP2DJ+TMX0YK2zjK3D+zYOaGjpumWxOFF/l2xVWjRVK5bGSLdEw==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
@@ -6923,9 +6923,9 @@
}
},
"node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
diff --git a/project.inlang/messages/de.json b/project.inlang/messages/de.json
index b5b0c3b..cefa317 100644
--- a/project.inlang/messages/de.json
+++ b/project.inlang/messages/de.json
@@ -367,6 +367,7 @@
"weekdays": "Wochentage"
}
},
+ "save": "Speichern",
"cancel": "Abbrechen",
"actions": "Aktionen",
"view": "Ansehen",
@@ -733,14 +734,17 @@
"settings": {
"title": "Einstellungen",
"loading": "Lade Einstellungen...",
- "form": {
- "sections": {
- "general": "Allgemein",
- "address": "Adresse",
- "links": "Links",
- "advanced": "Erweitert"
- },
- "fields": {
+ "success": "Einstellungen aktualisiert",
+ "error": "Fehler beim Speichern der Einstellungen",
+ "overview": {
+ "title": "Einstellungen"
+ },
+ "general": {
+ "title": "Allgemein",
+ "description": "Name, Beschreibung und Sprachen",
+ "languages": {
+ "title": "Sprachen",
+ "description": "Definiere Sprachen, die Du unterstützen möchtest und lege eine Standardsprache fest.",
"languages": {
"title": "Sprachen",
"description": "Wähle die Sprachen, die Klienten angezeigt bekommen sollen. Du musst mindestens Eine auswählen.",
@@ -753,7 +757,11 @@
"errors": {
"notInLanguages": "Muss eine der ausgewählten Sprachen sein"
}
- },
+ }
+ },
+ "branding": {
+ "title": "Branding",
+ "description": "Lege Name, Beschreibung und Logo für deine Organisation fest.",
"longName": {
"title": "Name",
"description": "Der öffentliche Name Deiner Organisation."
@@ -764,7 +772,49 @@
"descriptions": {
"title": "Beschreibung (optional)",
"description": "Kurze Beschreibung deiner Organisation. Wird auf der Startseite angezeigt."
- },
+ }
+ }
+ },
+ "address": {
+ "title": "Adresse",
+ "description": "Die vollständige Adresse deiner Organisation für Termine",
+ "address": {
+ "title": "Besucheradresse",
+ "description": "Diese Adresse wird in den Termininformationen für deine Klienten angezeigt."
+ }
+ },
+ "links": {
+ "title": "Links",
+ "description": "Webseite- und rechtliche Links",
+ "website": {
+ "title": "Webseite",
+ "description": "Wenn du eine Webseite angibst, wird ein Link auf der Startseite angezeigt."
+ },
+ "legal": {
+ "title": "Rechtliche Links",
+ "description": "Rechtliche Links werden in der Fußzeile angezeigt, wenn sie gesetzt sind."
+ }
+ },
+ "advanced": {
+ "title": "Erweitert",
+ "description": "Compliance und erforderliche Kundendaten",
+ "compliance": {
+ "title": "Compliance",
+ "description": "Stelle sicher, dass deine Organisation den gesetzlichen Anforderungen entspricht. Rechtliche Links können im Abschnitt Links gesetzt werden."
+ },
+ "requirements": {
+ "title": "Bedingungen",
+ "description": "Gib' an welche Informationen für alle öffentlich gebuchten Termine erforderlich sind."
+ }
+ },
+ "form": {
+ "sections": {
+ "general": "Allgemein",
+ "address": "Adresse",
+ "links": "Links",
+ "advanced": "Erweitert"
+ },
+ "fields": {
"street": {
"title": "Straße"
},
diff --git a/project.inlang/messages/en.json b/project.inlang/messages/en.json
index d1a900a..915bf61 100644
--- a/project.inlang/messages/en.json
+++ b/project.inlang/messages/en.json
@@ -375,6 +375,7 @@
"weekdays": "Weekdays"
}
},
+ "save": "Save",
"cancel": "Cancel",
"actions": "Actions",
"view": "View",
@@ -741,14 +742,17 @@
"settings": {
"title": "Settings",
"loading": "Loading settings...",
- "form": {
- "sections": {
- "general": "General",
- "address": "Address",
- "links": "Links",
- "advanced": "Advanced"
- },
- "fields": {
+ "success": "Settings updated",
+ "error": "Error saving settings",
+ "overview": {
+ "title": "Settings"
+ },
+ "general": {
+ "title": "General",
+ "description": "Name, Description and Language settings",
+ "languages": {
+ "title": "Languages",
+ "description": "Define the languages you want to support and set a default language.",
"languages": {
"title": "Languages",
"description": "Define the languages your customers will see. You need to select at least one.",
@@ -761,7 +765,11 @@
"errors": {
"notInLanguages": "Default language must be one of the selected languages"
}
- },
+ }
+ },
+ "branding": {
+ "title": "Branding",
+ "description": "Set name, description and logo for your organization.",
"longName": {
"title": "Name",
"description": "Public name of your organization."
@@ -772,7 +780,50 @@
"descriptions": {
"title": "Description (optional)",
"description": "Short description of your organization. Will be shown on the home page."
- },
+ }
+ }
+ },
+ "address": {
+ "title": "Address",
+ "description": "Your organizations full address for appointments",
+ "address": {
+ "title": "Visitor Address",
+ "description": "This address will be included in appointment information for your clients."
+ }
+ },
+ "links": {
+ "title": "Links",
+ "description": "Website and legal links",
+ "website": {
+ "title": "Website",
+ "description": "If you set this, a link will be shown on the home page."
+ },
+ "legal": {
+ "title": "Legal",
+ "description": "Legal links are shown in the footer, when set."
+ }
+ },
+ "advanced": {
+ "title": "Advanced",
+ "description": "Compliance and required client data",
+ "compliance": {
+ "title": "Compliance",
+ "description": "Ensure your organization complies with legal requirements. Legal links can be set in the Links section."
+ },
+ "requirements": {
+ "title": "Requirements",
+ "description": "Specify globally required client data for publically booked appointments."
+ }
+ },
+ "form": {
+ "sections": {
+ "general": "General",
+ "address": "Address",
+ "links": "Links",
+ "advanced": "Advanced"
+ },
+ "fields": {
+
"street": {
"title": "Street"
},
diff --git a/src/lib/components/layouts/sidebar-layout/components/nav-secondary.svelte b/src/lib/components/layouts/sidebar-layout/components/nav-secondary.svelte
index 45b6fe5..cdaba5c 100644
--- a/src/lib/components/layouts/sidebar-layout/components/nav-secondary.svelte
+++ b/src/lib/components/layouts/sidebar-layout/components/nav-secondary.svelte
@@ -21,7 +21,7 @@
},
{
title: m["nav.settings"](),
- url: ROUTES.DASHBOARD.SETTINGS,
+ url: ROUTES.DASHBOARD.SETTINGS.MAIN,
isTenantOnly: true,
icon: SettingsIcon,
roles: ["GLOBAL_ADMIN", "TENANT_ADMIN"],
diff --git a/src/lib/components/templates/form-split/form-split.svelte b/src/lib/components/templates/form-split/form-split.svelte
index 2a47f5b..e3925b0 100644
--- a/src/lib/components/templates/form-split/form-split.svelte
+++ b/src/lib/components/templates/form-split/form-split.svelte
@@ -6,9 +6,16 @@
let {
title,
description,
+ hint,
class: className,
children,
- }: { title: string; description: string; class?: string; children?: Snippet } = $props();
+ }: {
+ title: string;
+ description: string;
+ hint?: Snippet;
+ class?: string;
+ children?: Snippet;
+ } = $props();
@@ -21,8 +28,13 @@
{description}
{/if}
+ {#if hint}
+
+ {@render hint?.()}
+
+ {/if}
-
diff --git a/src/lib/const/routes.ts b/src/lib/const/routes.ts
index 9a8196b..0b5498b 100644
--- a/src/lib/const/routes.ts
+++ b/src/lib/const/routes.ts
@@ -25,7 +25,13 @@ export const ROUTES = {
CHANNEL_SLOTS: "/dashboard/channels/[channelId]/slots",
CHANNEL_PUBLIC_BOOKING: "/dashboard/channels/[channelId]/public-booking",
ABSENCES: "/dashboard/absences",
- SETTINGS: "/dashboard/settings",
+ SETTINGS: {
+ MAIN: "/dashboard/settings",
+ GENERAL: "/dashboard/settings/general",
+ ADDRESS: "/dashboard/settings/address",
+ LINKS: "/dashboard/settings/links",
+ ADVANCED: "/dashboard/settings/advanced",
+ },
ACCOUNT: {
MAIN: "/dashboard/account",
GENERAL: "/dashboard/account/general",
diff --git a/src/lib/stores/tenants.ts b/src/lib/stores/tenants.ts
index c4f1a72..a17a784 100644
--- a/src/lib/stores/tenants.ts
+++ b/src/lib/stores/tenants.ts
@@ -103,6 +103,15 @@ const triggerOnboardingNotifications = (
curState: TenantState,
) => {
if (newCurrentTenant && newCurrentTenant?.setupState !== "READY") {
+ // In the progress on configuring settings?
+ if (
+ newCurrentTenant?.setupState === "SETTINGS" &&
+ curState.currentTenant?.setupState === "SETTINGS"
+ ) {
+ return;
+ }
+
+ // In the progress on configuring the first channel?
if (newCurrentTenant?.setupState === "CHANNELS") {
const hasCompleteChannel = get(channels).channels.some(
(c) => c.agentIds?.length > 0 && c.slotTemplates?.length > 0 && c.pause === false,
diff --git a/src/routes/(pages)/dashboard/+page.svelte b/src/routes/(pages)/dashboard/+page.svelte
index a4b677c..9897d4e 100644
--- a/src/routes/(pages)/dashboard/+page.svelte
+++ b/src/routes/(pages)/dashboard/+page.svelte
@@ -107,7 +107,7 @@
{
label: m["dashboard.onboarding.sections.settings.action"](),
onClick: () => {
- goto(resolve(ROUTES.DASHBOARD.SETTINGS));
+ goto(resolve(ROUTES.DASHBOARD.SETTINGS.MAIN));
},
},
],
diff --git a/src/routes/(pages)/dashboard/account/+page.svelte b/src/routes/(pages)/dashboard/account/+page.svelte
index 8f1740b..6ef96d0 100644
--- a/src/routes/(pages)/dashboard/account/+page.svelte
+++ b/src/routes/(pages)/dashboard/account/+page.svelte
@@ -4,11 +4,10 @@
import { MaxPageWidth } from "$lib/components/layouts/max-page-width";
import { SidebarLayout } from "$lib/components/layouts/sidebar-layout";
import { PageHeadline } from "$lib/components/templates/page-headline";
+ import { GroupItem } from "$lib/components/ui/group-item";
import * as Item from "$lib/components/ui/item";
- import { Text } from "$lib/components/ui/typography";
import { ROUTES } from "$lib/const/routes";
import { auth } from "$lib/stores/auth";
- import { ChevronRight } from "@lucide/svelte/icons";
-
- {#snippet child({ props })}
-
-
-
- {m["account.general.title"]()}
-
-
- {m["account.general.description"]()}
-
-
-
-
-
-
- {/snippet}
-
-
-
- {#snippet child({ props })}
-
-
-
- {m["account.passkeys.title"]()}
-
-
- {m["account.passkeys.description"]()}
-
-
-
-
-
-
- {/snippet}
-
+
+
+
{#if $auth.user?.role === "GLOBAL_ADMIN"}
-
- {#snippet child({ props })}
-
-
-
- {m["account.change-passphrase.title"]()}
-
-
- {m["account.change-passphrase.description"]()}
-
-
-
-
-
-
- {/snippet}
-
+
{/if}
diff --git a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-address.svelte b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-address.svelte
new file mode 100644
index 0000000..e356981
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-address.svelte
@@ -0,0 +1,125 @@
+
+
+
+
+
+ {#snippet children({ props })}
+
+ {/snippet}
+
+
+
+
+
+
+
+
+ {m["save"]()}
+
+
+
diff --git a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-advanced.svelte b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-advanced.svelte
new file mode 100644
index 0000000..799e38d
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-advanced.svelte
@@ -0,0 +1,152 @@
+
+
+
+
+
+ {#snippet children({ props })}
+
+ {/snippet}
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.form.fields.autoDeleteDays.title"]()}
+ ($formData.settings.autoDeleteDays = parseInt(v))}
+ >
+
+ {$formData.settings.autoDeleteDays
+ ? autoDeleteDaysOptions.find((x) => x.key === $formData.settings.autoDeleteDays)
+ ?.label
+ : m["settings.form.fields.autoDeleteDays.placeholder"]()}
+
+
+ {#each autoDeleteDaysOptions as option (option.key)}
+
+ {option.label}
+
+ {/each}
+
+
+ {/snippet}
+
+
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.form.fields.requirePhone.title"]()}
+ {
+ $formData.settings.requirePhone = v;
+ }}
+ />
+ {/snippet}
+
+
+
+
+
+
+
+ {m["save"]()}
+
+
+
+
+{#snippet hint()}
+
+{/snippet}
diff --git a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-general.svelte b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-general.svelte
new file mode 100644
index 0000000..c96f704
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-general.svelte
@@ -0,0 +1,204 @@
+
+
+
+
+
+ {#snippet children({ props })}
+
+ {/snippet}
+
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.general.languages.languages.title"]()}
+ ($formData.languages = v)}
+ >
+
+ {$formData.languages.length > 0
+ ? $formData.languages
+ .map((id) => availableLocales.find((x) => x.key === id)?.label)
+ .join(", ")
+ : m["settings.general.languages.languages.placeholder"]()}
+
+
+ {#each availableLocales as locale (locale.key)}
+ {locale.label}
+ {/each}
+
+
+
+ {m["settings.general.languages.languages.description"]()}
+
+ {/snippet}
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.general.languages.defaultLanguage.title"]()}
+ ($formData.defaultLanguage = v)}
+ >
+
+ {$formData.defaultLanguage
+ ? availableLocales.find((x) => x.key === $formData.defaultLanguage)?.label
+ : m["settings.general.languages.defaultLanguage.placeholder"]()}
+
+
+ {#each $formData.languages as language (language)}
+
+ {availableLocales.find((x) => x.key === language)?.label}
+
+ {/each}
+
+
+
+ {m["settings.general.languages.defaultLanguage.description"]()}
+
+ {/snippet}
+
+
+
+
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.general.branding.logo.title"]()}
+
+ {/snippet}
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["form.name"]()}
+
+ {/snippet}
+
+
+ {m["settings.general.branding.longName.description"]()}
+
+
+
+
+ {#snippet children({ locale })}
+
+
+ {#snippet children({ props })}
+ {m["settings.general.branding.descriptions.title"]()}
+
+ {/snippet}
+
+ {m["settings.general.branding.descriptions.description"]()}
+
+
+ {/snippet}
+
+
+
+
+
+
+ {m["save"]()}
+
+
+
diff --git a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-links.svelte b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-links.svelte
new file mode 100644
index 0000000..c1a8821
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form-links.svelte
@@ -0,0 +1,117 @@
+
+
+
+
+
+ {#snippet children({ props })}
+
+ {/snippet}
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.form.fields.website.title"]()}
+
+ {/snippet}
+
+
+ {m["settings.form.fields.website.description"]()}
+
+
+
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.form.fields.imprint.title"]()}
+
+ {/snippet}
+
+
+ {m["settings.form.fields.imprint.description"]()}
+
+
+
+
+
+ {#snippet children({ props })}
+ {m["settings.form.fields.privacyStatement.title"]()}
+
+ {/snippet}
+
+
+ {m["settings.form.fields.privacyStatement.description"]()}
+
+
+
+
+
+
+
+
+ {m["save"]()}
+
+
+
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
deleted file mode 100644
index 9c76566..0000000
--- a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/edit-settings-form.svelte
+++ /dev/null
@@ -1,343 +0,0 @@
-
-
-
-
-
- {#snippet children({ props })}
-
- {/snippet}
-
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.languages.title"]()}
- ($formData.languages = v)}
- >
-
- {$formData.languages.length > 0
- ? $formData.languages
- .map((id) => availableLocales.find((x) => x.key === id)?.label)
- .join(", ")
- : m["settings.form.fields.languages.placeholder"]()}
-
-
- {#each availableLocales as locale (locale.key)}
- {locale.label}
- {/each}
-
-
-
- {m["settings.form.fields.languages.description"]()}
-
- {/snippet}
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.defaultLanguage.title"]()}
- ($formData.defaultLanguage = v)}
- >
-
- {$formData.defaultLanguage
- ? availableLocales.find((x) => x.key === $formData.defaultLanguage)?.label
- : m["settings.form.fields.defaultLanguage.placeholder"]()}
-
-
- {#each $formData.languages as language (language)}
-
- {availableLocales.find((x) => x.key === language)?.label}
-
- {/each}
-
-
-
- {m["settings.form.fields.defaultLanguage.description"]()}
-
- {/snippet}
-
-
-
-
-
- {#snippet children({ props })}
- {m["form.name"]()}
-
- {/snippet}
-
-
- {m["settings.form.fields.longName.description"]()}
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.logo.title"]()}
-
- {/snippet}
-
-
-
-
- {#snippet children({ locale })}
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.descriptions.title"]()}
-
- {/snippet}
-
- {m["settings.form.fields.descriptions.description"]()}
-
-
- {/snippet}
-
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.street.title"]()}
-
- {/snippet}
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.number.title"]()}
-
- {/snippet}
-
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.additionalAddressInfo.title"]()}
-
- {/snippet}
-
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.zip.title"]()}
-
- {/snippet}
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.city.title"]()}
-
- {/snippet}
-
-
-
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.website.title"]()}
-
- {/snippet}
-
-
- {m["settings.form.fields.website.description"]()}
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.imprint.title"]()}
-
- {/snippet}
-
-
- {m["settings.form.fields.imprint.description"]()}
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.privacyStatement.title"]()}
-
- {/snippet}
-
-
- {m["settings.form.fields.privacyStatement.description"]()}
-
-
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.autoDeleteDays.title"]()}
- ($formData.settings.autoDeleteDays = parseInt(v))}
- >
-
- {$formData.settings.autoDeleteDays
- ? autoDeleteDaysOptions.find((x) => x.key === $formData.settings.autoDeleteDays)
- ?.label
- : m["settings.form.fields.autoDeleteDays.placeholder"]()}
-
-
- {#each autoDeleteDaysOptions as option (option.key)}
-
- {option.label}
-
- {/each}
-
-
- {/snippet}
-
-
-
-
-
- {#snippet children({ props })}
- {m["settings.form.fields.requirePhone.title"]()}
- {
- $formData.settings.requirePhone = v;
- }}
- />
- {/snippet}
-
-
-
-
-
-
-
-
- {m["settings.form.action"]()}
-
-
-
diff --git a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/index.ts b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/index.ts
index ce181b7..733a0dd 100644
--- a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/index.ts
+++ b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/index.ts
@@ -1,5 +1,13 @@
-import EditSettingsForm from "./edit-settings-form.svelte";
+import EditSettingsFormGeneral from "./edit-settings-form-general.svelte";
+import EditSettingsFormAddress from "./edit-settings-form-address.svelte";
+import EditSettingsFormAdvanced from "./edit-settings-form-advanced.svelte";
+import EditSettingsFormLinks from "./edit-settings-form-links.svelte";
-export { EditSettingsForm };
+export {
+ EditSettingsFormGeneral,
+ EditSettingsFormAddress,
+ EditSettingsFormAdvanced,
+ EditSettingsFormLinks,
+};
export { formSchema } from "./schema";
export type { FormSchema } from "./schema";
diff --git a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/schema.ts b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/schema.ts
index 3cf8d51..2b10776 100644
--- a/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/schema.ts
+++ b/src/routes/(pages)/dashboard/settings/(components)/edit-settings-form/schema.ts
@@ -22,34 +22,39 @@ const optionalUrl = (errorMessage: string) =>
export const formSchema = z
.object({
id: z.string(),
- languages: z.array(z.string()).min(1, { message: m["form.errors.languages"]() }),
- defaultLanguage: z.string().min(1, { message: m["form.errors.language"]() }),
- shortName: z.string(),
- longName: z.string().min(1, { message: m["form.errors.longname"]() }),
- logo: z.string().optional().or(z.literal("")),
- descriptions: z.record(z.string(), z.string()).optional(),
- address: z.object({
- street: z.string().min(1, { message: m["form.errors.street"]() }),
- number: z.string().min(1, { message: m["form.errors.houseNo"]() }),
- additionalAddressInfo: z.string().optional().or(z.literal("")),
- zip: z.string().min(4, { message: m["form.errors.zip"]() }),
- city: z.string().min(1, { message: m["form.errors.city"]() }),
- }),
- links: z.object({
- website: optionalUrl(m["form.errors.url"]()),
- imprint: optionalUrl(m["form.errors.url"]()),
- privacyStatement: optionalUrl(m["form.errors.url"]()),
- }),
- settings: z.object({
- autoDeleteDays: z.number().min(30, { message: m["form.errors.deleteAfterDays"]() }),
- requirePhone: z.boolean().optional().default(false),
- }),
+ languages: z.array(z.string()).min(1, { message: m["form.errors.languages"]() }).optional(),
+ defaultLanguage: z.string().min(1, { message: m["form.errors.language"]() }).optional(),
+ longName: z.string().min(1, { message: m["form.errors.longname"]() }).optional(),
+ logo: z.string().optional().or(z.literal("")).optional(),
+ descriptions: z.record(z.string(), z.string()).optional().optional(),
+ address: z
+ .object({
+ street: z.string().min(1, { message: m["form.errors.street"]() }),
+ number: z.string().min(1, { message: m["form.errors.houseNo"]() }),
+ additionalAddressInfo: z.string().optional().or(z.literal("")),
+ zip: z.string().min(4, { message: m["form.errors.zip"]() }),
+ city: z.string().min(1, { message: m["form.errors.city"]() }),
+ })
+ .optional(),
+ links: z
+ .object({
+ website: optionalUrl(m["form.errors.url"]()),
+ imprint: optionalUrl(m["form.errors.url"]()),
+ privacyStatement: optionalUrl(m["form.errors.url"]()),
+ })
+ .optional(),
+ settings: z
+ .object({
+ autoDeleteDays: z.number().min(30, { message: m["form.errors.deleteAfterDays"]() }),
+ requirePhone: z.boolean().optional().default(false),
+ })
+ .optional(),
})
.superRefine(({ languages, defaultLanguage }, ctx) => {
- if (!languages.includes(defaultLanguage)) {
+ if (languages && defaultLanguage && !languages.includes(defaultLanguage)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
- message: m["settings.form.fields.defaultLanguage.errors.notInLanguages"](),
+ message: m["settings.general.languages.defaultLanguage.errors.notInLanguages"](),
path: ["defaultLanguage"],
});
}
diff --git a/src/routes/(pages)/dashboard/settings/+layout.server.ts b/src/routes/(pages)/dashboard/settings/+layout.server.ts
new file mode 100644
index 0000000..4c214a4
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/+layout.server.ts
@@ -0,0 +1,98 @@
+import { ROUTES } from "$lib/const/routes.js";
+import logger from "$lib/logger";
+import type { TTenantSettings } from "$lib/types/tenant";
+import { redirect } from "@sveltejs/kit";
+
+const log = logger.setContext(import.meta.filename);
+
+export const load = async (event) => {
+ event.depends(`channel:details`);
+
+ // To edit seetings you must have connected tenant
+ if (!event.locals.user?.tenantId) {
+ log.warn("No tenant ID found for user while loading settings");
+ throw redirect(302, ROUTES.DASHBOARD.MAIN);
+ }
+
+ const base = event
+ .fetch(`/api/tenants/${event.locals.user?.tenantId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin",
+ })
+ .then(async (res) => {
+ // Logout if session expired
+ if (res.status === 401) {
+ redirect(302, ROUTES.LOGOUT);
+ }
+
+ try {
+ const body = await res.json();
+ return {
+ id: body.tenant.id,
+ languages: body.tenant.languages,
+ defaultLanguage: body.tenant.defaultLanguage,
+ shortName: body.tenant.shortName,
+ longName: body.tenant.longName,
+ logo: body.tenant.logo || "",
+ descriptions: body.tenant.descriptions,
+ links: {
+ website: body.tenant.links.website || "",
+ imprint: body.tenant.links.imprint || "",
+ privacyStatement: body.tenant.links.privacyStatement || "",
+ },
+ };
+ } catch (error) {
+ log.error("Failed to parse settings base response", { error });
+ }
+ });
+
+ const config = event
+ .fetch(`/api/tenants/${event.locals.user?.tenantId}/config`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin",
+ })
+ .then(async (res) => {
+ // Logout if session expired
+ if (res.status === 401) {
+ redirect(302, ROUTES.LOGOUT);
+ }
+
+ try {
+ const body = await res.json();
+ return {
+ address: {
+ street: body["address.street"] || "",
+ number: body["address.number"] || "",
+ additionalAddressInfo: body["address.additionalAddressInfo"] || "",
+ zip: body["address.zip"] || "",
+ city: body["address.city"] || "",
+ },
+ settings: {
+ autoDeleteDays: body.autoDeleteDays || 90,
+ requirePhone: body.requirePhone || false,
+ },
+ };
+ } catch (error) {
+ log.error("Failed to parse settings config response", { error });
+ }
+ });
+
+ const item = Promise.all([base, config]).then(([base, config]) => {
+ return {
+ ...base,
+ ...config,
+ } as TTenantSettings;
+ });
+
+ return {
+ streamed: {
+ item,
+ },
+ };
+};
diff --git a/src/routes/(pages)/dashboard/settings/+page.server.ts b/src/routes/(pages)/dashboard/settings/+page.server.ts
index 003225c..3f28fda 100644
--- a/src/routes/(pages)/dashboard/settings/+page.server.ts
+++ b/src/routes/(pages)/dashboard/settings/+page.server.ts
@@ -1,103 +1,12 @@
-import { ROUTES } from "$lib/const/routes.js";
import logger from "$lib/logger";
-import { fail, redirect, type Actions } from "@sveltejs/kit";
+import { removeEmptyTranslations } from "$lib/utils/localizations";
+import { fail, type Actions } from "@sveltejs/kit";
import { superValidate } from "sveltekit-superforms";
import { zod4 as zod } from "sveltekit-superforms/adapters";
import { formSchema as editFormSchema } from "./(components)/edit-settings-form";
-import { removeEmptyTranslations } from "$lib/utils/localizations";
const log = logger.setContext(import.meta.filename);
-export const load = async (event) => {
- // To edit seetings you must have connected tenant
- if (!event.locals.user?.tenantId) {
- log.warn("No tenant ID found for user while loading settings");
- throw redirect(302, ROUTES.DASHBOARD.MAIN);
- }
-
- const base = event
- .fetch(`/api/tenants/${event.locals.user?.tenantId}`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- credentials: "same-origin",
- })
- .then(async (res) => {
- // Logout if session expired
- if (res.status === 401) {
- redirect(302, ROUTES.LOGOUT);
- }
-
- try {
- const body = await res.json();
- return {
- id: body.tenant.id,
- languages: body.tenant.languages,
- defaultLanguage: body.tenant.defaultLanguage,
- shortName: body.tenant.shortName,
- longName: body.tenant.longName,
- logo: body.tenant.logo || "",
- descriptions: body.tenant.descriptions,
- links: {
- website: body.tenant.links.website || "",
- imprint: body.tenant.links.imprint || "",
- privacyStatement: body.tenant.links.privacyStatement || "",
- },
- };
- } catch (error) {
- log.error("Failed to parse settings base response", { error });
- }
- });
-
- const config = event
- .fetch(`/api/tenants/${event.locals.user?.tenantId}/config`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- },
- credentials: "same-origin",
- })
- .then(async (res) => {
- // Logout if session expired
- if (res.status === 401) {
- redirect(302, ROUTES.LOGOUT);
- }
-
- try {
- const body = await res.json();
- return {
- address: {
- street: body["address.street"] || "",
- number: body["address.number"] || "",
- additionalAddressInfo: body["address.additionalAddressInfo"] || "",
- zip: body["address.zip"] || "",
- city: body["address.city"] || "",
- },
- settings: {
- autoDeleteDays: body.autoDeleteDays || 90,
- requirePhone: body.requirePhone || false,
- },
- };
- } catch (error) {
- log.error("Failed to parse settings config response", { error });
- }
- });
-
- const item = Promise.all([base, config]).then(([base, config]) => {
- return {
- ...base,
- ...config,
- };
- });
-
- return {
- streamed: {
- item,
- },
- };
-};
-
export const actions: Actions = {
edit: async (event) => {
const form = await superValidate(event, zod(editFormSchema));
@@ -110,44 +19,67 @@ export const actions: Actions = {
});
}
- const base = await event.fetch(`/api/tenants/${form.data.id}`, {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- },
- credentials: "same-origin",
- body: JSON.stringify({
- languages: form.data.languages,
- defaultLanguage: form.data.defaultLanguage,
- longName: form.data.longName,
- logo: form.data.logo,
- descriptions: removeEmptyTranslations(form.data.descriptions),
- links: form.data.links,
- }),
- });
+ const requests = [];
+ if (form.data.longName || form.data.links) {
+ const base = await event.fetch(`/api/tenants/${form.data.id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin",
+ body: JSON.stringify({
+ ...(form.data.longName
+ ? {
+ languages: form.data.languages,
+ defaultLanguage: form.data.defaultLanguage,
+ longName: form.data.longName,
+ logo: form.data.logo,
+ descriptions: removeEmptyTranslations(form.data.descriptions),
+ }
+ : undefined),
+ ...(form.data.links
+ ? {
+ links: form.data.links,
+ }
+ : undefined),
+ }),
+ });
+ requests.push(base);
+ }
- const config = await event.fetch(`/api/tenants/${form.data.id}/config`, {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- },
- credentials: "same-origin",
- body: JSON.stringify({
- "address.street": form.data.address.street,
- "address.number": form.data.address.number,
- "address.additionalAddressInfo": form.data.address.additionalAddressInfo,
- "address.zip": form.data.address.zip,
- "address.city": form.data.address.city,
- autoDeleteDays: form.data.settings.autoDeleteDays,
- requirePhone: form.data.settings.requirePhone ?? false,
- }),
- });
+ if (form.data.address || form.data.settings) {
+ const config = await event.fetch(`/api/tenants/${form.data.id}/config`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ credentials: "same-origin",
+ body: JSON.stringify({
+ ...(form.data.address
+ ? {
+ "address.street": form.data.address.street,
+ "address.number": form.data.address.number,
+ "address.additionalAddressInfo": form.data.address.additionalAddressInfo,
+ "address.zip": form.data.address.zip,
+ "address.city": form.data.address.city,
+ }
+ : undefined),
+ ...(form.data.settings
+ ? {
+ autoDeleteDays: form.data.settings.autoDeleteDays,
+ requirePhone: form.data.settings.requirePhone ?? false,
+ }
+ : undefined),
+ }),
+ });
+ requests.push(config);
+ }
- const resp = await Promise.all([base, config]).then(([base, config]) => {
- if (base.status < 400 && config.status < 400) {
+ const resp = await Promise.all(requests).then((all) => {
+ if (all.every((res) => res.status < 400)) {
return { success: true };
}
- return { success: false, bodies: { base: base, config: config } };
+ return { success: false, bodies: { base: all[0], config: all[1] } };
});
if (resp.success) {
diff --git a/src/routes/(pages)/dashboard/settings/+page.svelte b/src/routes/(pages)/dashboard/settings/+page.svelte
index 2067f08..4da20b6 100644
--- a/src/routes/(pages)/dashboard/settings/+page.svelte
+++ b/src/routes/(pages)/dashboard/settings/+page.svelte
@@ -1,14 +1,26 @@
@@ -19,18 +31,45 @@
breakcrumbs={[
{
label: m["nav.settings"](),
- href: ROUTES.DASHBOARD.SETTINGS,
+ href: ROUTES.DASHBOARD.SETTINGS.MAIN,
},
]}
>
-
+
{#await data.streamed.item}
-
+
{:then item}
-
- Settings
-
-
+ {#if item}
+
+
+
+
+
+
+
+ {/if}
{/await}
diff --git a/src/routes/(pages)/dashboard/settings/address/+page.svelte b/src/routes/(pages)/dashboard/settings/address/+page.svelte
new file mode 100644
index 0000000..57499d4
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/address/+page.svelte
@@ -0,0 +1,43 @@
+
+
+
+ {m["settings.title"]()}: {m["settings.address.title"]()} - OpenReception
+
+
+
+
+ {#await data.streamed.item}
+
+ {:then item}
+ {#if item}
+
+
+ {/if}
+ {/await}
+
+
diff --git a/src/routes/(pages)/dashboard/settings/advanced/+page.svelte b/src/routes/(pages)/dashboard/settings/advanced/+page.svelte
new file mode 100644
index 0000000..9abb8a9
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/advanced/+page.svelte
@@ -0,0 +1,43 @@
+
+
+
+ {m["settings.title"]()}: {m["settings.advanced.title"]()} - OpenReception
+
+
+
+
+ {#await data.streamed.item}
+
+ {:then item}
+ {#if item}
+
+
+ {/if}
+ {/await}
+
+
diff --git a/src/routes/(pages)/dashboard/settings/general/+page.svelte b/src/routes/(pages)/dashboard/settings/general/+page.svelte
new file mode 100644
index 0000000..58dabdd
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/general/+page.svelte
@@ -0,0 +1,43 @@
+
+
+
+ {m["settings.title"]()}: {m["settings.general.title"]()} - OpenReception
+
+
+
+
+ {#await data.streamed.item}
+
+ {:then item}
+ {#if item}
+
+
+ {/if}
+ {/await}
+
+
diff --git a/src/routes/(pages)/dashboard/settings/links/+page.svelte b/src/routes/(pages)/dashboard/settings/links/+page.svelte
new file mode 100644
index 0000000..793e568
--- /dev/null
+++ b/src/routes/(pages)/dashboard/settings/links/+page.svelte
@@ -0,0 +1,43 @@
+
+
+
+ {m["settings.title"]()}: {m["settings.links.title"]()} - OpenReception
+
+
+
+
+ {#await data.streamed.item}
+
+ {:then item}
+ {#if item}
+
+
+ {/if}
+ {/await}
+
+
diff --git a/src/routes/(pages)/dashboard/tenants/+page.svelte b/src/routes/(pages)/dashboard/tenants/+page.svelte
index 832c5a8..ffe55ba 100644
--- a/src/routes/(pages)/dashboard/tenants/+page.svelte
+++ b/src/routes/(pages)/dashboard/tenants/+page.svelte
@@ -92,7 +92,6 @@
type: "action",
icon: EditIcon,
label: m["edit"](),
- isMainAction: true,
onClick: () => {
curItem = item;
openDialog("edit");
@@ -101,6 +100,7 @@
{
type: "action",
icon: SelectIcon,
+ isMainAction: true,
label: m["select"](),
onClick: () => tenantsStore.setCurrentTenant(item.id),
},