Skip to content

Commit 496b739

Browse files
authored
Chore/eid wallet UI fixes (#917)
* chore: address small UI things eid wallet * feat: eid-wallet support thing * chore: format * fix: scan thing * chore: address code-rabbit suggestions
1 parent 867f67b commit 496b739

8 files changed

Lines changed: 97 additions & 11 deletions

File tree

infrastructure/control-panel/src/routes/api/references/+server.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ export const GET: RequestHandler = async () => {
1010
const timeout = setTimeout(() => controller.abort(), 5000);
1111
const response = await fetch(`${baseUrl}/api/references/all`, {
1212
headers: env.VISUALIZER_API_KEY ? { 'x-visualizer-key': env.VISUALIZER_API_KEY } : {},
13-
signal: controller.signal,
13+
signal: controller.signal
1414
});
1515
clearTimeout(timeout);
16-
1716

1817
if (!response.ok) {
1918
console.error('eReputation API error:', response.status, response.statusText);
@@ -24,6 +23,9 @@ export const GET: RequestHandler = async () => {
2423
return json(data);
2524
} catch (error) {
2625
console.error('Error fetching references from eReputation:', error);
27-
return json({ error: 'Failed to connect to eReputation API', references: [] }, { status: 500 });
26+
return json(
27+
{ error: 'Failed to connect to eReputation API', references: [] },
28+
{ status: 500 }
29+
);
2830
}
2931
};

infrastructure/control-panel/src/routes/visualizer/+page.server.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ export interface ReferenceEdge {
1717
createdAt: string;
1818
}
1919

20-
2120
export const load: PageServerLoad = async ({ fetch }) => {
2221
try {
2322
const response = await fetch('/api/references');

infrastructure/eid-wallet/src/lib/services/NotificationService.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { PUBLIC_PROVISIONER_URL } from "$env/static/public";
2-
import { addNotification } from "$lib/stores/notifications";
2+
import { addNotification, hasNotification } from "$lib/stores/notifications";
33
import {
44
isPermissionGranted,
5+
onNotificationReceived,
56
registerForPushNotifications,
67
requestPermission,
78
sendNotification,
89
} from "@choochmeque/tauri-plugin-notifications-api";
10+
import type { PluginListener } from "@tauri-apps/api/core";
911
import { invoke } from "@tauri-apps/api/core";
1012

1113
export interface DeviceRegistration {
@@ -401,6 +403,33 @@ class NotificationService {
401403
if (platform !== "android" && platform !== "ios") return undefined;
402404
return this.getPushNotificationToken();
403405
}
406+
/**
407+
* Listen for push notifications arriving while the app is in the foreground.
408+
* Stores them in the notification panel so they appear in the notifications tab.
409+
* Returns a cleanup function to remove the listener.
410+
*/
411+
async listenForForegroundNotifications(): Promise<PluginListener> {
412+
return onNotificationReceived((notification) => {
413+
if (notification.source !== "push") return;
414+
const title = notification.title ?? "";
415+
if (!title) return;
416+
417+
const raw = (notification.extra ?? {}) as Record<string, unknown>;
418+
const data = Object.fromEntries(
419+
Object.entries(raw).filter(
420+
(entry): entry is [string, string] =>
421+
typeof entry[1] === "string",
422+
),
423+
);
424+
425+
const body = notification.body ?? "";
426+
const payload = Object.keys(data).length > 0 ? data : undefined;
427+
if (!hasNotification(title, body, payload)) {
428+
addNotification({ title, body, data: payload });
429+
}
430+
});
431+
}
432+
404433
/**
405434
* Get eName from vault (helper method)
406435
*/

infrastructure/eid-wallet/src/lib/stores/notifications.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ export function getUnreadCount(): number {
5050
return loadNotifications().length;
5151
}
5252

53+
export function hasNotification(
54+
title: string,
55+
body: string,
56+
data?: Record<string, string>,
57+
): boolean {
58+
const notifications = loadNotifications();
59+
const key = `${title}\0${body}\0${JSON.stringify(data ?? {})}`;
60+
return notifications.some(
61+
(n) => `${n.title}\0${n.body}\0${JSON.stringify(n.data ?? {})}` === key,
62+
);
63+
}
64+
5365
export function addNotification(
5466
notification: Omit<StoredNotification, "id" | "createdAt">,
5567
): void {

infrastructure/eid-wallet/src/routes/(app)/+layout.svelte

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
import { goto } from "$app/navigation";
33
import { page } from "$app/state";
44
import type { GlobalState } from "$lib/global";
5+
import type { PluginListener } from "@tauri-apps/api/core";
56
import type { Snippet } from "svelte";
6-
import { getContext, onMount } from "svelte";
7+
import { getContext, onDestroy, onMount } from "svelte";
78
import type { LayoutData } from "./$types";
89
910
let { data, children }: { data: LayoutData; children: Snippet } = $props();
1011
1112
let currentRoute = $derived(page.url.pathname.split("/").pop() || "home");
1213
let globalState: GlobalState | undefined = $state(undefined);
14+
let notificationListener: PluginListener | undefined;
1315
1416
onMount(async () => {
1517
// Get global state
@@ -47,6 +49,14 @@ onMount(async () => {
4749
);
4850
}
4951
52+
// Listen for push notifications while app is in the foreground
53+
try {
54+
notificationListener =
55+
await globalState.notificationService.listenForForegroundNotifications();
56+
} catch (error) {
57+
console.error("Failed to set up notification listener:", error);
58+
}
59+
5060
// Check for pending notifications and navigate to the message's open page
5161
try {
5262
const notificationService = globalState.notificationService;
@@ -72,6 +82,10 @@ onMount(async () => {
7282
}
7383
});
7484
85+
onDestroy(() => {
86+
notificationListener?.unregister();
87+
});
88+
7589
$effect(() => {
7690
const isScanPage = currentRoute === "scan-qr";
7791
if (isScanPage) return document.body.classList.add("custom-global-style");

infrastructure/eid-wallet/src/routes/(app)/notifications/+page.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { onDestroy, onMount } from "svelte";
1212
1313
let notifications: StoredNotification[] = $state([]);
14+
let loaded = $state(false);
1415
let unsubscribe: (() => void) | undefined;
1516
1617
function refresh() {
@@ -19,6 +20,7 @@ function refresh() {
1920
2021
onMount(() => {
2122
refresh();
23+
loaded = true;
2224
unsubscribe = subscribe(refresh);
2325
});
2426
@@ -80,7 +82,11 @@ function handleClearAll() {
8082
</div>
8183
{/if}
8284

83-
{#if notifications.length === 0}
85+
{#if !loaded}
86+
<div class="flex flex-col items-center justify-center mt-20">
87+
<p class="text-sm text-black-500">Loading...</p>
88+
</div>
89+
{:else if notifications.length === 0}
8490
<div class="flex flex-col items-center justify-center mt-20">
8591
<p class="text-lg text-black-700">No notifications</p>
8692
<p class="text-sm text-black-500 mt-1">You're all caught up</p>

infrastructure/eid-wallet/src/routes/(app)/scan-qr/scanLogic.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,15 @@ export function createScanLogic({
205205
const formats = [Format.QRCode];
206206
const windowed = true;
207207
if (get(scanning)) return;
208+
// Don't start scanning if any drawer is already open
209+
if (
210+
get(codeScannedDrawerOpen) ||
211+
get(loggedInDrawerOpen) ||
212+
get(signingDrawerOpen) ||
213+
get(isRevealRequest) ||
214+
get(socialBindingDrawerOpen)
215+
)
216+
return;
208217
scanning.set(true);
209218

210219
scan({ formats, windowed })
@@ -414,7 +423,6 @@ export function createScanLogic({
414423
// Ensure auth drawer is closed before opening logged in drawer
415424
codeScannedDrawerOpen.set(false);
416425
loggedInDrawerOpen.set(true);
417-
startScan();
418426
return;
419427
}
420428

@@ -425,7 +433,6 @@ export function createScanLogic({
425433
// Ensure auth drawer is closed before opening logged in drawer
426434
codeScannedDrawerOpen.set(false);
427435
loggedInDrawerOpen.set(true);
428-
startScan();
429436
return;
430437
}
431438

@@ -471,7 +478,6 @@ export function createScanLogic({
471478
// Ensure auth drawer is closed before opening logged in drawer
472479
codeScannedDrawerOpen.set(false);
473480
loggedInDrawerOpen.set(true);
474-
startScan();
475481
} catch (error) {
476482
console.error("Error completing authentication:", error);
477483

@@ -1317,7 +1323,10 @@ export function createScanLogic({
13171323
return;
13181324
}
13191325

1320-
if (data.type === "auth" && get(codeScannedDrawerOpen)) {
1326+
if (
1327+
data.type === "auth" &&
1328+
(get(codeScannedDrawerOpen) || get(loggedInDrawerOpen))
1329+
) {
13211330
console.log(
13221331
"Auth request already in progress, ignoring duplicate",
13231332
);

infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1438,10 +1438,25 @@ onMount(async () => {
14381438
{diditRejectionReason ??
14391439
"Your verification could not be completed."}
14401440
</p>
1441+
<p class="text-black-500 text-xs">
1442+
If you believe this was a mistake, please contact us at
1443+
<a href="mailto:info@metastate.foundation" class="text-primary underline">info@metastate.foundation</a>.
1444+
</p>
14411445
<div class="flex flex-col gap-3 pt-2">
14421446
<ButtonAction class="w-full" callback={handleKycNext}>
14431447
Try Again
14441448
</ButtonAction>
1449+
{#if !upgradeMode}
1450+
<ButtonAction
1451+
variant="soft"
1452+
class="w-full"
1453+
callback={() => {
1454+
step = "anonymous-form";
1455+
}}
1456+
>
1457+
Self-declare instead
1458+
</ButtonAction>
1459+
{/if}
14451460
<ButtonAction
14461461
variant="soft"
14471462
class="w-full"

0 commit comments

Comments
 (0)