diff --git a/.env.example b/.env.example index acdfda8a..67764b19 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,17 @@ +# Pictique Configuration +PUBLIC_PICTIQUE_BASE_URL=your_public_pictique_base_url_here + +# Blabsy Configuration +PUBLIC_BLABSY_BASE_URL=your_public_blabsy_base_url_here + +# Eid Wallet Configuration +PUBLIC_REGISTRY_URL=your_public_registry_url_here +PUBLIC_PROVISIONER_URL=your_public_provisioner_url_here + # Neo4j Configuration NEO4J_URI=bolt://neo4j:7687 NEO4J_USER=neo4j NEO4J_PASSWORD=your_secure_password_here # eVault Configuration -PORT=4000 \ No newline at end of file +PORT=4000 diff --git a/infrastructure/eid-wallet/.storybook/main.ts b/infrastructure/eid-wallet/.storybook/main.ts index 3602cf26..598d02a9 100644 --- a/infrastructure/eid-wallet/.storybook/main.ts +++ b/infrastructure/eid-wallet/.storybook/main.ts @@ -1,22 +1,22 @@ import type { StorybookConfig } from "@storybook/sveltekit"; -import { join, dirname } from "path"; +import { join, dirname } from "node:path"; function getAbsolutePath(value: string): any { - return dirname(require.resolve(join(value, "package.json"))); + return dirname(require.resolve(join(value, "package.json"))); } const config: StorybookConfig = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|ts)"], - addons: [ - getAbsolutePath("@storybook/addon-essentials"), - getAbsolutePath("@chromatic-com/storybook"), - getAbsolutePath("@storybook/experimental-addon-test"), - ], - framework: { - name: "@storybook/sveltekit", - options: {}, - }, + stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|ts)"], + addons: [ + getAbsolutePath("@storybook/addon-essentials"), + getAbsolutePath("@chromatic-com/storybook"), + getAbsolutePath("@storybook/experimental-addon-test"), + ], + framework: { + name: "@storybook/sveltekit", + options: {}, + }, - staticDirs: ["../static"], + staticDirs: ["../static"], }; export default config; diff --git a/infrastructure/eid-wallet/.storybook/preview.ts b/infrastructure/eid-wallet/.storybook/preview.ts index b2cb78f5..3503b2ee 100644 --- a/infrastructure/eid-wallet/.storybook/preview.ts +++ b/infrastructure/eid-wallet/.storybook/preview.ts @@ -2,14 +2,14 @@ import type { Preview } from "@storybook/svelte"; import "../src/app.css"; const preview: Preview = { - parameters: { - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/i, - }, - }, - }, + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, }; export default preview; diff --git a/infrastructure/eid-wallet/.vscode/extensions.json b/infrastructure/eid-wallet/.vscode/extensions.json index 648a8aa6..bd589476 100644 --- a/infrastructure/eid-wallet/.vscode/extensions.json +++ b/infrastructure/eid-wallet/.vscode/extensions.json @@ -1,7 +1,7 @@ { - "recommendations": [ - "svelte.svelte-vscode", - "tauri-apps.tauri-vscode", - "rust-lang.rust-analyzer" - ] + "recommendations": [ + "svelte.svelte-vscode", + "tauri-apps.tauri-vscode", + "rust-lang.rust-analyzer" + ] } diff --git a/infrastructure/eid-wallet/.vscode/settings.json b/infrastructure/eid-wallet/.vscode/settings.json index 69e59732..3f9b0f9e 100644 --- a/infrastructure/eid-wallet/.vscode/settings.json +++ b/infrastructure/eid-wallet/.vscode/settings.json @@ -1,3 +1,3 @@ { - "svelte.enable-ts-plugin": true + "svelte.enable-ts-plugin": true } diff --git a/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json b/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json index 90eea7ec..1ec4b8c0 100644 --- a/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,116 +1,116 @@ { - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "AppIcon-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "AppIcon-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "AppIcon-29x29@2x-1.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "AppIcon-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "AppIcon-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "AppIcon-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "AppIcon-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "AppIcon-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "AppIcon-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "AppIcon-20x20@2x-1.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "AppIcon-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "AppIcon-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "AppIcon-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "AppIcon-40x40@2x-1.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "AppIcon-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "AppIcon-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "AppIcon-83.5x83.5@2x.png", - "scale" : "2x" - }, - { - "size" : "1024x1024", - "idiom" : "ios-marketing", - "filename" : "AppIcon-512@2x.png", - "scale" : "1x" + "images": [ + { + "size": "20x20", + "idiom": "iphone", + "filename": "AppIcon-20x20@2x.png", + "scale": "2x" + }, + { + "size": "20x20", + "idiom": "iphone", + "filename": "AppIcon-20x20@3x.png", + "scale": "3x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "AppIcon-29x29@2x-1.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "iphone", + "filename": "AppIcon-29x29@3x.png", + "scale": "3x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "AppIcon-40x40@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "iphone", + "filename": "AppIcon-40x40@3x.png", + "scale": "3x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "AppIcon-60x60@2x.png", + "scale": "2x" + }, + { + "size": "60x60", + "idiom": "iphone", + "filename": "AppIcon-60x60@3x.png", + "scale": "3x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "AppIcon-20x20@1x.png", + "scale": "1x" + }, + { + "size": "20x20", + "idiom": "ipad", + "filename": "AppIcon-20x20@2x-1.png", + "scale": "2x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "AppIcon-29x29@1x.png", + "scale": "1x" + }, + { + "size": "29x29", + "idiom": "ipad", + "filename": "AppIcon-29x29@2x.png", + "scale": "2x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "AppIcon-40x40@1x.png", + "scale": "1x" + }, + { + "size": "40x40", + "idiom": "ipad", + "filename": "AppIcon-40x40@2x-1.png", + "scale": "2x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "AppIcon-76x76@1x.png", + "scale": "1x" + }, + { + "size": "76x76", + "idiom": "ipad", + "filename": "AppIcon-76x76@2x.png", + "scale": "2x" + }, + { + "size": "83.5x83.5", + "idiom": "ipad", + "filename": "AppIcon-83.5x83.5@2x.png", + "scale": "2x" + }, + { + "size": "1024x1024", + "idiom": "ios-marketing", + "filename": "AppIcon-512@2x.png", + "scale": "1x" + } + ], + "info": { + "version": 1, + "author": "xcode" } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file +} diff --git a/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/Contents.json b/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/Contents.json index da4a164c..9e0da7c5 100644 --- a/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/Contents.json +++ b/infrastructure/eid-wallet/src-tauri/gen/apple/Assets.xcassets/Contents.json @@ -1,6 +1,6 @@ { - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/infrastructure/eid-wallet/src-tauri/tauri.conf.json b/infrastructure/eid-wallet/src-tauri/tauri.conf.json index 0ccfcaa2..a7e7c556 100644 --- a/infrastructure/eid-wallet/src-tauri/tauri.conf.json +++ b/infrastructure/eid-wallet/src-tauri/tauri.conf.json @@ -18,9 +18,7 @@ } ], "security": { - "capabilities": [ - "mobile-capability" - ], + "capabilities": ["mobile-capability"], "csp": null } }, diff --git a/infrastructure/eid-wallet/src/declaration.d.ts b/infrastructure/eid-wallet/src/declaration.d.ts new file mode 100644 index 00000000..356f352c --- /dev/null +++ b/infrastructure/eid-wallet/src/declaration.d.ts @@ -0,0 +1,7 @@ +declare module "svelte-qrcode" { + import { SvelteComponentTyped } from "svelte"; + export default class QRCode extends SvelteComponentTyped<{ + value: string; + size?: number; + }> {} +} diff --git a/infrastructure/eid-wallet/src/env.d.ts b/infrastructure/eid-wallet/src/env.d.ts new file mode 100644 index 00000000..103b229d --- /dev/null +++ b/infrastructure/eid-wallet/src/env.d.ts @@ -0,0 +1,8 @@ +/// + +declare namespace App {} + +declare module "$env/static/public" { + export const PUBLIC_REGISTRY_URL: string; + export const PUBLIC_PROVISIONER_URL: string; +} diff --git a/infrastructure/eid-wallet/src/lib/fragments/Hero/Hero.svelte b/infrastructure/eid-wallet/src/lib/fragments/Hero/Hero.svelte index 164a6b40..4c97f00f 100644 --- a/infrastructure/eid-wallet/src/lib/fragments/Hero/Hero.svelte +++ b/infrastructure/eid-wallet/src/lib/fragments/Hero/Hero.svelte @@ -1,25 +1,25 @@
diff --git a/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte b/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte index 7fdbd699..cefc4c68 100644 --- a/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte +++ b/infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte @@ -1,47 +1,47 @@
diff --git a/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte b/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte index 2bbf39e7..ac5317a2 100644 --- a/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte +++ b/infrastructure/eid-wallet/src/lib/fragments/SplashScreen/SplashScreen.svelte @@ -1,6 +1,6 @@
parsed: any; }; }; @@ -62,7 +63,11 @@ export class VaultController { /** * Set the profile creation status */ - set profileCreationStatus(status: "idle" | "loading" | "success" | "failed") { + set profileCreationStatus(status: + | "idle" + | "loading" + | "success" + | "failed") { this.#profileCreationStatus = status; } @@ -76,20 +81,23 @@ export class VaultController { } this.profileCreationStatus = "loading"; - + try { const userData = await this.#userController.user; const displayName = userData?.name || vault.ename; - + await this.createUserProfileInEVault( vault.ename, displayName, - vault.ename + vault.ename, ); - + this.profileCreationStatus = "success"; } catch (error) { - console.error("Failed to create UserProfile in eVault (retry):", error); + console.error( + "Failed to create UserProfile in eVault (retry):", + error, + ); this.profileCreationStatus = "failed"; throw error; } @@ -101,7 +109,7 @@ export class VaultController { private async resolveEndpoint(w3id: string): Promise { try { const response = await axios.get( - new URL(`resolve?w3id=${w3id}`, PUBLIC_REGISTRY_URL).toString() + new URL(`resolve?w3id=${w3id}`, PUBLIC_REGISTRY_URL).toString(), ); return new URL("/graphql", response.data.uri).toString(); } catch (error) { @@ -128,10 +136,10 @@ export class VaultController { ename: string, displayName: string, w3id: string, - maxRetries: number = 10 + maxRetries = 10, ): Promise { - console.log("attempting") - const username = ename.replace('@', ''); + console.log("attempting"); + const username = ename.replace("@", ""); const now = new Date().toISOString(); const userProfile: UserProfile = { @@ -145,14 +153,16 @@ export class VaultController { isPrivate: false, createdAt: now, updatedAt: now, - isArchived: false + isArchived: false, }; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const client = await this.ensureClient(w3id); - console.log(`Attempting to create UserProfile in eVault (attempt ${attempt}/${maxRetries})`); + console.log( + `Attempting to create UserProfile in eVault (attempt ${attempt}/${maxRetries})`, + ); const response = await client.request( STORE_META_ENVELOPE, @@ -162,57 +172,64 @@ export class VaultController { payload: userProfile, acl: ["*"], }, - } + }, ); - console.log("UserProfile created successfully in eVault:", response.storeMetaEnvelope.metaEnvelope.id); + console.log( + "UserProfile created successfully in eVault:", + response.storeMetaEnvelope.metaEnvelope.id, + ); return; } catch (error) { - console.error(`Failed to create UserProfile in eVault (attempt ${attempt}/${maxRetries}):`, error); + console.error( + `Failed to create UserProfile in eVault (attempt ${attempt}/${maxRetries}):`, + error, + ); if (attempt === maxRetries) { - console.error("Max retries reached, giving up on UserProfile creation"); + console.error( + "Max retries reached, giving up on UserProfile creation", + ); throw error; } // Wait before retrying (exponential backoff) - const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000); + const delay = Math.min(1000 * 2 ** (attempt - 1), 10000); console.log(`Waiting ${delay}ms before retry...`); - await new Promise(resolve => setTimeout(resolve, delay)); + await new Promise((resolve) => setTimeout(resolve, delay)); } } } - set vault( - vault: - | Promise | undefined> - | Record - | undefined, - ) { - if (vault instanceof Promise) { + set vault(vault: + | Promise | undefined> + | Record + | undefined) { + if (vault instanceof Promise) vault .then(async (resolvedUser) => { if (resolvedUser?.ename) { this.#store.set("vault", resolvedUser); - // Set loading status this.profileCreationStatus = "loading"; - // Get user data for display name const userData = await this.#userController.user; - const displayName = userData?.name || resolvedUser.ename; - + const displayName = + userData?.name || resolvedUser?.ename; + try { await this.createUserProfileInEVault( - resolvedUser.ename, - displayName, - resolvedUser.ename + resolvedUser?.ename as string, + displayName as string, + resolvedUser?.ename as string, ); this.profileCreationStatus = "success"; } catch (error) { - console.error("Failed to create UserProfile in eVault:", error); + console.error( + "Failed to create UserProfile in eVault:", + error, + ); this.profileCreationStatus = "failed"; - // Don't throw here to avoid breaking the vault setting } } }) @@ -220,42 +237,46 @@ export class VaultController { console.error("Failed to set vault:", error); this.profileCreationStatus = "failed"; }); - } else { - if (vault?.ename) { - this.#store.set("vault", vault); - - // Set loading status - this.profileCreationStatus = "loading"; - - // Get user data for display name and create UserProfile - (async () => { + else if (vault?.ename) { + this.#store.set("vault", vault); + + // Set loading status + this.profileCreationStatus = "loading"; + + // Get user data for display name and create UserProfile + (async () => { + try { + const userData = await this.#userController.user; + const displayName = userData?.name || vault.ename; + + await this.createUserProfileInEVault( + vault.ename, + displayName, + vault.ename, + ); + this.profileCreationStatus = "success"; + } catch (error) { + console.error( + "Failed to get user data or create UserProfile:", + error, + ); + // Fallback to using ename as display name try { - const userData = await this.#userController.user; - const displayName = userData?.name || vault.ename; - await this.createUserProfileInEVault( vault.ename, - displayName, - vault.ename + vault.ename, + vault.ename, ); this.profileCreationStatus = "success"; - } catch (error) { - console.error("Failed to get user data or create UserProfile:", error); - // Fallback to using ename as display name - try { - await this.createUserProfileInEVault( - vault.ename, - vault.ename, - vault.ename - ); - this.profileCreationStatus = "success"; - } catch (fallbackError) { - console.error("Failed to create UserProfile in eVault (fallback):", fallbackError); - this.profileCreationStatus = "failed"; - } + } catch (fallbackError) { + console.error( + "Failed to create UserProfile in eVault (fallback):", + fallbackError, + ); + this.profileCreationStatus = "failed"; } - })(); - } + } + })(); } } @@ -275,11 +296,11 @@ export class VaultController { } // Getters for internal properties - get client() { + getclient() { return this.#client; } - get endpoint() { + getendpoint() { return this.#endpoint; } } diff --git a/infrastructure/eid-wallet/src/lib/global/controllers/user.ts b/infrastructure/eid-wallet/src/lib/global/controllers/user.ts index ce3ee5fe..54ce1f22 100644 --- a/infrastructure/eid-wallet/src/lib/global/controllers/user.ts +++ b/infrastructure/eid-wallet/src/lib/global/controllers/user.ts @@ -55,14 +55,10 @@ export class UserController { * @throws {Error} If the user state cannot be set in the store */ - - - set user( - user: - | Promise | undefined> - | Record - | undefined, - ) { + set user(user: + | Promise | undefined> + | Record + | undefined) { if (user instanceof Promise) { user.then((resolvedUser) => { this.#store.set("user", resolvedUser); @@ -89,13 +85,7 @@ export class UserController { }); } - - set isFake( - f: - | Promise - | boolean - | undefined, - ) { + set isFake(f: Promise | boolean | undefined) { if (f instanceof Promise) { f.then((resolved) => { this.#store.set("fake", resolved); @@ -122,12 +112,10 @@ export class UserController { }); } - set document( - document: - | Promise | undefined> - | Record - | undefined, - ) { + set document(document: + | Promise | undefined> + | Record + | undefined) { if (document instanceof Promise) { document .then((resolvedDoc) => { diff --git a/infrastructure/eid-wallet/src/lib/global/state.ts b/infrastructure/eid-wallet/src/lib/global/state.ts index 7fde698b..e1f1d1af 100644 --- a/infrastructure/eid-wallet/src/lib/global/state.ts +++ b/infrastructure/eid-wallet/src/lib/global/state.ts @@ -1,7 +1,7 @@ import { Store } from "@tauri-apps/plugin-store"; +import { VaultController } from "./controllers/evault"; import { SecurityController } from "./controllers/security"; import { UserController } from "./controllers/user"; -import { VaultController } from "./controllers/evault"; /** * @author SoSweetHam * @description A centralized state that can be used to control the global state of the application, meant to be used as a singleton through the main layout component. diff --git a/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte b/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte index f380472f..82372f47 100644 --- a/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte +++ b/infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte @@ -1,56 +1,56 @@
- import { cn } from "$lib/utils"; - import { onMount } from "svelte"; - import type { HTMLAttributes } from "svelte/elements"; - - const KEYBOARD = { - BACKSPACE: "Backspace", - DELETE: "Delete", - ANDROID_BACKSPACE: "Backspace", - }; - - let inputs = $state([0]); - let pins: { [key: number]: string } = $state({}); - - interface IInputPinProps extends HTMLAttributes { - pin: string; - variant?: "lg" | "sm"; - size?: number; - focusOnMount?: boolean | undefined; - inFocus?: boolean | undefined; - isError?: boolean; - } - - let { - pin = $bindable(""), - variant = "lg", - size = 4, - focusOnMount = true, - inFocus = false, - isError = $bindable(false), - ...restProps - }: IInputPinProps = $props(); - - onMount(async () => { - inputs = createArray(size); - pins = await createValueSlot(inputs); - pin = calcPin(pins); - if (!focusOnMount) return; - document.getElementById("pin0")?.focus(); - }); - - $effect(() => { - pin = calcPin(pins); - }); - - const calcPin = (pins: { [key: number]: string }) => { - return Object.values(pins).join("") || ""; - }; - - const isKeyDelete = (key: string) => { - return ( - key === KEYBOARD.BACKSPACE || - key === KEYBOARD.DELETE || - key === KEYBOARD.ANDROID_BACKSPACE - ); - }; - - const changeHandler = (e: KeyboardEvent, i: number) => { - const current = - document.activeElement ?? document.getElementById("pin0"); - const items = Array.from(document.getElementsByClassName("pin-item")); - const currentIndex = items.indexOf(current as HTMLElement); - let newIndex: number; - - const regx = /^\d+$/; - - if (isKeyDelete(e.key)) { - e.preventDefault(); - if (pins[i] !== "") { - pins[i] = ""; - return; - } - if (currentIndex > 0) { - newIndex = currentIndex - 1; - (items[newIndex] as HTMLInputElement)?.focus(); - } - } - - if (regx.test(e.key)) { - e.preventDefault(); - pins[i] = e.key; - if (currentIndex < items.length - 1) { - newIndex = currentIndex + 1; - (items[newIndex] as HTMLInputElement)?.focus(); - } +import { cn } from "$lib/utils"; +import { onMount } from "svelte"; +import type { HTMLAttributes } from "svelte/elements"; + +const KEYBOARD = { + BACKSPACE: "Backspace", + DELETE: "Delete", + ANDROID_BACKSPACE: "Backspace", +}; + +let inputs = $state([0]); +let pins: { [key: number]: string } = $state({}); + +interface IInputPinProps extends HTMLAttributes { + pin: string; + variant?: "lg" | "sm"; + size?: number; + focusOnMount?: boolean | undefined; + inFocus?: boolean | undefined; + isError?: boolean; +} + +let { + pin = $bindable(""), + variant = "lg", + size = 4, + focusOnMount = true, + inFocus = false, + isError = $bindable(false), + ...restProps +}: IInputPinProps = $props(); + +onMount(async () => { + inputs = createArray(size); + pins = await createValueSlot(inputs); + pin = calcPin(pins); + if (!focusOnMount) return; + document.getElementById("pin0")?.focus(); +}); + +$effect(() => { + pin = calcPin(pins); +}); + +const calcPin = (pins: { [key: number]: string }) => { + return Object.values(pins).join("") || ""; +}; + +const isKeyDelete = (key: string) => { + return ( + key === KEYBOARD.BACKSPACE || + key === KEYBOARD.DELETE || + key === KEYBOARD.ANDROID_BACKSPACE + ); +}; + +const changeHandler = (e: KeyboardEvent, i: number) => { + const current = document.activeElement ?? document.getElementById("pin0"); + const items = Array.from(document.getElementsByClassName("pin-item")); + const currentIndex = items.indexOf(current as HTMLElement); + let newIndex: number; + + const regx = /^\d+$/; + + if (isKeyDelete(e.key)) { + e.preventDefault(); + if (pins[i] !== "") { + pins[i] = ""; + return; } - - // Allow arrow keys for navigation - if (e.key === "ArrowLeft" && currentIndex > 0) { + if (currentIndex > 0) { newIndex = currentIndex - 1; (items[newIndex] as HTMLInputElement)?.focus(); } + } - if (e.key === "ArrowRight" && currentIndex < items.length - 1) { + if (regx.test(e.key)) { + e.preventDefault(); + pins[i] = e.key; + if (currentIndex < items.length - 1) { newIndex = currentIndex + 1; (items[newIndex] as HTMLInputElement)?.focus(); } - }; - - const createArray = (size: number) => { - return new Array(size); - }; - - const createValueSlot = (arr: number[]) => { - return arr.reduce( - (obj, item) => { - obj[item] = ""; - return obj; - }, - {} as Record, - ); - }; - - const uniqueId = `input${Math.random().toString().split(".")[1]}`; - const cBase = - "relative w-full margin-x-[auto] flex justify-between items-center gap-[10px] flex-row flex-nowrap select-none"; + } + + // Allow arrow keys for navigation + if (e.key === "ArrowLeft" && currentIndex > 0) { + newIndex = currentIndex - 1; + (items[newIndex] as HTMLInputElement)?.focus(); + } + + if (e.key === "ArrowRight" && currentIndex < items.length - 1) { + newIndex = currentIndex + 1; + (items[newIndex] as HTMLInputElement)?.focus(); + } +}; + +const createArray = (size: number) => { + return new Array(size); +}; + +const createValueSlot = (arr: number[]) => { + return arr.reduce( + (obj, item) => { + obj[item] = ""; + return obj; + }, + {} as Record, + ); +}; + +const uniqueId = `input${Math.random().toString().split(".")[1]}`; +const cBase = + "relative w-full margin-x-[auto] flex justify-between items-center gap-[10px] flex-row flex-nowrap select-none";
diff --git a/infrastructure/eid-wallet/src/lib/ui/Selector/Selector.svelte b/infrastructure/eid-wallet/src/lib/ui/Selector/Selector.svelte index 88ca2189..cd2020fc 100644 --- a/infrastructure/eid-wallet/src/lib/ui/Selector/Selector.svelte +++ b/infrastructure/eid-wallet/src/lib/ui/Selector/Selector.svelte @@ -1,31 +1,31 @@
+ ); } diff --git a/platforms/blabsy/src/components/input/image-preview.tsx b/platforms/blabsy/src/components/input/image-preview.tsx index 3d204e9b..a6f55fe3 100644 --- a/platforms/blabsy/src/components/input/image-preview.tsx +++ b/platforms/blabsy/src/components/input/image-preview.tsx @@ -13,186 +13,200 @@ import type { MotionProps } from 'framer-motion'; import type { ImagesPreview, ImageData } from '@lib/types/file'; type ImagePreviewProps = { - tweet?: boolean; - viewTweet?: boolean; - previewCount: number; - imagesPreview: ImagesPreview; - removeImage?: (targetId: string) => () => void; + tweet?: boolean; + viewTweet?: boolean; + previewCount: number; + imagesPreview: ImagesPreview; + removeImage?: (targetId: string) => () => void; }; const variants: MotionProps = { - initial: { opacity: 0, scale: 0.5 }, - animate: { - opacity: 1, - scale: 1, - transition: { duration: 0.3 } - }, - exit: { opacity: 0, scale: 0.5 }, - transition: { type: 'spring', duration: 0.5 } + initial: { opacity: 0, scale: 0.5 }, + animate: { + opacity: 1, + scale: 1, + transition: { duration: 0.3 } + }, + exit: { opacity: 0, scale: 0.5 }, + transition: { type: 'spring', duration: 0.5 } }; type PostImageBorderRadius = Record; const postImageBorderRadius: Readonly = { - 1: ['rounded-2xl'], - 2: ['rounded-tl-2xl rounded-bl-2xl', 'rounded-tr-2xl rounded-br-2xl'], - 3: ['rounded-tl-2xl rounded-bl-2xl', 'rounded-tr-2xl', 'rounded-br-2xl'], - 4: ['rounded-tl-2xl', 'rounded-tr-2xl', 'rounded-bl-2xl', 'rounded-br-2xl'] + 1: ['rounded-2xl'], + 2: ['rounded-tl-2xl rounded-bl-2xl', 'rounded-tr-2xl rounded-br-2xl'], + 3: ['rounded-tl-2xl rounded-bl-2xl', 'rounded-tr-2xl', 'rounded-br-2xl'], + 4: ['rounded-tl-2xl', 'rounded-tr-2xl', 'rounded-bl-2xl', 'rounded-br-2xl'] }; export function ImagePreview({ - tweet, - viewTweet, - previewCount, - imagesPreview, - removeImage + tweet, + viewTweet, + previewCount, + imagesPreview, + removeImage }: ImagePreviewProps): JSX.Element { - const [selectedIndex, setSelectedIndex] = useState(0); - const [selectedImage, setSelectedImage] = useState(null); - - const videoRef = useRef(null); - - const { open, openModal, closeModal } = useModal(); - - useEffect(() => { - const imageData = imagesPreview[selectedIndex]; - setSelectedImage(imageData); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedIndex]); - - const handleVideoStop = (): void => { - if (videoRef.current) videoRef.current.pause(); - }; - - const handleSelectedImage = (index: number, isVideo?: boolean) => () => { - if (isVideo) handleVideoStop(); - - setSelectedIndex(index); - openModal(); - }; - - const handleNextIndex = (type: 'prev' | 'next') => () => { - const nextIndex = - type === 'prev' - ? selectedIndex === 0 - ? previewCount - 1 - : selectedIndex - 1 - : selectedIndex === previewCount - 1 - ? 0 - : selectedIndex + 1; - - setSelectedIndex(nextIndex); - }; - - const isTweet = tweet ?? viewTweet; - - return ( -
- - - - - {imagesPreview.map(({ id, src, alt }, index) => { - const isVideo = imagesPreview[index].type?.includes('video'); - - return ( - (null); + + const videoRef = useRef(null); + + const { open, openModal, closeModal } = useModal(); + + useEffect(() => { + const imageData = imagesPreview[selectedIndex]; + setSelectedImage(imageData); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedIndex]); + + const handleVideoStop = (): void => { + if (videoRef.current) videoRef.current.pause(); + }; + + const handleSelectedImage = (index: number, isVideo?: boolean) => () => { + if (isVideo) handleVideoStop(); + + setSelectedIndex(index); + openModal(); + }; + + const handleNextIndex = (type: 'prev' | 'next') => () => { + const nextIndex = + type === 'prev' + ? selectedIndex === 0 + ? previewCount - 1 + : selectedIndex - 1 + : selectedIndex === previewCount - 1 + ? 0 + : selectedIndex + 1; + + setSelectedIndex(nextIndex); + }; + + const isTweet = tweet ?? viewTweet; + + return ( +
+ - {isVideo ? ( - <> - -
- ); + onClick={preventBubbling(removeImage(id))} + > + + + + )} +
+ ); + })} +
+
+ ); } diff --git a/platforms/blabsy/src/components/input/input-accent-radio.tsx b/platforms/blabsy/src/components/input/input-accent-radio.tsx index 1239c0fc..17c3f4c4 100644 --- a/platforms/blabsy/src/components/input/input-accent-radio.tsx +++ b/platforms/blabsy/src/components/input/input-accent-radio.tsx @@ -4,54 +4,50 @@ import { HeroIcon } from '@components/ui/hero-icon'; import type { Accent } from '@lib/types/theme'; type InputAccentRadioProps = { - type: Accent; + type: Accent; }; type InputAccentData = Record; const InputColors: Readonly = { - yellow: - 'bg-accent-yellow hover:ring-accent-yellow/10 active:ring-accent-yellow/20', - blue: 'bg-accent-blue hover:ring-accent-blue/10 active:ring-accent-blue/20', - pink: 'bg-accent-pink hover:ring-accent-pink/10 active:ring-accent-pink/20', - purple: - 'bg-accent-purple hover:ring-accent-purple/10 active:ring-accent-purple/20', - orange: - 'bg-accent-orange hover:ring-accent-orange/10 active:ring-accent-orange/20', - green: - 'bg-accent-green hover:ring-accent-green/10 active:ring-accent-green/20' + yellow: 'bg-accent-yellow hover:ring-accent-yellow/10 active:ring-accent-yellow/20', + blue: 'bg-accent-blue hover:ring-accent-blue/10 active:ring-accent-blue/20', + pink: 'bg-accent-pink hover:ring-accent-pink/10 active:ring-accent-pink/20', + purple: 'bg-accent-purple hover:ring-accent-purple/10 active:ring-accent-purple/20', + orange: 'bg-accent-orange hover:ring-accent-orange/10 active:ring-accent-orange/20', + green: 'bg-accent-green hover:ring-accent-green/10 active:ring-accent-green/20' }; export function InputAccentRadio({ type }: InputAccentRadioProps): JSX.Element { - const { accent, changeAccent } = useTheme(); + const { accent, changeAccent } = useTheme(); - const bgColor = InputColors[type]; - const isChecked = type === accent; + const bgColor = InputColors[type]; + const isChecked = type === accent; - return ( - - ); + bgColor + )} + htmlFor={type} + > + + + + + + ); } diff --git a/platforms/blabsy/src/components/input/input-field.tsx b/platforms/blabsy/src/components/input/input-field.tsx index 4627fe32..d218b8f4 100644 --- a/platforms/blabsy/src/components/input/input-field.tsx +++ b/platforms/blabsy/src/components/input/input-field.tsx @@ -3,99 +3,105 @@ import type { User, EditableData } from '@lib/types/user'; import type { KeyboardEvent, ChangeEvent } from 'react'; export type InputFieldProps = { - label: string; - inputId: EditableData | Extract; - inputValue: string | null; - inputLimit?: number; - useTextArea?: boolean; - errorMessage?: string; - handleChange: ( - e: ChangeEvent - ) => void; - handleKeyboardShortcut?: ({ - key, - ctrlKey - }: KeyboardEvent) => void; + label: string; + inputId: EditableData | Extract; + inputValue: string | null; + inputLimit?: number; + useTextArea?: boolean; + errorMessage?: string; + handleChange: ( + e: ChangeEvent + ) => void; + handleKeyboardShortcut?: ({ + key, + ctrlKey + }: KeyboardEvent) => void; }; export function InputField({ - label, - inputId, - inputValue, - inputLimit, - useTextArea, - errorMessage, - handleChange, - handleKeyboardShortcut + label, + inputId, + inputValue, + inputLimit, + useTextArea, + errorMessage, + handleChange, + handleKeyboardShortcut }: InputFieldProps): JSX.Element { - const slicedInputValue = inputValue?.slice(0, inputLimit) ?? ''; + const slicedInputValue = inputValue?.slice(0, inputLimit) ?? ''; - const inputLength = slicedInputValue.length; - const isHittingInputLimit = inputLimit && inputLength > inputLimit; + const inputLength = slicedInputValue.length; + const isHittingInputLimit = inputLimit && inputLength > inputLimit; - return ( -
-
+
- {useTextArea ? ( - diff --git a/platforms/pictique/src/lib/ui/index.ts b/platforms/pictique/src/lib/ui/index.ts index 52f1d457..948eec5e 100644 --- a/platforms/pictique/src/lib/ui/index.ts +++ b/platforms/pictique/src/lib/ui/index.ts @@ -6,3 +6,6 @@ export { default as Label } from './Label/Label.svelte'; export { default as Toggle } from './Toggle/Toggle.svelte'; export { default as Helper } from './Helper/Helper.svelte'; export { default as Qr } from './Qr/Qr.svelte'; +export { default as InputRadio } from './InputRadio/InputRadio.svelte'; +export { default as Textarea } from './Textarea/Textarea.svelte'; +export { default as Modal } from './Modal/Modal.svelte'; diff --git a/platforms/pictique/src/lib/utils/axios.ts b/platforms/pictique/src/lib/utils/axios.ts index 275f9977..0c3d7e7c 100644 --- a/platforms/pictique/src/lib/utils/axios.ts +++ b/platforms/pictique/src/lib/utils/axios.ts @@ -1,46 +1,46 @@ -import axios, { type AxiosInstance } from 'axios'; import { PUBLIC_PICTIQUE_BASE_URL } from '$env/static/public'; +import axios, { type AxiosInstance } from 'axios'; const TOKEN_KEY = 'pictique_auth_token'; -let headers: Record = { - 'Content-Type': 'application/json' +const headers: Record = { + 'Content-Type': 'application/json' }; if (getAuthToken()) { - headers.authorization = `Bearer ${getAuthToken()}`; + headers.authorization = `Bearer ${getAuthToken()}`; } // Create axios instance with base configuration export const apiClient: AxiosInstance = axios.create({ - baseURL: PUBLIC_PICTIQUE_BASE_URL, - headers + baseURL: PUBLIC_PICTIQUE_BASE_URL, + headers }); // Utility function to store auth token export const setAuthToken = (token: string): void => { - localStorage.setItem(TOKEN_KEY, token); - window.location.href = '/home'; + localStorage.setItem(TOKEN_KEY, token); + window.location.href = '/home'; }; export function getAuthToken() { - return localStorage.getItem(TOKEN_KEY); + return localStorage.getItem(TOKEN_KEY); } // Utility function to remove auth token export const removeAuthToken = (): void => { - localStorage.removeItem(TOKEN_KEY); + localStorage.removeItem(TOKEN_KEY); }; // Utility function to store auth id export const setAuthId = (id: string): void => { - localStorage.setItem('ownerId', id); + localStorage.setItem('ownerId', id); }; export const getAuthId = () => { - return localStorage.getItem('ownerId'); + return localStorage.getItem('ownerId'); }; // Utility function to remove auth token export const removeAuthId = (): void => { - localStorage.removeItem('ownerId'); + localStorage.removeItem('ownerId'); }; diff --git a/platforms/pictique/src/lib/utils/memoryHelper.ts b/platforms/pictique/src/lib/utils/memoryHelper.ts index c9cff633..01ac2d4e 100644 --- a/platforms/pictique/src/lib/utils/memoryHelper.ts +++ b/platforms/pictique/src/lib/utils/memoryHelper.ts @@ -1,5 +1,9 @@ import type { Image } from '$lib/types'; export const revokeImageUrls = (imageArray: Image[]) => { - imageArray?.forEach((img) => URL.revokeObjectURL(img.url)); + if (imageArray) { + for (const img of imageArray) { + URL.revokeObjectURL(img.url); + } + } }; diff --git a/platforms/pictique/src/routes/(auth)/auth/+page.svelte b/platforms/pictique/src/routes/(auth)/auth/+page.svelte index 092a077e..5459c2b8 100644 --- a/platforms/pictique/src/routes/(auth)/auth/+page.svelte +++ b/platforms/pictique/src/routes/(auth)/auth/+page.svelte @@ -1,9 +1,9 @@
@@ -61,7 +62,7 @@
No users found
{:else if searchValue}
    - {#each $searchResults as user} + {#each $searchResults as user (user.id)}
  • + import { goto } from '$app/navigation'; import { Drawer, Post } from '$lib/fragments'; - import { onMount } from 'svelte'; - import type { CupertinoPane } from 'cupertino-pane'; import { Comment, MessageInput } from '$lib/fragments'; - import type { CommentType, userProfile } from '$lib/types'; - import { ownerId, showComments } from '$lib/store/store.svelte'; - import { posts, isLoading, error, fetchFeed, toggleLike } from '$lib/stores/posts'; + import { showComments } from '$lib/store/store.svelte'; import { activePostId } from '$lib/stores/comments'; + import { error, fetchFeed, isLoading, posts, toggleLike } from '$lib/stores/posts'; + import type { CommentType, userProfile } from '$lib/types'; import { apiClient, getAuthToken } from '$lib/utils'; - import { goto } from '$app/navigation'; import type { AxiosError } from 'axios'; + import type { CupertinoPane } from 'cupertino-pane'; + import { onMount } from 'svelte'; let listElement: HTMLElement; let drawer: CupertinoPane | undefined = $state(); @@ -44,9 +44,8 @@ if (c.commentId === activeReplyToId) { c.replies.push(newComment); return true; - } else if (c.replies.length) { - if (addReplyToComment(c.replies)) return true; } + if (c.replies.length && addReplyToComment(c.replies)) return true; } return false; }; @@ -65,14 +64,13 @@ goto('/auth'); return; } - const response = await apiClient.get(`/api/users`).catch((e: AxiosError) => { + const response = await apiClient.get('/api/users').catch((e: AxiosError) => { if (e.response?.status === 401) { goto('/auth'); } }); if (!response) return; profile = response.data; - console.log(profile); } catch (err) { console.log(err instanceof Error ? err.message : 'Failed to load profile'); } @@ -87,6 +85,8 @@ fetchFeed(); fetchProfile(); }); + + $inspect($posts);
    @@ -96,14 +96,14 @@ {:else if $error}
  • {$error}
  • {:else} - {#each $posts.posts as post (post.id)} + {#each $posts as post (post.id)}
  • p.id === profile?.id)} + isLiked={post.likedBy.find((p) => p.id === profile?.id) !== undefined} text={post.text} time={new Date(post.createdAt).toLocaleDateString()} count={{ likes: post.likedBy.length, comments: post.comments.length }} @@ -137,7 +137,7 @@

      {_comments.length} Comments

      - {#each _comments as comment} + {#each _comments as comment (comment.commentId)}
    • import { goto } from '$app/navigation'; import { Message } from '$lib/fragments'; - import { onMount } from 'svelte'; + import type { Chats, MessageItem, userProfile } from '$lib/types'; import { apiClient } from '$lib/utils/axios'; + import { onMount } from 'svelte'; import { heading } from '../../store'; - let messages = $state([]); + let messages = $state([]); onMount(async () => { - const { data } = await apiClient.get('/api/chats'); - const { data: userData } = await apiClient.get('/api/users'); + const { data } = await apiClient.get<{ chats: Chats[] }>('/api/chats'); + const { data: userData } = await apiClient.get('/api/users'); + console.log(data); + console.log(userData); messages = data.chats.map((c) => { const members = c.participants.filter((u) => u.id !== userData.id); const memberNames = members.map((m) => m.name ?? m.handle ?? m.ename); @@ -27,7 +30,7 @@
      {#if messages && messages.length > 0} - {#each messages as message} + {#each messages as message (message.id)} - import { MessageInput, ChatMessage } from '$lib/fragments'; - import { PUBLIC_PICTIQUE_BASE_URL } from '$env/static/public'; import { page } from '$app/state'; - import { onMount } from 'svelte'; - import { getAuthToken, apiClient } from '$lib/utils/axios'; + import { PUBLIC_PICTIQUE_BASE_URL } from '$env/static/public'; + import { ChatMessage, MessageInput } from '$lib/fragments'; + import { apiClient, getAuthToken } from '$lib/utils/axios'; import moment from 'moment'; + import { onMount } from 'svelte'; const id = page.params.id; let userId = $state(); - let messages: any[] = $state([]); + let messages: Record[] = $state([]); let messageValue = $state(''); let messagesContainer: HTMLDivElement; @@ -33,11 +33,11 @@ ).toString(); const eventSource = new EventSource(sseUrl); - eventSource.onopen = function (e) { + eventSource.onopen = () => { console.log('Successfully connected.'); }; - eventSource.onmessage = function (e) { + eventSource.onmessage = (e) => { const data = JSON.parse(e.data); console.log('messages', data); addMessages(data); @@ -55,13 +55,16 @@ function addMessages(arr: Record[]) { console.log(arr); - const newMessages = arr.map((m) => ({ - id: m.id, - isOwn: m.sender.id !== userId, - userImgSrc: m.sender.avatarUrl, - time: moment(m.createdAt).fromNow(), - message: m.text - })); + const newMessages = arr.map((m) => { + const sender = m.sender as Record; + return { + id: m.id, + isOwn: sender.id !== userId, + userImgSrc: sender.avatarUrl, + time: moment(m.createdAt as string).fromNow(), + message: m.text + }; + }); apiClient.post(`/api/chats/${id}/messages/read`); messages = messages.concat(newMessages); @@ -78,10 +81,10 @@
      {#each messages as msg (msg.id)} {/each}
      diff --git a/platforms/pictique/src/routes/(protected)/post/+page.svelte b/platforms/pictique/src/routes/(protected)/post/+page.svelte index e69de29b..009d9665 100644 --- a/platforms/pictique/src/routes/(protected)/post/+page.svelte +++ b/platforms/pictique/src/routes/(protected)/post/+page.svelte @@ -0,0 +1,58 @@ + + +
      + { + if (uploadedImages.value) + uploadedImages.value = uploadedImages.value.filter((img, index) => { + revokeImageUrls([img]); + return index !== i; + }); + }} + /> + +