diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..661611739 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "allow": [ + "Read(//c/Users/julie/AppData/Local/Packages/Claude_pzs8sxrjxfjjc/LocalCache/Roaming/Claude/local-agent-mode-sessions/184ceb9f-c63d-479f-9de2-eb3191328fca/d7f7f518-2e61-4301-ac45-f5c68b836ee3/local_1b897c7e-d73e-435f-a62d-ce4a750943aa/outputs/frames/**)" + ] + } +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 4e98aedf6..317f25aa8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,7 +21,7 @@ } }, "[svelte]": { - "editor.defaultFormatter": "svelte.svelte-vscode", + "editor.defaultFormatter": "biomejs.biome", "editor.codeActionsOnSave": { "source.organizeImports.biome": "explicit", "source.fixAll.biome": "explicit" diff --git a/infrastructure/eid-wallet/package.json b/infrastructure/eid-wallet/package.json index c9c7e5ca8..d4951b6be 100644 --- a/infrastructure/eid-wallet/package.json +++ b/infrastructure/eid-wallet/package.json @@ -22,21 +22,19 @@ "license": "MIT", "dependencies": { "@auvo/tauri-plugin-crypto-hw-api": "^0.1.0", + "@choochmeque/tauri-plugin-notifications-api": "^0.4.3", "@didit-protocol/sdk-web": "^0.1.6", + "@fontsource-variable/roboto": "^5.2.10", + "@fontsource-variable/roboto-condensed": "^5.2.8", "@hugeicons/core-free-icons": "^1.0.13", "@hugeicons/svelte": "^1.0.2", - "@iconify/svelte": "^5.0.1", - "@ngneat/falso": "^7.3.0", "@tailwindcss/container-queries": "^0.1.1", "@tauri-apps/api": "^2.9.0", "@tauri-apps/plugin-barcode-scanner": "^2.4.2", "@tauri-apps/plugin-biometric": "^2.3.2", "@tauri-apps/plugin-deep-link": "^2.4.5", - "@choochmeque/tauri-plugin-notifications-api": "^0.4.3", "@tauri-apps/plugin-opener": "^2.5.2", "@tauri-apps/plugin-store": "^2.4.1", - "@veriff/incontext-sdk": "^2.4.0", - "@veriff/js-sdk": "^1.5.1", "axios": "^1.6.7", "blindvote": "workspace:*", "clsx": "^2.1.1", @@ -44,12 +42,10 @@ "flag-icons": "^7.3.2", "graphql-request": "^6.1.0", "html5-qrcode": "^2.3.8", - "import": "^0.0.6", "jose": "^5.2.0", "svelte-loading-spinners": "^0.3.6", "svelte-qrcode": "^1.0.1", "tailwind-merge": "^3.0.2", - "ts-md5": "^2.0.1", "uuid": "^11.1.0", "wallet-sdk": "workspace:*" }, diff --git a/infrastructure/eid-wallet/src-tauri/Cargo.toml b/infrastructure/eid-wallet/src-tauri/Cargo.toml index 790282b15..65f7866ae 100644 --- a/infrastructure/eid-wallet/src-tauri/Cargo.toml +++ b/infrastructure/eid-wallet/src-tauri/Cargo.toml @@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } +tauri = { version = "2", features = ["devtools"] } tauri-plugin-opener = "2" tauri-plugin-deep-link = "2" tauri-plugin-notifications = { version = "0.4", default-features = false, features = ["push-notifications", "notify-rust"] } diff --git a/infrastructure/eid-wallet/src-tauri/capabilities/desktop.json b/infrastructure/eid-wallet/src-tauri/capabilities/desktop.json new file mode 100644 index 000000000..d0147cab7 --- /dev/null +++ b/infrastructure/eid-wallet/src-tauri/capabilities/desktop.json @@ -0,0 +1,22 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "desktop-capability", + "description": "Capability for the main window on desktop", + "windows": [ + "main" + ], + "permissions": [ + "core:default", + "opener:default", + "store:default", + "deep-link:default", + "notifications:default", + "process:default", + "opener:allow-default-urls" + ], + "platforms": [ + "windows", + "macOS", + "linux" + ] +} diff --git a/infrastructure/eid-wallet/src-tauri/tauri.conf.json b/infrastructure/eid-wallet/src-tauri/tauri.conf.json index ab31af488..2ba504829 100644 --- a/infrastructure/eid-wallet/src-tauri/tauri.conf.json +++ b/infrastructure/eid-wallet/src-tauri/tauri.conf.json @@ -19,7 +19,8 @@ ], "security": { "capabilities": [ - "mobile-capability" + "mobile-capability", + "desktop-capability" ], "csp": null } diff --git a/infrastructure/eid-wallet/src/app.css b/infrastructure/eid-wallet/src/app.css index 7df806969..966a04168 100644 --- a/infrastructure/eid-wallet/src/app.css +++ b/infrastructure/eid-wallet/src/app.css @@ -1,12 +1,7 @@ @import "tailwindcss"; @import "flag-icons/css/flag-icons.min.css"; - -@font-face { - font-family: "Archivo"; - src: url("/fonts/Archivo-VariableFont_wdth,wght.ttf") format("truetype"); - font-weight: 100 900; - font-style: normal; -} +@import "@fontsource-variable/roboto"; +@import "@fontsource-variable/roboto-condensed"; @layer base { /* Typography */ @@ -15,34 +10,35 @@ } h2 { - @apply text-6xl/[1.5] text-black font-semibold; + @apply text-6xl/normal text-black font-semibold; } h3 { - @apply text-3xl/[1.5] text-black font-semibold; + @apply text-3xl/normal text-black font-semibold; } h4 { - @apply text-xl/[1.5] text-black font-semibold; + @apply text-xl/normal text-black font-semibold; } p { - @apply text-base/[1.5] text-black font-normal; + @apply text-base/normal text-black font-normal; } .small { - @apply text-xs/[1.5] text-black font-normal; + @apply text-xs/normal text-black font-normal; } } @theme { /* Custom theme */ - --color-primary: #8e52ff; - --color-primary-100: #e8dcff; - --color-primary-200: #d2baff; - --color-primary-300: #bb97ff; - --color-primary-400: #a575ff; - --color-primary-500: #8e52ff; + --color-primary: #8968ff; + --color-primary-50: #cec1ff66; + --color-primary-100: #e7e1ff; + --color-primary-200: #d0c3ff; + --color-primary-300: #b8a4ff; + --color-primary-400: #a186ff; + --color-primary-500: #8968ff; --color-secondary: #73efd5; --color-secondary-100: #e3fcf7; @@ -54,12 +50,13 @@ --color-white: #ffffff; --color-gray: #f5f5f5; - --color-black: #1f1f1f; - --color-black-100: #d2d2d2; - --color-black-300: #a5a5a5; - --color-black-500: #797979; - --color-black-700: #4c4c4c; - --color-black-900: #1f1f1f; + --color-black: #1d2636; + --color-black-50: #ebeff5; + --color-black-100: #dce0e8; + --color-black-300: #b8c0cb; + --color-black-500: #788292; + --color-black-700: #364052; + --color-black-900: #1d2636; --color-danger: #ff5255; --color-danger-100: #ffdcdd; @@ -67,28 +64,49 @@ --color-danger-300: #ff968e; --color-danger-400: #ff7b77; --color-danger-500: #ff5255; + + --color-card-alternative: #f2f5fa; + + /* Success / verified-state palette — used by the verified-ID chip and + the Legal ID accordion. Mirrors the lime scale we were reaching for. */ + --color-success: #84cc16; + --color-success-100: #ecfccb; + --color-success-200: #d9f99d; + --color-success-300: #c0f77c; + --color-success-500: #84cc16; + --color-success-900: #365314; + + /* Typography */ + --font-sans: "Roboto Variable", sans-serif; + --font-condensed: "Roboto Condensed Variable", sans-serif; + + /* Display size — Greeting h1/h2 and the NameInput field. */ + --text-display: 40px; + --text-display--line-height: 1; + + /* Micro chip / badge label — verified/unverified pills, notification + count, the small checkmark on the Legal ID avatar. */ + --text-chip: 10px; + --text-chip--line-height: 1.2; + + /* Pill button label — ADD / INVITE / REPLACE buttons. */ + --text-pill: 13px; + --text-pill--line-height: 1.2; + + /* Standard elevation used across the main-page cards. */ + --shadow-card: 0px 4px 19.9px 0px #00000024; } body { - font-family: "Archivo", sans-serif; - /* padding-top: env(safe-area-inset-top); */ - /* padding-bottom: env(safe-area-inset-bottom); */ padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); - background-color: var(--color-primary); } /* Ensure background remains correct during transitions */ :root[data-transition]::view-transition-group(root), :root[data-transition]::view-transition-old(root), :root[data-transition]::view-transition-new(root) { - background-color: white !important; /* Default to white */ -} - -.dark:root[data-transition]::view-transition-group(root), -.dark:root[data-transition]::view-transition-old(root), -.dark:root[data-transition]::view-transition-new(root) { - background-color: #0b0d13 !important; /* Use dark background in dark mode */ + background-color: white !important; } /* Prevent flickering */ @@ -155,18 +173,24 @@ body { } } -:root[data-transition]::view-transition-old(root) { - animation: 400ms ease-out both fade-out; +/* Forward (data-transition="left"): the NEW page slides in from the right, + covering the OLD which stays put. */ +:root[data-transition="left"]::view-transition-old(root) { + animation: none; } - -:root[data-transition="right"]::view-transition-new(root) { +:root[data-transition="left"]::view-transition-new(root) { animation: 200ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right; position: relative; z-index: 1; } -:root[data-transition="left"]::view-transition-new(root) { - animation: 200ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-left; +/* Backward (data-transition="right"): the OLD page slides out to the right, + revealing the NEW page underneath which stays put. */ +:root[data-transition="right"]::view-transition-new(root) { + animation: none; +} +:root[data-transition="right"]::view-transition-old(root) { + animation: 200ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-right; position: relative; z-index: 1; } diff --git a/infrastructure/eid-wallet/src/app.html b/infrastructure/eid-wallet/src/app.html index 02490b24f..e1f2955cf 100644 --- a/infrastructure/eid-wallet/src/app.html +++ b/infrastructure/eid-wallet/src/app.html @@ -4,7 +4,6 @@ - Tauri + SvelteKit + Typescript App %sveltekit.head% diff --git a/infrastructure/eid-wallet/src/lib/actions/keyboardInset.ts b/infrastructure/eid-wallet/src/lib/actions/keyboardInset.ts new file mode 100644 index 000000000..95b381c39 --- /dev/null +++ b/infrastructure/eid-wallet/src/lib/actions/keyboardInset.ts @@ -0,0 +1,47 @@ +/** Exposes the soft keyboard's height as `--kb-inset` on the node, and locks + * document scroll while mounted so the page can't be panned when the + * keyboard pushes the layout viewport past the visual viewport. */ +export function keyboardInset(node: HTMLElement) { + const vv = window.visualViewport; + + const update = () => { + if (!vv) { + node.style.setProperty("--kb-inset", "0px"); + return; + } + const inset = Math.max(0, window.innerHeight - vv.height); + node.style.setProperty("--kb-inset", `${inset}px`); + }; + + const html = document.documentElement; + const body = document.body; + const prevHtmlOverflow = html.style.overflow; + const prevBodyOverflow = body.style.overflow; + const prevHtmlOverscroll = html.style.overscrollBehavior; + const prevBodyOverscroll = body.style.overscrollBehavior; + html.style.overflow = "hidden"; + body.style.overflow = "hidden"; + html.style.overscrollBehavior = "none"; + body.style.overscrollBehavior = "none"; + + const blockTouchMove = (e: TouchEvent) => { + if (e.touches.length === 1) e.preventDefault(); + }; + document.addEventListener("touchmove", blockTouchMove, { passive: false }); + + vv?.addEventListener("resize", update); + vv?.addEventListener("scroll", update); + update(); + + return { + destroy() { + vv?.removeEventListener("resize", update); + vv?.removeEventListener("scroll", update); + document.removeEventListener("touchmove", blockTouchMove); + html.style.overflow = prevHtmlOverflow; + body.style.overflow = prevBodyOverflow; + html.style.overscrollBehavior = prevHtmlOverscroll; + body.style.overscrollBehavior = prevBodyOverscroll; + }, + }; +} diff --git a/infrastructure/eid-wallet/src/lib/fragments/AppNav/AppNav.svelte b/infrastructure/eid-wallet/src/lib/fragments/AppNav/AppNav.svelte index 5743cc361..8dcb2b349 100644 --- a/infrastructure/eid-wallet/src/lib/fragments/AppNav/AppNav.svelte +++ b/infrastructure/eid-wallet/src/lib/fragments/AppNav/AppNav.svelte @@ -1,33 +1,53 @@ - + \ No newline at end of file + --> diff --git a/infrastructure/eid-wallet/src/lib/fragments/SettingsNavigationBtn/SettingsNavigationBtn.svelte b/infrastructure/eid-wallet/src/lib/fragments/SettingsNavigationBtn/SettingsNavigationBtn.svelte index b691c2409..072206ac6 100644 --- a/infrastructure/eid-wallet/src/lib/fragments/SettingsNavigationBtn/SettingsNavigationBtn.svelte +++ b/infrastructure/eid-wallet/src/lib/fragments/SettingsNavigationBtn/SettingsNavigationBtn.svelte @@ -1,25 +1,67 @@ - -
-
- -
-

{label}

+ +
+ {#if iconSlot} + {@render iconSlot()} + {:else if icon} + + {/if} +
+
+

{label}

+ {#if subtitle} +

{subtitle}

+ {/if}
- - \ No newline at end of file + +
diff --git a/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte b/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte index ac5317a25..6988238a8 100644 --- a/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte +++ b/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte @@ -1,62 +1,163 @@
-
- illustration - illustration + +
+
+ + eID + + +
+

+ Your Digital Self +

+
+ W3DS +
+
+ + {#if showDrawer}
-

- eID Wallet -

-

- for Web 3.0 Data Space -

+ {#if oncontinue} + + Continue + + {:else} + + Create Digital Self + + + Restore Digital Self + +

+ By continuing you agree to our + + Terms & Conditions + + and + + Privacy Policy + +

+ {/if}
- logo -
+ {/if}
diff --git a/infrastructure/eid-wallet/src/lib/global/runtime.svelte.ts b/infrastructure/eid-wallet/src/lib/global/runtime.svelte.ts index a6a8a4142..fc22038da 100644 --- a/infrastructure/eid-wallet/src/lib/global/runtime.svelte.ts +++ b/infrastructure/eid-wallet/src/lib/global/runtime.svelte.ts @@ -3,7 +3,11 @@ import type { BiometryType } from "@tauri-apps/plugin-biometric"; export const runtime = $state<{ header: { title: string | undefined; + subtitle: string | undefined; backEnabled: boolean | undefined; + /** Optional override for the AppNav back chevron. Falls back to + * window.history.back() when undefined. */ + onback: (() => void) | undefined; }; /** * None = 0, @@ -15,7 +19,9 @@ export const runtime = $state<{ }>({ header: { title: undefined, + subtitle: undefined, backEnabled: undefined, + onback: undefined, }, biometry: undefined, }); diff --git a/infrastructure/eid-wallet/src/lib/global/state.ts b/infrastructure/eid-wallet/src/lib/global/state.ts index 3dc2587f3..cc6481730 100644 --- a/infrastructure/eid-wallet/src/lib/global/state.ts +++ b/infrastructure/eid-wallet/src/lib/global/state.ts @@ -166,6 +166,26 @@ export class GlobalState { } } + get hasSeenWelcomeTour() { + return this.#store + .get("hasSeenWelcomeTour") + .then((value) => value ?? false) + .catch((error) => { + console.error("Failed to get welcome-tour status:", error); + return false; + }); + } + + set hasSeenWelcomeTour(value: boolean | Promise) { + const resolve = + value instanceof Promise ? value : Promise.resolve(value); + resolve + .then((resolved) => this.#store.set("hasSeenWelcomeTour", resolved)) + .catch((error) => { + console.error("Failed to set welcome-tour status:", error); + }); + } + async reset() { try { await this.securityController.clear(); @@ -174,6 +194,7 @@ export class GlobalState { await this.keyService.clear(); await this.#store.delete("initialized"); await this.#store.delete("isOnboardingComplete"); + await this.#store.delete("hasSeenWelcomeTour"); } catch (error) { console.error("Failed to reset global state:", error); } diff --git a/infrastructure/eid-wallet/src/lib/stores/language.ts b/infrastructure/eid-wallet/src/lib/stores/language.ts new file mode 100644 index 000000000..9793da400 --- /dev/null +++ b/infrastructure/eid-wallet/src/lib/stores/language.ts @@ -0,0 +1,71 @@ +export interface Language { + /** Display name, e.g. "English". */ + name: string; + /** ISO 3166-1 alpha-2 country code (lowercased) used for `fi-${country}` + * flag rendering. */ + country: string; + /** Whether localized strings exist for this language yet. Only enabled + * languages can be selected by the user. */ + enabled: boolean; +} + +export const AVAILABLE_LANGUAGES: Language[] = [ + { name: "English", country: "gb", enabled: true }, + { name: "Spanish", country: "es", enabled: false }, + { name: "German", country: "de", enabled: false }, + { name: "French", country: "fr", enabled: false }, + { name: "Luxembourgish", country: "lu", enabled: false }, + { name: "Dutch", country: "nl", enabled: false }, +]; + +const STORAGE_KEY = "eid_wallet_language"; +const DEFAULT = AVAILABLE_LANGUAGES[0]; + +let cached: Language | null = null; +let listeners: Array<() => void> = []; + +function loadFromStorage(): Language { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) { + const parsed = JSON.parse(raw) as { name?: string }; + const found = AVAILABLE_LANGUAGES.find( + (l) => l.name === parsed.name && l.enabled, + ); + if (found) return found; + } + } catch { + // ignore — fall through to default + } + return DEFAULT; +} + +function notify() { + for (const fn of listeners) fn(); +} + +export function getCurrentLanguage(): Language { + if (cached) return cached; + cached = loadFromStorage(); + return cached; +} + +export function setCurrentLanguage(name: string): void { + const found = AVAILABLE_LANGUAGES.find((l) => l.name === name); + if (!found || !found.enabled) return; + if (cached?.name === found.name) return; + cached = found; + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify({ name: found.name })); + } catch (error) { + console.warn("Failed to persist language preference:", error); + } + notify(); +} + +export function subscribe(fn: () => void): () => void { + listeners.push(fn); + return () => { + listeners = listeners.filter((l) => l !== fn); + }; +} diff --git a/infrastructure/eid-wallet/src/lib/stores/notifications.ts b/infrastructure/eid-wallet/src/lib/stores/notifications.ts index 21ce3d513..b6d3394bb 100644 --- a/infrastructure/eid-wallet/src/lib/stores/notifications.ts +++ b/infrastructure/eid-wallet/src/lib/stores/notifications.ts @@ -95,3 +95,119 @@ export function clearAllNotifications(): void { saveNotifications([]); notify(); } + +// Dev-only seed used to populate the notifications list with a varied set of +// fixtures while iterating on the notifications UI. Replaces any existing +// notifications. Spread across "Just now" → ~a week ago to exercise the +// relative-time formatter. +export function seedDummyNotifications(): void { + const now = Date.now(); + const minutes = (n: number) => + new Date(now - n * 60_000).toISOString(); + const hours = (n: number) => + new Date(now - n * 3_600_000).toISOString(); + const days = (n: number) => + new Date(now - n * 86_400_000).toISOString(); + + const dummies: StoredNotification[] = [ + { + id: crypto.randomUUID(), + title: "Alex Chen", + body: "Are we still on for the meeting later today?", + data: { + type: "new_message", + globalChatId: "dummy-chat-alex", + globalMessageId: "dummy-msg-1", + avatar: "https://i.pravatar.cc/96?img=12", + }, + createdAt: minutes(0), + }, + { + id: crypto.randomUUID(), + title: "Verification complete", + body: "Your Legal ID has been verified successfully.", + createdAt: minutes(8), + }, + { + id: crypto.randomUUID(), + title: "Sam Patel", + body: "Sent you a photo", + data: { + type: "new_message", + globalChatId: "dummy-chat-sam", + globalMessageId: "dummy-msg-2", + avatar: "https://i.pravatar.cc/96?img=33", + }, + createdAt: minutes(42), + }, + { + id: crypto.randomUUID(), + title: "Social binding request", + body: "Jordan Reyes wants to create a social binding with you.", + createdAt: hours(2), + }, + { + id: crypto.randomUUID(), + title: "eVault storage", + body: "You've used 60% of your eVault storage.", + createdAt: hours(5), + }, + { + id: crypto.randomUUID(), + title: "Mia Tanaka", + body: "👋", + data: { + type: "new_message", + globalChatId: "dummy-chat-mia", + globalMessageId: "dummy-msg-3", + avatar: "https://i.pravatar.cc/96?img=45", + }, + createdAt: hours(9), + }, + { + id: crypto.randomUUID(), + title: "Backup reminder", + body: "It's been 30 days since your last eVault backup. Take a moment to back up now.", + createdAt: hours(18), + }, + { + id: crypto.randomUUID(), + title: "App connected", + body: "Pictique is now authorized to read your eName.", + createdAt: days(1), + }, + { + id: crypto.randomUUID(), + title: "Security alert", + body: "A new device signed in to your eVault. Wasn't you? Tap to review.", + createdAt: days(2), + }, + { + id: crypto.randomUUID(), + title: "Riley Park", + body: "Thanks for sending the docs!", + data: { + type: "new_message", + globalChatId: "dummy-chat-riley", + globalMessageId: "dummy-msg-4", + avatar: "https://i.pravatar.cc/96?img=27", + }, + createdAt: days(3), + }, + { + id: crypto.randomUUID(), + title: "Marketplace", + body: "Dreamsync just launched — try it out from the apps marketplace.", + createdAt: days(5), + }, + { + id: crypto.randomUUID(), + title: "Welcome to eID Wallet", + body: "Thanks for setting up your wallet. You can replay the welcome tour anytime from settings.", + createdAt: days(8), + }, + ]; + + saveNotifications(dummies); + notify(); +} diff --git a/infrastructure/eid-wallet/src/lib/stores/personalBinding.ts b/infrastructure/eid-wallet/src/lib/stores/personalBinding.ts new file mode 100644 index 000000000..8fd3bca5b --- /dev/null +++ b/infrastructure/eid-wallet/src/lib/stores/personalBinding.ts @@ -0,0 +1,90 @@ +/** + * Local view of the Personal binding flow. The store is just state — actual + * BE writes live in lib/utils/personalBinding.ts. Pages call those helpers + * and then call the mutators here to reflect the new state. + * + * Items that have round-tripped through the vault carry their + * metaEnvelopeId; pre-save items (rare — only seen while a create mutation + * is in flight) carry null. + */ + +import { writable } from "svelte/store"; + +export interface PhotoMark { + /** Local UUID — stable key for list rendering. */ + id: string; + /** Server-assigned metaEnvelopeId once the binding doc is persisted. */ + metaEnvelopeId: string | null; + /** Data URL of the captured/picked image (= photoBlob on the BE). */ + dataUrl: string; + description: string; + source: "camera" | "gallery"; +} + +export interface ParametersMark { + metaEnvelopeId: string; + text: string; +} + +export interface KnowledgeMark { + metaEnvelopeId: string; + question: string; +} + +export interface PersonalBinding { + photos: PhotoMark[]; + parameters: ParametersMark | null; + /** Note: raw answer is never kept locally. Only the question text. */ + knowledge: KnowledgeMark | null; +} + +const initial: PersonalBinding = { + photos: [], + parameters: null, + knowledge: null, +}; + +export const personalBinding = writable(initial); + +export function replaceAll(next: PersonalBinding): void { + personalBinding.set(next); +} + +export function addPhotoLocal(photo: PhotoMark): void { + personalBinding.update((b) => ({ ...b, photos: [...b.photos, photo] })); +} + +export function updatePhotoLocal( + metaEnvelopeId: string, + patch: Partial, +): void { + personalBinding.update((b) => ({ + ...b, + photos: b.photos.map((p) => + p.metaEnvelopeId === metaEnvelopeId ? { ...p, ...patch } : p, + ), + })); +} + +export function removePhotoLocal(metaEnvelopeId: string): void { + personalBinding.update((b) => ({ + ...b, + photos: b.photos.filter((p) => p.metaEnvelopeId !== metaEnvelopeId), + })); +} + +export function setParametersLocal(mark: ParametersMark | null): void { + personalBinding.update((b) => ({ ...b, parameters: mark })); +} + +export function setKnowledgeLocal(mark: KnowledgeMark | null): void { + personalBinding.update((b) => ({ ...b, knowledge: mark })); +} + +export function marksAchieved(b: PersonalBinding): number { + let n = 0; + if (b.photos.length > 0) n++; + if (b.parameters && b.parameters.text.trim().length > 0) n++; + if (b.knowledge && b.knowledge.question.trim().length > 0) n++; + return n; +} diff --git a/infrastructure/eid-wallet/src/lib/ui/BottomSheet/BottomSheet.svelte b/infrastructure/eid-wallet/src/lib/ui/BottomSheet/BottomSheet.svelte index 43ed5a191..1842588ee 100644 --- a/infrastructure/eid-wallet/src/lib/ui/BottomSheet/BottomSheet.svelte +++ b/infrastructure/eid-wallet/src/lib/ui/BottomSheet/BottomSheet.svelte @@ -1,7 +1,9 @@ {#if isOpen} - -