Skip to content

Commit 69517e9

Browse files
committed
feat: pictique header & eid privacy thing
1 parent 837bb90 commit 69517e9

8 files changed

Lines changed: 172 additions & 81 deletions

File tree

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

Lines changed: 19 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ class NotificationService {
2626
private static instance: NotificationService;
2727
private deviceRegistration: DeviceRegistration | null = null;
2828
private provisionerEndpoint: string;
29-
private pendingMessageNavigation: {
30-
globalMessageId: string;
31-
globalChatId: string;
32-
} | null = null;
33-
private onNavigate: ((path: string) => void) | null = null;
3429

3530
constructor() {
3631
// Get the provisioner endpoint from environment or use default
@@ -119,26 +114,11 @@ class NotificationService {
119114
}
120115
}
121116

122-
/**
123-
* Set a navigation callback for handling notification taps
124-
*/
125-
setNavigationHandler(handler: (path: string) => void): void {
126-
this.onNavigate = handler;
127-
128-
// If there's a pending navigation from a tapped notification, go to notifications page
129-
if (this.pendingMessageNavigation) {
130-
this.pendingMessageNavigation = null;
131-
handler("/notifications");
132-
}
133-
}
134-
135117
/**
136118
* Send a local notification
137119
*/
138120
async sendLocalNotification(payload: NotificationPayload): Promise<void> {
139121
try {
140-
console.log("Attempting to send local notification:", payload);
141-
142122
// Store notification for the notification panel
143123
const data = payload.data as Record<string, string> | undefined;
144124
addNotification({
@@ -147,12 +127,6 @@ class NotificationService {
147127
data,
148128
});
149129

150-
// Navigate to notifications page when notification is tapped
151-
this.pendingMessageNavigation = {
152-
globalMessageId: "",
153-
globalChatId: "",
154-
};
155-
156130
// Check permissions first
157131
const hasPermission = await isPermissionGranted();
158132
console.log("Has notification permission:", hasPermission);
@@ -225,7 +199,7 @@ class NotificationService {
225199
/**
226200
* Check for notifications from provisioner and show them locally
227201
*/
228-
async checkAndShowNotifications(): Promise<void> {
202+
async checkAndShowNotifications(): Promise<{ globalMessageId?: string; globalChatId?: string } | null> {
229203
try {
230204
console.log("🔍 Checking for notifications from provisioner...");
231205

@@ -253,25 +227,25 @@ class NotificationService {
253227
console.log("Device registered successfully");
254228
} else {
255229
console.log("Failed to register device");
256-
return;
230+
return null;
257231
}
258232
} else {
259233
console.log(
260234
"No eName found in vault, skipping notification check",
261235
);
262-
return;
236+
return null;
263237
}
264238
} catch (error) {
265239
console.error("Error getting vault eName:", error);
266-
return;
240+
return null;
267241
}
268242
}
269243

270244
if (!registration) {
271245
console.log(
272246
"Still no device registration, skipping notification check",
273247
);
274-
return;
248+
return null;
275249
}
276250

277251
// Check for notifications from provisioner
@@ -293,24 +267,37 @@ class NotificationService {
293267
const data = await response.json();
294268
if (data.notifications && data.notifications.length > 0) {
295269
console.log(
296-
`📱 Found ${data.notifications.length} notification(s)`,
270+
`Found ${data.notifications.length} notification(s)`,
297271
);
298272

299273
// Show each notification locally
274+
let lastMessageNotif: { globalMessageId?: string; globalChatId?: string; title?: string; body?: string } | null = null;
300275
for (const notification of data.notifications) {
301276
await this.sendLocalNotification(notification);
277+
if (notification.data?.type === "new_message") {
278+
lastMessageNotif = {
279+
globalMessageId: notification.data.globalMessageId,
280+
globalChatId: notification.data.globalChatId,
281+
title: notification.title,
282+
body: notification.body,
283+
};
284+
}
302285
}
286+
return lastMessageNotif;
303287
} else {
304288
console.log("No new notifications");
289+
return null;
305290
}
306291
} else {
307292
console.log(
308293
"No notifications endpoint available or error:",
309294
response.status,
310295
);
296+
return null;
311297
}
312298
} catch (error) {
313299
console.error("Error checking notifications:", error);
300+
return null;
314301
}
315302
}
316303

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,18 @@ onMount(async () => {
4747
);
4848
}
4949
50-
// Set up notification navigation handler and check for notifications
50+
// Check for pending notifications and navigate to the message's open page
5151
try {
5252
const notificationService = globalState.notificationService;
53-
notificationService.setNavigationHandler((path: string) => {
54-
goto(path);
55-
});
56-
await notificationService.checkAndShowNotifications();
53+
const notifData = await notificationService.checkAndShowNotifications();
54+
if (notifData?.globalChatId) {
55+
const params = new URLSearchParams({
56+
chatId: notifData.globalChatId,
57+
...(notifData.title && { title: notifData.title }),
58+
...(notifData.body && { body: notifData.body }),
59+
});
60+
await goto(`/open-message/${encodeURIComponent(notifData.globalMessageId || notifData.globalChatId)}?${params.toString()}`);
61+
}
5762
} catch (error) {
5863
console.error("Failed to check notifications:", error);
5964
}

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

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script lang="ts">
22
import { goto } from "$app/navigation";
3+
import { AppNav } from "$lib/fragments";
34
import {
45
getNotifications,
56
clearNotificationsForChat,
@@ -34,7 +35,7 @@ function handleNotificationClick(notification: StoredNotification) {
3435
}
3536
// Navigate to open-message page
3637
goto(
37-
`/open-message/${encodeURIComponent(data.globalMessageId)}?chatId=${encodeURIComponent(data.globalChatId || data.globalMessageId)}`,
38+
`/open-message/${encodeURIComponent(data.globalMessageId)}?chatId=${encodeURIComponent(data.globalChatId || data.globalMessageId)}&title=${encodeURIComponent(notification.title)}&body=${encodeURIComponent(notification.body)}`,
3839
);
3940
}
4041
}
@@ -56,47 +57,48 @@ function formatTime(dateStr: string): string {
5657
5758
return date.toLocaleDateString();
5859
}
60+
61+
function handleClearAll() {
62+
clearAllNotifications();
63+
refresh();
64+
}
5965
</script>
6066

61-
<div class="flex items-center justify-between mb-6">
62-
<button onclick={() => goto("/main")} class="text-gray-500 text-sm">
63-
&larr; Back
64-
</button>
65-
<h1 class="text-xl font-semibold">Notifications</h1>
66-
{#if notifications.length > 0}
67+
<AppNav title="Notifications" />
68+
69+
{#if notifications.length > 0}
70+
<div class="flex justify-end mb-4">
6771
<button
68-
onclick={clearAllNotifications}
69-
class="text-sm text-blue-500"
72+
onclick={handleClearAll}
73+
class="text-sm text-primary"
7074
>
7175
Clear all
7276
</button>
73-
{:else}
74-
<div class="w-16"></div>
75-
{/if}
76-
</div>
77+
</div>
78+
{/if}
7779

7880
{#if notifications.length === 0}
79-
<div class="flex flex-col items-center justify-center mt-20 text-gray-400">
80-
<p class="text-lg">No notifications</p>
81-
<p class="text-sm mt-1">You're all caught up</p>
81+
<div class="flex flex-col items-center justify-center mt-20">
82+
<p class="text-lg text-black-700">No notifications</p>
83+
<p class="text-sm text-black-500 mt-1">You're all caught up</p>
8284
</div>
8385
{:else}
84-
<div class="flex flex-col gap-2">
86+
<div class="flex flex-col gap-3">
8587
{#each notifications as notification (notification.id)}
8688
<button
8789
onclick={() => handleNotificationClick(notification)}
88-
class="w-full text-left p-4 bg-white rounded-xl border border-gray-200 hover:border-gray-300 transition-all"
90+
class="w-full text-left p-4 bg-gray rounded-2xl active:opacity-80 transition-opacity"
8991
>
9092
<div class="flex justify-between items-start">
91-
<h3 class="font-medium text-gray-800 text-sm">
93+
<p class="font-medium text-sm">
9294
{notification.title}
93-
</h3>
94-
<span class="text-xs text-gray-400 ml-2 shrink-0">
95+
</p>
96+
<span class="text-xs text-black-500 ml-2 shrink-0">
9597
{formatTime(notification.createdAt)}
9698
</span>
9799
</div>
98100
{#if notification.body}
99-
<p class="text-sm text-gray-500 mt-1 line-clamp-2">
101+
<p class="text-sm text-black-700 mt-1 line-clamp-2">
100102
{notification.body}
101103
</p>
102104
{/if}

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ async function handleVersionTap() {
107107
}
108108
}
109109
110+
async function openPrivacy(e: Event) {
111+
e.preventDefault();
112+
try {
113+
const { openUrl } = await import("@tauri-apps/plugin-opener");
114+
await openUrl("https://metastate.foundation/");
115+
} catch {
116+
window.location.href = "https://metastate.foundation/";
117+
}
118+
}
119+
110120
$effect(() => {
111121
runtime.header.title = "Settings";
112122
});
@@ -130,11 +140,14 @@ $effect(() => {
130140
label="Recovery Passphrase"
131141
href="/settings/passphrase"
132142
/>
133-
<SettingsNavigationBtn
134-
icon={Shield01Icon}
135-
label="Privacy"
136-
href="https://metastate.foundation/"
137-
/>
143+
<!-- svelte-ignore a11y_no_static_element_interactions -->
144+
<div onclick={openPrivacy}>
145+
<SettingsNavigationBtn
146+
icon={Shield01Icon}
147+
label="Privacy"
148+
href="https://metastate.foundation/"
149+
/>
150+
</div>
138151
</div>
139152
<div>
140153
<ButtonAction class="mt-5 w-full" callback={showDeleteConfirmation}

infrastructure/eid-wallet/src/routes/(public)/open-message/[globalId]/+page.svelte

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,71 @@ import {
77
88
const globalId = page.params.globalId;
99
const chatId = page.url.searchParams.get("chatId") || globalId;
10+
const title = page.url.searchParams.get("title") || "";
11+
const body = page.url.searchParams.get("body") || "";
1012
11-
function openInPictique() {
12-
window.location.href = `${PUBLIC_PICTIQUE_BASE_URL}/open-message/${encodeURIComponent(chatId)}`;
13+
async function openInApp(baseUrl: string) {
14+
const url = new URL(`/open-message/${encodeURIComponent(chatId)}`, baseUrl);
15+
try {
16+
const { openUrl } = await import("@tauri-apps/plugin-opener");
17+
await openUrl(url.toString());
18+
} catch {
19+
window.location.href = url.toString();
20+
}
1321
}
1422
15-
function openInBlabsy() {
16-
window.location.href = `${PUBLIC_BLABSY_BASE_URL}/open-message/${encodeURIComponent(chatId)}`;
23+
function goBack() {
24+
if (window.history.length > 1) {
25+
window.history.back();
26+
} else {
27+
window.location.href = "/main";
28+
}
1729
}
1830
</script>
1931

20-
<div class="flex flex-col items-center justify-center min-h-screen bg-gray-50 p-6">
21-
<h1 class="text-xl font-semibold text-gray-800 mb-2">New Message</h1>
22-
<p class="text-gray-500 mb-8">Choose where to open this message</p>
32+
<div class="flex flex-col items-center justify-center min-h-screen p-6">
33+
{#if title}
34+
<p class="font-medium text-lg mb-1">{title}</p>
35+
{/if}
36+
{#if body}
37+
<p class="text-black-700 text-sm text-center max-w-sm mb-6">{body}</p>
38+
{/if}
39+
{#if !title && !body}
40+
<p class="font-medium text-lg mb-6">New Message</p>
41+
{/if}
2342

24-
<div class="flex flex-col gap-4 w-full max-w-xs">
43+
<p class="text-black-500 text-sm mb-6">Open this conversation in</p>
44+
45+
<div class="flex flex-col gap-3 w-full max-w-xs">
2546
<button
26-
onclick={openInPictique}
27-
class="flex items-center gap-4 w-full px-6 py-4 bg-white rounded-xl shadow-sm border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all"
47+
onclick={() => openInApp(PUBLIC_PICTIQUE_BASE_URL)}
48+
class="flex items-center gap-4 w-full px-5 py-4 bg-gray rounded-2xl active:opacity-80 transition-opacity"
2849
>
2950
<img
30-
src="/images/pictique-logo.png"
51+
src="/images/pictique-logo.svg"
3152
alt="Pictique"
32-
class="w-10 h-10 rounded-lg"
53+
class="w-10 h-10 rounded-xl"
3354
/>
34-
<span class="text-lg font-medium text-gray-800">Open in Pictique</span>
55+
<span class="font-medium">Pictique</span>
3556
</button>
3657

3758
<button
38-
onclick={openInBlabsy}
39-
class="flex items-center gap-4 w-full px-6 py-4 bg-white rounded-xl shadow-sm border border-gray-200 hover:border-gray-300 hover:shadow-md transition-all"
59+
onclick={() => openInApp(PUBLIC_BLABSY_BASE_URL)}
60+
class="flex items-center gap-4 w-full px-5 py-4 bg-gray rounded-2xl active:opacity-80 transition-opacity"
4061
>
4162
<img
4263
src="/images/blabsy-logo.svg"
4364
alt="Blabsy"
44-
class="w-10 h-10 rounded-lg"
65+
class="w-10 h-10 rounded-xl"
4566
/>
46-
<span class="text-lg font-medium text-gray-800">Open in Blabsy</span>
67+
<span class="font-medium">Blabsy</span>
68+
</button>
69+
70+
<button
71+
onclick={goBack}
72+
class="w-full py-3 text-sm text-black-500 active:opacity-80 transition-opacity mt-2"
73+
>
74+
Cancel
4775
</button>
4876
</div>
4977
</div>
-1.53 KB
Binary file not shown.

0 commit comments

Comments
 (0)