diff --git a/README.md b/README.md
index cb36eea..cb07b75 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,14 @@
End-to-end encrypted appointment booking platform
+## Local development
+
+- `npm install`
+- `npm run docker:dev:up`
+- `npm run db:migrate`
+- `npm run db:push`
+- `npm run dev`
+
## Funding
This project is funded by:
diff --git a/package-lock.json b/package-lock.json
index 8751463..4da4c5a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -50,6 +50,7 @@
"eslint": "^9.29.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.3",
+ "formsnap": "^2.0.1",
"globals": "^16.2.0",
"jsdom": "^26.1.0",
"prettier": "^3.5.3",
@@ -57,6 +58,8 @@
"prettier-plugin-tailwindcss": "^0.6.13",
"svelte": "^5.34.7",
"svelte-check": "^4.2.2",
+ "svelte-sonner": "^1.0.5",
+ "sveltekit-superforms": "^2.27.1",
"tailwind-merge": "^3.3.1",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.10",
@@ -87,6 +90,25 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@ark/schema": {
+ "version": "0.46.0",
+ "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.46.0.tgz",
+ "integrity": "sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@ark/util": "0.46.0"
+ }
+ },
+ "node_modules/@ark/util": {
+ "version": "0.46.0",
+ "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.46.0.tgz",
+ "integrity": "sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@asamuzakjp/css-color": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
@@ -1299,6 +1321,14 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@exodus/schemasafe": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz",
+ "integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@floating-ui/core": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.2.tgz",
@@ -1327,6 +1357,58 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@gcornut/valibot-json-schema": {
+ "version": "0.42.0",
+ "resolved": "https://registry.npmjs.org/@gcornut/valibot-json-schema/-/valibot-json-schema-0.42.0.tgz",
+ "integrity": "sha512-4Et4AN6wmqeA0PfU5Clkv/IS27wiefsWf6TemAZrb75uzkClYEFavim7SboeKwbll9Nbsn2Iv0LT/HS5H7orZg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "valibot": "~0.42.0"
+ },
+ "bin": {
+ "valibot-json-schema": "bin/index.js"
+ },
+ "optionalDependencies": {
+ "@types/json-schema": ">= 7.0.14",
+ "esbuild-runner": ">= 2.2.2"
+ }
+ },
+ "node_modules/@gcornut/valibot-json-schema/node_modules/valibot": {
+ "version": "0.42.1",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.42.1.tgz",
+ "integrity": "sha512-3keXV29Ar5b//Hqi4MbSdV7lfVp6zuYLZuA9V1PvQUsXqogr+u5lvLPLk3A4f74VUXDnf/JfWMN6sB+koJ/FFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@hapi/hoek": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
+ "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@hapi/topo": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
+ "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1647,6 +1729,14 @@
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
"license": "MIT"
},
+ "node_modules/@poppinss/macroable": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.0.5.tgz",
+ "integrity": "sha512-6u61y1HHd090MEk1Av0/1btDmm2Hh/+XoJj+HgFYRh9koUPI822ybJbwLHuqjLNCiY+o1gRykg2igEqOf/VBZw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@rollup/plugin-commonjs": {
"version": "28.0.6",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.6.tgz",
@@ -2020,6 +2110,33 @@
"win32"
]
},
+ "node_modules/@sideway/address": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
+ "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@sideway/formula": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
+ "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
+ "node_modules/@sideway/pinpoint": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
+ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true
+ },
"node_modules/@sinclair/typebox": {
"version": "0.31.28",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.31.28.tgz",
@@ -2037,6 +2154,14 @@
"sqlite-wasm": "bin/index.js"
}
},
+ "node_modules/@standard-schema/spec": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
+ "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@sveltejs/acorn-typescript": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
@@ -2629,6 +2754,49 @@
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
"license": "MIT"
},
+ "node_modules/@types/validator": {
+ "version": "13.15.2",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.2.tgz",
+ "integrity": "sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/@typeschema/class-validator": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/@typeschema/class-validator/-/class-validator-0.3.0.tgz",
+ "integrity": "sha512-OJSFeZDIQ8EK1HTljKLT5CItM2wsbgczLN8tMEfz3I1Lmhc5TBfkZ0eikFzUC16tI3d1Nag7um6TfCgp2I2Bww==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@typeschema/core": "0.14.0"
+ },
+ "peerDependencies": {
+ "class-validator": "^0.14.1"
+ },
+ "peerDependenciesMeta": {
+ "class-validator": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typeschema/core": {
+ "version": "0.14.0",
+ "resolved": "https://registry.npmjs.org/@typeschema/core/-/core-0.14.0.tgz",
+ "integrity": "sha512-Ia6PtZHcL3KqsAWXjMi5xIyZ7XMH4aSnOQes8mfMLx+wGFGtGRNlwe6Y7cYvX+WfNK67OL0/HSe9t8QDygV0/w==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peerDependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "peerDependenciesMeta": {
+ "@types/json-schema": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.34.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.1.tgz",
@@ -2886,6 +3054,38 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
+ "node_modules/@vinejs/compiler": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-3.0.0.tgz",
+ "integrity": "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@vinejs/vine": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-3.0.1.tgz",
+ "integrity": "sha512-ZtvYkYpZOYdvbws3uaOAvTFuvFXoQGAtmzeiXu+XSMGxi5GVsODpoI9Xu9TplEMuD/5fmAtBbKb9cQHkWkLXDQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@poppinss/macroable": "^1.0.4",
+ "@types/validator": "^13.12.2",
+ "@vinejs/compiler": "^3.0.0",
+ "camelcase": "^8.0.0",
+ "dayjs": "^1.11.13",
+ "dlv": "^1.1.3",
+ "normalize-url": "^8.0.1",
+ "validator": "^13.12.0"
+ },
+ "engines": {
+ "node": ">=18.16.0"
+ }
+ },
"node_modules/@vitest/expect": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
@@ -3113,6 +3313,18 @@
"node": ">= 0.4"
}
},
+ "node_modules/arktype": {
+ "version": "2.1.20",
+ "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.1.20.tgz",
+ "integrity": "sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@ark/schema": "0.46.0",
+ "@ark/util": "0.46.0"
+ }
+ },
"node_modules/array-timsort": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz",
@@ -3266,6 +3478,20 @@
"node": ">=6"
}
},
+ "node_modules/camelcase": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz",
+ "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/chai": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
@@ -3333,6 +3559,19 @@
"node": ">=18"
}
},
+ "node_modules/class-validator": {
+ "version": "0.14.2",
+ "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
+ "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/validator": "^13.11.8",
+ "libphonenumber-js": "^1.11.1",
+ "validator": "^13.9.0"
+ }
+ },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3545,6 +3784,14 @@
"url": "https://github.com/sponsors/kossnocorp"
}
},
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -3636,6 +3883,14 @@
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
"license": "MIT"
},
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/dom-accessibility-api": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
@@ -3811,6 +4066,18 @@
}
}
},
+ "node_modules/effect": {
+ "version": "3.17.7",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.17.7.tgz",
+ "integrity": "sha512-dpt0ONUn3zzAuul6k4nC/coTTw27AL5nhkORXgTi6NfMPzqWYa1M05oKmOMTxpVSTKepqXVcW9vIwkuaaqx9zA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
@@ -3918,6 +4185,32 @@
"esbuild": ">=0.12 <1"
}
},
+ "node_modules/esbuild-runner": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/esbuild-runner/-/esbuild-runner-2.2.2.tgz",
+ "integrity": "sha512-fRFVXcmYVmSmtYm2mL8RlUASt2TDkGh3uRcvHFOKNr/T58VrfVeKD9uT9nlgxk96u0LS0ehS/GY7Da/bXWKkhw==",
+ "dev": true,
+ "license": "Apache License 2.0",
+ "optional": true,
+ "dependencies": {
+ "source-map-support": "0.5.21",
+ "tslib": "2.4.0"
+ },
+ "bin": {
+ "esr": "bin/esr.js"
+ },
+ "peerDependencies": {
+ "esbuild": "*"
+ }
+ },
+ "node_modules/esbuild-runner/node_modules/tslib": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
+ "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
@@ -4202,6 +4495,30 @@
"node": ">=12.0.0"
}
},
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -4353,6 +4670,47 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
"license": "MIT"
},
+ "node_modules/formsnap": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.1.tgz",
+ "integrity": "sha512-iJSe4YKd/W6WhLwKDVJU9FQeaJRpEFuolhju7ZXlRpUVyDdqFdMP8AUBICgnVvQPyP41IPAlBa/v0Eo35iE6wQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "svelte-toolbelt": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=18",
+ "pnpm": ">=8.7.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/huntabyte"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0",
+ "sveltekit-superforms": "^2.19.0"
+ }
+ },
+ "node_modules/formsnap/node_modules/svelte-toolbelt": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.5.0.tgz",
+ "integrity": "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/huntabyte"
+ ],
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "style-to-object": "^1.0.8"
+ },
+ "engines": {
+ "node": ">=18",
+ "pnpm": ">=8.7.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0-next.126"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
@@ -4739,6 +5097,21 @@
"jiti": "lib/jiti-cli.mjs"
}
},
+ "node_modules/joi": {
+ "version": "17.13.3",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
+ "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "@hapi/hoek": "^9.3.0",
+ "@hapi/topo": "^5.1.0",
+ "@sideway/address": "^4.1.5",
+ "@sideway/formula": "^3.0.1",
+ "@sideway/pinpoint": "^2.0.0"
+ }
+ },
"node_modules/jose": {
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz",
@@ -4822,6 +5195,21 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-schema-to-ts": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
+ "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "ts-algebra": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -4905,6 +5293,14 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/libphonenumber-js": {
+ "version": "1.12.12",
+ "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.12.tgz",
+ "integrity": "sha512-aWVR6xXYYRvnK0v/uIwkf5Lthq9Jpn0N8TISW/oDTWlYB2sOimuiLn9Q26aUw4KxkJoiT8ACdiw44Y8VwKFIfQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/lightningcss": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
@@ -5244,6 +5640,13 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
+ "node_modules/memoize-weak": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/memoize-weak/-/memoize-weak-1.0.2.tgz",
+ "integrity": "sha512-gj39xkrjEw7nCn4nJ1M5ms6+MyMlyiGmttzsqAUsAKn6bYKwuTHh/AO3cKPF8IBrTIYTxb0wWXFs3E//Y8VoWQ==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -5434,6 +5837,20 @@
"node": ">=6.0.0"
}
},
+ "node_modules/normalize-url": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.2.tgz",
+ "integrity": "sha512-Ee/R3SyN4BuynXcnTaekmaVdbDAEiNrHqjQIA37mHU8G9pf7aaAD4ZX3XjBLo6rsdcxA/gtkcNYZLt30ACgynw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/nwsapi": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz",
@@ -5912,6 +6329,14 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/property-expr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
+ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5922,6 +6347,24 @@
"node": ">=6"
}
},
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -6426,6 +6869,17 @@
"inline-style-parser": "0.2.4"
}
},
+ "node_modules/superstruct": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-2.0.2.tgz",
+ "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -6543,6 +6997,36 @@
"node": ">=4"
}
},
+ "node_modules/svelte-sonner": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz",
+ "integrity": "sha512-9dpGPFqKb/QWudYqGnEz93vuY+NgCEvyNvxoCLMVGw6sDN/3oVeKV1xiEirW2E1N3vJEyj5imSBNOGltQHA7mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "runed": "^0.28.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.0.0"
+ }
+ },
+ "node_modules/svelte-sonner/node_modules/runed": {
+ "version": "0.28.0",
+ "resolved": "https://registry.npmjs.org/runed/-/runed-0.28.0.tgz",
+ "integrity": "sha512-k2xx7RuO9hWcdd9f+8JoBeqWtYrm5CALfgpkg2YDB80ds/QE4w0qqu34A7fqiAwiBBSBQOid7TLxwxVC27ymWQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/huntabyte",
+ "https://github.com/sponsors/tglide"
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "esm-env": "^1.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^5.7.0"
+ }
+ },
"node_modules/svelte-toolbelt": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.1.tgz",
@@ -6578,6 +7062,111 @@
"svelte": "^5.7.0"
}
},
+ "node_modules/sveltekit-superforms": {
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.27.1.tgz",
+ "integrity": "sha512-cvq2AevkZ0Zrk0w0gNM3kjcnJMtJ0jzu+2zqDoM9a+lZa+8bGpNl4YqxVkemiJNkGnFgNC8xr5xF5BlMzjookQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ciscoheat"
+ },
+ {
+ "type": "ko-fi",
+ "url": "https://ko-fi.com/ciscoheat"
+ },
+ {
+ "type": "paypal",
+ "url": "https://www.paypal.com/donate/?hosted_button_id=NY7F5ALHHSVQS"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devalue": "^5.1.1",
+ "memoize-weak": "^1.0.2",
+ "ts-deepmerge": "^7.0.3"
+ },
+ "optionalDependencies": {
+ "@exodus/schemasafe": "^1.3.0",
+ "@gcornut/valibot-json-schema": "^0.42.0",
+ "@sinclair/typebox": "^0.34.35",
+ "@typeschema/class-validator": "^0.3.0",
+ "@vinejs/vine": "^3.0.1",
+ "arktype": "^2.1.20",
+ "class-validator": "^0.14.2",
+ "effect": "^3.16.7",
+ "joi": "^17.13.3",
+ "json-schema-to-ts": "^3.1.1",
+ "superstruct": "^2.0.2",
+ "valibot": "^1.1.0",
+ "yup": "^1.6.1",
+ "zod": "^3.25.64",
+ "zod-to-json-schema": "^3.24.5"
+ },
+ "peerDependencies": {
+ "@exodus/schemasafe": "^1.3.0",
+ "@sinclair/typebox": "^0.34.28",
+ "@sveltejs/kit": "1.x || 2.x",
+ "@typeschema/class-validator": "^0.3.0",
+ "@vinejs/vine": "^1.8.0 || ^2.0.0 || ^3.0.0",
+ "arktype": ">=2.0.0-rc.23",
+ "class-validator": "^0.14.1",
+ "effect": "^3.13.7",
+ "joi": "^17.13.1",
+ "superstruct": "^2.0.2",
+ "svelte": "3.x || 4.x || >=5.0.0-next.51",
+ "valibot": "^1.0.0",
+ "yup": "^1.4.0",
+ "zod": "^3.25.0"
+ },
+ "peerDependenciesMeta": {
+ "@exodus/schemasafe": {
+ "optional": true
+ },
+ "@sinclair/typebox": {
+ "optional": true
+ },
+ "@typeschema/class-validator": {
+ "optional": true
+ },
+ "@vinejs/vine": {
+ "optional": true
+ },
+ "arktype": {
+ "optional": true
+ },
+ "class-validator": {
+ "optional": true
+ },
+ "effect": {
+ "optional": true
+ },
+ "joi": {
+ "optional": true
+ },
+ "superstruct": {
+ "optional": true
+ },
+ "valibot": {
+ "optional": true
+ },
+ "yup": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sveltekit-superforms/node_modules/@sinclair/typebox": {
+ "version": "0.34.40",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.40.tgz",
+ "integrity": "sha512-gwBNIP8ZAYev/ORDWW0QvxdwPXwxBtLsdsJgSc7eDIRt8ubP+rxUBzPsrwnu16fgEF8Bx4lh/+mvQvJzcTM6Kw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -6672,6 +7261,14 @@
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
"license": "MIT"
},
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
@@ -6765,6 +7362,14 @@
"node": ">=8.0"
}
},
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/totalist": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
@@ -6809,6 +7414,14 @@
"node": ">= 14.0.0"
}
},
+ "node_modules/ts-algebra": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
+ "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -6822,6 +7435,16 @@
"typescript": ">=4.8.4"
}
},
+ "node_modules/ts-deepmerge": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.3.tgz",
+ "integrity": "sha512-Du/ZW2RfwV/D4cmA5rXafYjBQVuvu4qGiEEla4EmEHVHgRdx68Gftx7i66jn2bzHPwSVZY36Ae6OuDn9el4ZKA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14.13.1"
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -6852,6 +7475,20 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "optional": true,
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/typescript": {
"version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@@ -6957,6 +7594,33 @@
"uuidv7": "cli.js"
}
},
+ "node_modules/valibot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.1.0.tgz",
+ "integrity": "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.15.15",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
+ "integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/vite": {
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
@@ -7381,6 +8045,20 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/yup": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.0.tgz",
+ "integrity": "sha512-VJce62dBd+JQvoc+fCVq+KZfPHr+hXaxCcVgotfwWvlR0Ja3ffYKaJBT8rptPOSKOGJDCUnW2C2JWpud7aRP6Q==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
+ },
"node_modules/zimmerframe": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
@@ -7395,6 +8073,17 @@
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.24.6",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.6.tgz",
+ "integrity": "sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==",
+ "dev": true,
+ "license": "ISC",
+ "optional": true,
+ "peerDependencies": {
+ "zod": "^3.24.1"
+ }
}
}
}
diff --git a/package.json b/package.json
index d9a7ee5..3fb41c6 100644
--- a/package.json
+++ b/package.json
@@ -58,6 +58,7 @@
"eslint": "^9.29.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-svelte": "^3.9.3",
+ "formsnap": "^2.0.1",
"globals": "^16.2.0",
"jsdom": "^26.1.0",
"prettier": "^3.5.3",
@@ -65,6 +66,8 @@
"prettier-plugin-tailwindcss": "^0.6.13",
"svelte": "^5.34.7",
"svelte-check": "^4.2.2",
+ "svelte-sonner": "^1.0.5",
+ "sveltekit-superforms": "^2.27.1",
"tailwind-merge": "^3.3.1",
"tailwind-variants": "^1.0.0",
"tailwindcss": "^4.1.10",
diff --git a/project.inlang/messages/de.json b/project.inlang/messages/de.json
index 5fe9afb..e155f42 100644
--- a/project.inlang/messages/de.json
+++ b/project.inlang/messages/de.json
@@ -6,5 +6,91 @@
"label": "Sprache wählen",
"search": "Sprache suchen",
"notFound": "Sprache nicht gefunden"
+ },
+ "setup": {
+ "start": "Konfiguration starten",
+ "create_admin_account": {
+ "title": "Administratorkonto hinzufügen",
+ "description": "Wir beginnen damit, Ihr Administratorkonto hinzuzufügen. Es wird zur Verwaltung dieser Instanz verwendet.",
+ "action": "Bestätigungsmail senden",
+ "success": "Konto erstellt!"
+ },
+ "verify_email": {
+ "title": "Rufe Deine E-Mails ab",
+ "description": "Wir haben Dir einen magischen Link an {email} gesendet",
+ "action": "E-Mail erneut senden",
+ "success": "E-Mail wurde gesendet"
+ },
+ "confirm": {
+ "success": {
+ "title": "Gute Arbeit!",
+ "description": "Du hast Dir diese Instanz gesichert.",
+ "hint": "Im nächsten Schritt legen wir einen Mandanten an, damit dieser Termine entgegen nehmen kann",
+ "action": "Ersten Mandanten anlegen"
+ },
+ "error": {
+ "title": "Bestätigung fehlgeschlagen",
+ "description": "Dein magischer Link war inkorrekt oder veraltet"
+ }
+ }
+ },
+ "slogan": "Ende zu Ende verschlüsselte Platform für Termine.",
+ "logo": "OpenReception Logo: Ein 3D-Pixel-Art mit Kalender, der auch ein Schloss ist",
+ "form": {
+ "email": "E-Mail Adresse",
+ "passphrase": "Passphrase",
+ "passphraseRequirements": "Mindestens 30 Zeichen.",
+ "errors": {
+ "email": "Ungültiges Format",
+ "passphrase": "Muss mindestens 30 Zeichen lang sein",
+ "noPassAtAll": "Either passphrase or passkey is required",
+ "bothPassSet": "Nur Passphrase oder Passkey darf gesetzt sein"
+ },
+ "passkey": "Passkey"
+ },
+ "login": {
+ "or": "Oder",
+ "usePasskey": "Passkey verwenden",
+ "usePassphrase": "Passphrase verwenden",
+ "title": "Anmelden",
+ "description_passphrase": "Gib Deine E-Mail-Adresse und Deinen Passphrase ein, um Dich anzumelden.",
+ "action": "Anmelden",
+ "error": "Anmeldung fehlgeschlagen. Bitte erneut versuchen"
+ },
+ "confirm": {
+ "success": {
+ "title": "Du bist bereit!",
+ "description": "Du hast Dein Konto erfolgreich gesichert",
+ "action": "Weiter"
+ },
+ "error": {
+ "title": "Bestätigung fehlgeschlagen",
+ "description": "Dein magischer Link war inkorrekt oder veraltet",
+ "action": "Neue E-Mail anfordern"
+ },
+ "title": "E-Mail wird verifiziert",
+ "resend": {
+ "title": "Bestätigungsmail erneut senden",
+ "description": "Gib' Deine E-Mail Adresse an, damit wir Dir einen neuen magischen Link senden können.",
+ "action": "E-Mail erneut senden",
+ "success": "E-Mail gesendet"
+ }
+ },
+ "logout": {
+ "title": "Erfolgreich abgemeldet",
+ "description": "Du wurdest abgemeldet.",
+ "success": "Erfolgreich abgemeldet",
+ "action": "Zur Anmeldung"
+ },
+ "passkey": {
+ "add": {
+ "initial": "Warten auf E-Mail Adresse",
+ "click": "Klicke/Tippe um einen Passkey hinzuzufügen",
+ "loading": "Lade Passkey-Challenge",
+ "user": "Warte auf Passkey-Erstellung",
+ "success": "Passkey hinzugefügt",
+ "error": "Passkey konnte nicht hinzugefügt werden",
+ "retry": "Erneut versuchen"
+ }
}
}
diff --git a/project.inlang/messages/en.json b/project.inlang/messages/en.json
index 0f9e789..43b650e 100644
--- a/project.inlang/messages/en.json
+++ b/project.inlang/messages/en.json
@@ -6,5 +6,99 @@
"label": "Select Language",
"search": "Search Language",
"notFound": "Language not found"
+ },
+ "setup": {
+ "start": "Start configuration",
+ "create_admin_account": {
+ "title": "Create Admin Account",
+ "description": "We’ll start by adding your admin account. It will be used to manage this instance.",
+ "action": "Send Verification E-Mail",
+ "success": "Account created!",
+ "error": "An unkonwn error occured."
+ },
+ "verify_email": {
+ "title": "Check your E-Mail",
+ "description": "We’ve send you a magic link to {email}",
+ "action": "Resend E-Mail",
+ "success": "E-Mail was resent"
+ },
+ "confirm": {
+ "success": {
+ "title": "Good job!",
+ "description": "You’ve secured this instance.",
+ "hint": "Next step is setting up a tenant to collect appointments",
+ "action": "Add first Tenant"
+ },
+ "error": {
+ "title": "Verification failed",
+ "description": "The magic link you’ve tried is incorrect or outdated"
+ }
+ }
+ },
+ "slogan": "End-to-End encrypted appointment booking platform.",
+ "logo": "OpenReception Logo: A 3D pixel art with calendar that's also a lock",
+ "form": {
+ "email": "E-Mail Address",
+ "passphrase": "Passphrase",
+ "passphraseRequirements": "At least 30 characters.",
+ "errors": {
+ "email": "Invalid format",
+ "passphrase": "Must be at least 30 characters long",
+ "noPassAtAll": "Either passphrase or passkey is required",
+ "bothPassSet": "Only one of passphrase or passkey should be provided"
+ },
+ "noPassAtAll": "Either passphrase or passkey is required",
+ "passkey": "Passkey"
+ },
+ "login": {
+ "or": "Or",
+ "usePasskey": "use passkey",
+ "usePassphrase": "use passphrase",
+ "orUse": "Or {button}",
+ "title": "Login",
+ "description_passphrase": "Enter your email and passphrase below to access your account.",
+ "action": "Login",
+ "error": "Login failed. Please retry",
+ "passphrase": {
+ "initial": "Requesting passkey",
+ "success": "Passkey saved",
+ "error": "Passkey could not be retrieved"
+ }
+ },
+ "confirm": {
+ "success": {
+ "title": "You’re all set!",
+ "description": "You’ve successfully secured your account",
+ "action": "Proceed"
+ },
+ "error": {
+ "title": "Verification failed",
+ "description": "The magic link you’ve tried is incorrect or outdated",
+ "action": "Request new E-Mail"
+ },
+ "title": "E-Mail verification",
+ "resend": {
+ "title": "Resend Verification E-Mail",
+ "description": "Enter your e-mail address to get the verification link again.",
+ "action": "Resend E-Mail",
+ "success": "E-Mail sent"
+ }
+ },
+ "logout": {
+ "title": "Logout complete",
+ "description": "You are logged-out.",
+ "success": "Logout successful",
+ "action": "Go to Login"
+ },
+ "passkey": {
+ "add": {
+ "initial": "Waiting for E-Mail Address",
+ "click": "Click/Tap to add passkey",
+ "loading": "Completing challenge",
+ "user": "Waiting for creation to complete",
+ "success": "Added Passkey successfully",
+ "error": "Passkey could not be added",
+ "retry": "Retry"
+ }
}
}
diff --git a/src/app.css b/src/app.css
index 29cefe3..a856a7a 100644
--- a/src/app.css
+++ b/src/app.css
@@ -126,6 +126,9 @@
--color-medium: var(--medium);
--color-dark: var(--dark);
--color-darker: var(--darker);
+
+ --max-w-sm: 400px;
+ --max-w-md: 640px;
}
@layer base {
diff --git a/src/app.d.ts b/src/app.d.ts
index a7c8026..8876c95 100644
--- a/src/app.d.ts
+++ b/src/app.d.ts
@@ -13,6 +13,9 @@ declare global {
// interface PageState {}
// interface Platform {}
}
+ interface PageState {
+ email?: string;
+ }
}
export {};
diff --git a/src/hooks.server.ts b/src/hooks.server.ts
index e98e909..8d234ca 100644
--- a/src/hooks.server.ts
+++ b/src/hooks.server.ts
@@ -5,7 +5,8 @@ import { corsHandle } from "./server-hooks/corsHandle";
import { loggingHandle } from "./server-hooks/loggingHandle";
import { rateLimitHandle } from "./server-hooks/rateLimitHandle";
import { secHeaderHandle } from "./server-hooks/secHeaderHandle";
-import { authHandle } from "./server-hooks/authHandle";
+import { apiAuthHandle } from "./server-hooks/apiAuthHandle";
+import { authGuard } from "./server-hooks/authGuard";
import { i18nHandle } from "./server-hooks/i18nHandle";
import { building } from "$app/environment";
@@ -40,5 +41,6 @@ export const handle = sequence(
rateLimitHandle,
corsHandle,
secHeaderHandle,
- authHandle
+ apiAuthHandle,
+ authGuard
);
diff --git a/src/lib/components/layouts/centered-card/action-hint.svelte b/src/lib/components/layouts/centered-card/action-hint.svelte
new file mode 100644
index 0000000..e053c8d
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/action-hint.svelte
@@ -0,0 +1,10 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/layouts/centered-card/action.svelte b/src/lib/components/layouts/centered-card/action.svelte
new file mode 100644
index 0000000..eab531b
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/action.svelte
@@ -0,0 +1,9 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/layouts/centered-card/description.svelte b/src/lib/components/layouts/centered-card/description.svelte
new file mode 100644
index 0000000..df36383
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/description.svelte
@@ -0,0 +1,11 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/layouts/centered-card/header.svelte b/src/lib/components/layouts/centered-card/header.svelte
new file mode 100644
index 0000000..0f8d05d
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/header.svelte
@@ -0,0 +1,9 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/layouts/centered-card/index.ts b/src/lib/components/layouts/centered-card/index.ts
new file mode 100644
index 0000000..b88fb2e
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/index.ts
@@ -0,0 +1,9 @@
+import Action from "./action.svelte";
+import ActionHint from "./action-hint.svelte";
+import Description from "./description.svelte";
+import Header from "./header.svelte";
+import Main from "./main.svelte";
+import Root from "./root.svelte";
+import Title from "./title.svelte";
+
+export { Action, ActionHint, Description, Header, Main, Root, Title };
diff --git a/src/lib/components/layouts/centered-card/main.svelte b/src/lib/components/layouts/centered-card/main.svelte
new file mode 100644
index 0000000..925987c
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/main.svelte
@@ -0,0 +1,10 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/layouts/centered-card/root.svelte b/src/lib/components/layouts/centered-card/root.svelte
new file mode 100644
index 0000000..3715f1d
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/root.svelte
@@ -0,0 +1,21 @@
+
+
+
+
+ {@render children?.()}
+
+
diff --git a/src/lib/components/layouts/centered-card/title.svelte b/src/lib/components/layouts/centered-card/title.svelte
new file mode 100644
index 0000000..9efb262
--- /dev/null
+++ b/src/lib/components/layouts/centered-card/title.svelte
@@ -0,0 +1,11 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/layouts/empty-layout/action.svelte b/src/lib/components/layouts/empty-layout/action.svelte
new file mode 100644
index 0000000..eac18d1
--- /dev/null
+++ b/src/lib/components/layouts/empty-layout/action.svelte
@@ -0,0 +1,7 @@
+
+
+{@render children?.()}
diff --git a/src/lib/components/layouts/empty-layout/index.ts b/src/lib/components/layouts/empty-layout/index.ts
new file mode 100644
index 0000000..5f51c7f
--- /dev/null
+++ b/src/lib/components/layouts/empty-layout/index.ts
@@ -0,0 +1,5 @@
+import Root from "./root.svelte";
+import Action from "./action.svelte";
+import Main from "./main.svelte";
+
+export { Root, Action, Main };
diff --git a/src/lib/components/layouts/empty-layout/main.svelte b/src/lib/components/layouts/empty-layout/main.svelte
new file mode 100644
index 0000000..e8a5e3a
--- /dev/null
+++ b/src/lib/components/layouts/empty-layout/main.svelte
@@ -0,0 +1,16 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/lib/components/layouts/empty-layout/root.svelte b/src/lib/components/layouts/empty-layout/root.svelte
new file mode 100644
index 0000000..c0150e2
--- /dev/null
+++ b/src/lib/components/layouts/empty-layout/root.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+ {@render children?.()}
+
+
diff --git a/src/lib/components/layouts/index.ts b/src/lib/components/layouts/index.ts
new file mode 100644
index 0000000..be69dbe
--- /dev/null
+++ b/src/lib/components/layouts/index.ts
@@ -0,0 +1,4 @@
+import * as EmptyLayout from "./empty-layout";
+import * as CenteredCard from "./centered-card";
+
+export { EmptyLayout, CenteredCard };
diff --git a/src/lib/components/templates/empty-state/center-loading-state.svelte b/src/lib/components/templates/empty-state/center-loading-state.svelte
new file mode 100644
index 0000000..5f4cad6
--- /dev/null
+++ b/src/lib/components/templates/empty-state/center-loading-state.svelte
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/templates/empty-state/empty-state.svelte b/src/lib/components/templates/empty-state/empty-state.svelte
new file mode 100644
index 0000000..e07ec90
--- /dev/null
+++ b/src/lib/components/templates/empty-state/empty-state.svelte
@@ -0,0 +1,18 @@
+
+
+
+
+ {headline}
+ {description}
+
diff --git a/src/lib/components/templates/empty-state/index.ts b/src/lib/components/templates/empty-state/index.ts
new file mode 100644
index 0000000..eeba8e0
--- /dev/null
+++ b/src/lib/components/templates/empty-state/index.ts
@@ -0,0 +1,4 @@
+import CenterState from "./empty-state.svelte";
+import CenterLoadingState from "./center-loading-state.svelte";
+
+export { CenterState, CenterLoadingState };
diff --git a/src/lib/components/templates/index.ts b/src/lib/components/templates/language-switch/index.ts
similarity index 100%
rename from src/lib/components/templates/index.ts
rename to src/lib/components/templates/language-switch/index.ts
diff --git a/src/lib/components/templates/language-switch.svelte b/src/lib/components/templates/language-switch/language-switch.svelte
similarity index 62%
rename from src/lib/components/templates/language-switch.svelte
rename to src/lib/components/templates/language-switch/language-switch.svelte
index a82e6dd..890ed83 100644
--- a/src/lib/components/templates/language-switch.svelte
+++ b/src/lib/components/templates/language-switch/language-switch.svelte
@@ -1,10 +1,17 @@
-
+
diff --git a/src/lib/components/ui/button/button.svelte b/src/lib/components/ui/button/button.svelte
index 4da437d..46d9ec8 100644
--- a/src/lib/components/ui/button/button.svelte
+++ b/src/lib/components/ui/button/button.svelte
@@ -1,10 +1,11 @@
@@ -49,6 +52,7 @@
type = "button",
disabled,
children,
+ isLoading,
...restProps
}: ButtonProps = $props();
@@ -75,6 +79,9 @@
{disabled}
{...restProps}
>
+ {#if isLoading}
+
+ {/if}
{@render children?.()}
{/if}
diff --git a/src/lib/components/ui/combobox/combobox.svelte b/src/lib/components/ui/combobox/combobox.svelte
index e95177b..7286e41 100644
--- a/src/lib/components/ui/combobox/combobox.svelte
+++ b/src/lib/components/ui/combobox/combobox.svelte
@@ -4,14 +4,17 @@
import { tick } from "svelte";
import * as Command from "$lib/components/ui/command/index.js";
import * as Popover from "$lib/components/ui/popover/index.js";
- import { Button } from "$lib/components/ui/button/index.js";
+ import { Button, type ButtonSize, type ButtonVariant } from "$lib/components/ui/button/index.js";
import { cn } from "$lib/utils.js";
const {
labels,
value,
options = [],
- onChange
+ onChange,
+ class: className = "",
+ triggerVariant = "outline",
+ triggerSize = "default"
}: {
labels: {
placeholder: string;
@@ -20,6 +23,9 @@
};
value: string;
onChange: (value: string) => void;
+ triggerVariant?: ButtonVariant;
+ triggerSize?: ButtonSize;
+ class?: string;
options?: { label: string; value: string; keywords?: string[] }[];
} = $props();
@@ -42,8 +48,9 @@