@ima-jin/ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1891 @@
1
+ // src/nav-bar.tsx
2
+ import React6, { useState as useState5, useEffect as useEffect6, useRef as useRef4 } from "react";
3
+
4
+ // src/app-launcher.tsx
5
+ import React2, { useState as useState2, useEffect as useEffect2, useRef } from "react";
6
+
7
+ // src/use-identities.ts
8
+ import { useState, useEffect } from "react";
9
+
10
+ // src/acting-as.ts
11
+ function getActingAs() {
12
+ if (typeof window === "undefined") return null;
13
+ return localStorage.getItem("imajin:acting-as");
14
+ }
15
+ function setActingAs(did) {
16
+ if (typeof window === "undefined") return;
17
+ if (did) {
18
+ localStorage.setItem("imajin:acting-as", did);
19
+ document.cookie = `x-acting-as=${did}; path=/; max-age=31536000; SameSite=Lax`;
20
+ } else {
21
+ localStorage.removeItem("imajin:acting-as");
22
+ document.cookie = "x-acting-as=; path=/; max-age=0";
23
+ }
24
+ window.dispatchEvent(new CustomEvent("imajin:acting-as-changed", { detail: { did } }));
25
+ }
26
+ function getActingAsHeaders() {
27
+ const did = getActingAs();
28
+ return did ? { "X-Acting-As": did } : {};
29
+ }
30
+
31
+ // src/use-identities.ts
32
+ function useIdentities(authUrl, profileUrl) {
33
+ const [identities, setIdentities] = useState([]);
34
+ const [loading, setLoading] = useState(false);
35
+ const [activeIdentity, setActiveIdentityState] = useState(null);
36
+ const [activeConfig, setActiveConfig] = useState(null);
37
+ useEffect(() => {
38
+ setActiveIdentityState(getActingAs());
39
+ }, []);
40
+ useEffect(() => {
41
+ if (!authUrl) return;
42
+ setLoading(true);
43
+ fetch(`${authUrl}/api/groups`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((data) => {
44
+ if (Array.isArray(data)) setIdentities(data);
45
+ else if (data == null ? void 0 : data.groups) setIdentities(data.groups);
46
+ }).catch(() => {
47
+ }).finally(() => setLoading(false));
48
+ }, [authUrl]);
49
+ useEffect(() => {
50
+ const configBase = profileUrl;
51
+ if (!configBase || !activeIdentity) {
52
+ setActiveConfig(null);
53
+ return;
54
+ }
55
+ fetch(`${configBase}/api/forest/${encodeURIComponent(activeIdentity)}/config/public`).then((r) => r.ok ? r.json() : null).then((data) => {
56
+ if (data) setActiveConfig(data);
57
+ }).catch(() => {
58
+ });
59
+ }, [authUrl, profileUrl, activeIdentity]);
60
+ function setActiveIdentity(did) {
61
+ setActingAs(did);
62
+ setActiveIdentityState(did);
63
+ window.location.reload();
64
+ }
65
+ return { identities, loading, activeIdentity, activeConfig, setActiveIdentity };
66
+ }
67
+
68
+ // src/app-launcher.tsx
69
+ function filterByTier(services, tier) {
70
+ const hasProfile = tier === "hard" || tier === "creator";
71
+ return services.filter((s) => {
72
+ if (s.visibility === "internal") return false;
73
+ if (s.visibility === "public") return true;
74
+ if (s.visibility === "authenticated") return hasProfile;
75
+ if (s.visibility === "creator") return hasProfile;
76
+ return false;
77
+ });
78
+ }
79
+ function groupByCategory(services) {
80
+ return {
81
+ kernel: services.filter((s) => s.category === "kernel"),
82
+ core: services.filter((s) => s.category === "core"),
83
+ creator: services.filter((s) => s.category === "creator"),
84
+ developer: services.filter((s) => s.category === "developer"),
85
+ meta: services.filter((s) => s.category === "meta")
86
+ };
87
+ }
88
+ function scopeIcon(scope) {
89
+ if (scope === "community") return "\u{1F3DB}\uFE0F";
90
+ if (scope === "org") return "\u{1F3E2}";
91
+ if (scope === "family") return "\u{1F468}\u200D\u{1F469}\u200D\u{1F466}";
92
+ if (scope === "node") return "\u{1F5A5}\uFE0F";
93
+ if (scope === "agent") return "\u{1F916}";
94
+ if (scope === "device") return "\u{1F4F1}";
95
+ return "\u{1F464}";
96
+ }
97
+ function AppLauncher({ registryUrl, currentService, tier = "anonymous", inline = false, variant = "list", authUrl, enabledServices }) {
98
+ var _a, _b;
99
+ const [services, setServices] = useState2([]);
100
+ const [showPanel, setShowPanel] = useState2(false);
101
+ const panelRef = useRef(null);
102
+ const showIdentities = tier === "hard" || tier === "creator";
103
+ const { identities, activeIdentity, setActiveIdentity } = useIdentities(showIdentities && authUrl ? authUrl : null, null);
104
+ useEffect2(() => {
105
+ fetch(`${registryUrl}/api/specs`).then((r) => r.ok ? r.json() : null).then((data) => {
106
+ if (data == null ? void 0 : data.services) setServices(data.services);
107
+ }).catch(() => {
108
+ });
109
+ }, [registryUrl]);
110
+ useEffect2(() => {
111
+ if (!showPanel) return;
112
+ function handleClickOutside(event) {
113
+ if (panelRef.current && !panelRef.current.contains(event.target)) {
114
+ setShowPanel(false);
115
+ }
116
+ }
117
+ document.addEventListener("mousedown", handleClickOutside);
118
+ return () => document.removeEventListener("mousedown", handleClickOutside);
119
+ }, [showPanel]);
120
+ let visible = filterByTier(services, tier);
121
+ if (enabledServices && enabledServices.length > 0) {
122
+ visible = visible.filter((s) => enabledServices.includes(s.name));
123
+ }
124
+ const { kernel, core, creator, developer, meta } = groupByCategory(visible);
125
+ const wwwUrl = ((_a = services.find((s) => s.name === "kernel")) == null ? void 0 : _a.url) || ((_b = services.find((s) => s.name === "www")) == null ? void 0 : _b.url) || "#";
126
+ function renderTile(service) {
127
+ const isCurrent = service.name === currentService;
128
+ const isExternal = !!service.externalUrl;
129
+ return /* @__PURE__ */ React2.createElement(
130
+ "a",
131
+ {
132
+ key: service.name,
133
+ href: service.url,
134
+ ...isExternal && { target: "_blank", rel: "noopener noreferrer" },
135
+ className: `flex items-center gap-3 px-3 py-2.5 rounded-lg text-sm transition no-underline ${isCurrent ? "bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800"}`
136
+ },
137
+ /* @__PURE__ */ React2.createElement("span", { className: "text-lg flex-shrink-0" }, service.icon),
138
+ /* @__PURE__ */ React2.createElement("span", null, service.label)
139
+ );
140
+ }
141
+ function renderGridTile(service) {
142
+ const isCurrent = service.name === currentService;
143
+ return /* @__PURE__ */ React2.createElement(
144
+ "a",
145
+ {
146
+ key: service.name,
147
+ href: service.url,
148
+ className: `flex flex-col items-center justify-center w-16 h-16 rounded-lg text-center transition no-underline ${isCurrent ? "bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800"}`
149
+ },
150
+ /* @__PURE__ */ React2.createElement("span", { className: "text-2xl" }, service.icon),
151
+ /* @__PURE__ */ React2.createElement("span", { className: "text-[10px] mt-0.5 leading-tight" }, service.label)
152
+ );
153
+ }
154
+ const identitiesSection = showIdentities && (identities.length > 0 || authUrl) ? /* @__PURE__ */ React2.createElement("div", { className: "border-t border-gray-200 dark:border-gray-800 mt-1 pt-1" }, /* @__PURE__ */ React2.createElement("div", { className: "px-3 pt-1 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Switch To"), identities.map((ident) => {
155
+ const isActive = ident.groupDid === activeIdentity;
156
+ return /* @__PURE__ */ React2.createElement(
157
+ "button",
158
+ {
159
+ key: ident.groupDid,
160
+ onClick: () => {
161
+ setActiveIdentity(isActive ? null : ident.groupDid);
162
+ setShowPanel(false);
163
+ },
164
+ className: `w-full text-left flex items-center gap-3 px-3 py-2 rounded-lg text-sm transition ${isActive ? "bg-amber-50 dark:bg-amber-900/20 text-amber-700 dark:text-amber-300 font-medium" : "text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800"}`
165
+ },
166
+ /* @__PURE__ */ React2.createElement("span", { className: "text-lg flex-shrink-0" }, scopeIcon(ident.scope)),
167
+ /* @__PURE__ */ React2.createElement("span", null, ident.name || ident.handle || ident.groupDid.slice(0, 12)),
168
+ isActive && /* @__PURE__ */ React2.createElement("span", { className: "ml-auto text-amber-600 dark:text-amber-400 font-bold text-xs" }, "\u2713")
169
+ );
170
+ })) : null;
171
+ const footer = /* @__PURE__ */ React2.createElement("div", { className: "border-t border-gray-200 dark:border-gray-800 mt-1 pt-1" }, /* @__PURE__ */ React2.createElement(
172
+ "a",
173
+ {
174
+ href: `${wwwUrl}/`,
175
+ className: "flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition no-underline"
176
+ },
177
+ "See all apps \u2192"
178
+ ));
179
+ const content = variant === "grid" ? /* @__PURE__ */ React2.createElement(React2.Fragment, null, kernel.length > 0 && /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "col-span-4 px-3 pt-1 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Kernel Services"), /* @__PURE__ */ React2.createElement("div", { className: "grid grid-cols-4 gap-1 px-2" }, kernel.map(renderGridTile))), core.length > 0 && /* @__PURE__ */ React2.createElement("div", { className: "grid grid-cols-4 gap-1 px-2" }, core.map(renderGridTile)), creator.length > 0 && /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "col-span-4 px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Creator Tools"), /* @__PURE__ */ React2.createElement("div", { className: "grid grid-cols-4 gap-1 px-2" }, creator.map(renderGridTile))), developer.length > 0 && /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "col-span-4 px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Developers"), /* @__PURE__ */ React2.createElement("div", { className: "grid grid-cols-4 gap-1 px-2" }, developer.map(renderGridTile))), identitiesSection, footer) : /* @__PURE__ */ React2.createElement(React2.Fragment, null, kernel.length > 0 && /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "px-3 pt-1 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Kernel Services"), kernel.map(renderTile)), core.length > 0 && /* @__PURE__ */ React2.createElement("div", null, core.map(renderTile)), creator.length > 0 && /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Creator Tools"), creator.map(renderTile)), developer.length > 0 && /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Developers"), developer.map(renderTile)), meta.length > 0 && /* @__PURE__ */ React2.createElement("div", null, /* @__PURE__ */ React2.createElement("div", { className: "px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Project"), meta.map(renderTile)), identitiesSection, footer);
180
+ if (inline) {
181
+ return /* @__PURE__ */ React2.createElement("div", { className: "space-y-0.5" }, content);
182
+ }
183
+ return /* @__PURE__ */ React2.createElement("div", { className: "relative", ref: panelRef }, /* @__PURE__ */ React2.createElement(
184
+ "button",
185
+ {
186
+ onClick: () => setShowPanel(!showPanel),
187
+ className: `px-3 py-1.5 rounded-lg text-sm transition flex items-center gap-1.5 ${showPanel ? "bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800"}`
188
+ },
189
+ variant === "grid" ? /* @__PURE__ */ React2.createElement("span", { className: "grid grid-cols-2 gap-0.5 w-4 h-4" }, /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-sm bg-current" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-sm bg-current" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-sm bg-current" }), /* @__PURE__ */ React2.createElement("span", { className: "w-1.5 h-1.5 rounded-sm bg-current" })) : /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement("span", null, "\u{1F680}"), /* @__PURE__ */ React2.createElement("span", { className: "hidden sm:inline" }, "Launcher"))
190
+ ), showPanel && /* @__PURE__ */ React2.createElement("div", { className: `absolute left-0 mt-2 ${variant === "grid" ? "w-72" : "w-56"} bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-xl shadow-xl py-2 z-50` }, visible.length === 0 ? /* @__PURE__ */ React2.createElement("div", { className: "px-4 py-3 text-sm text-gray-400" }, "Loading...") : content));
191
+ }
192
+
193
+ // src/notification-bell.tsx
194
+ import React5, { useState as useState4, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback3 } from "react";
195
+
196
+ // src/notification-provider.tsx
197
+ import React4, { createContext as createContext2, useContext as useContext2, useState as useState3, useEffect as useEffect4, useRef as useRef2, useCallback as useCallback2 } from "react";
198
+
199
+ // src/toast.tsx
200
+ import React3, { createContext, useContext, useReducer, useCallback, useEffect as useEffect3 } from "react";
201
+ function reducer(state, action) {
202
+ switch (action.type) {
203
+ case "ADD":
204
+ return [...state, action.toast].slice(-5);
205
+ case "REMOVE":
206
+ return state.filter((t) => t.id !== action.id);
207
+ default:
208
+ return state;
209
+ }
210
+ }
211
+ var ToastContext = createContext(null);
212
+ var BORDER_COLORS = {
213
+ success: "border-l-green-500",
214
+ error: "border-l-red-500",
215
+ warning: "border-l-amber-500",
216
+ info: "border-l-blue-500"
217
+ };
218
+ var DURATIONS = {
219
+ success: 4e3,
220
+ error: 0,
221
+ warning: 6e3,
222
+ info: 4e3
223
+ };
224
+ function AutoDismiss({
225
+ id,
226
+ duration,
227
+ onDismiss
228
+ }) {
229
+ useEffect3(() => {
230
+ const timer = setTimeout(() => onDismiss(id), duration);
231
+ return () => clearTimeout(timer);
232
+ }, [id, duration, onDismiss]);
233
+ return null;
234
+ }
235
+ function ToastItem({
236
+ toast,
237
+ onDismiss
238
+ }) {
239
+ return /* @__PURE__ */ React3.createElement(
240
+ "div",
241
+ {
242
+ className: `flex items-start gap-3 bg-gray-900 text-white px-4 py-3 rounded-lg shadow-lg border-l-4 ${BORDER_COLORS[toast.type]} min-w-[280px] max-w-sm`,
243
+ style: { animation: "toastSlideIn 0.2s ease-out" }
244
+ },
245
+ /* @__PURE__ */ React3.createElement("p", { className: "flex-1 text-sm" }, toast.message),
246
+ /* @__PURE__ */ React3.createElement(
247
+ "button",
248
+ {
249
+ onClick: onDismiss,
250
+ className: "text-gray-400 hover:text-white transition-colors shrink-0 text-lg leading-none",
251
+ "aria-label": "Dismiss"
252
+ },
253
+ "\xD7"
254
+ )
255
+ );
256
+ }
257
+ function ToastProvider({ children }) {
258
+ const [toasts, dispatch] = useReducer(reducer, []);
259
+ const dismiss = useCallback((id) => {
260
+ dispatch({ type: "REMOVE", id });
261
+ }, []);
262
+ const add = useCallback((type, message) => {
263
+ const id = typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : String(Date.now() + Math.random());
264
+ const sticky = type === "error";
265
+ dispatch({ type: "ADD", toast: { id, type, message, sticky } });
266
+ }, []);
267
+ const toast = {
268
+ success: (message) => add("success", message),
269
+ error: (message) => add("error", message),
270
+ warning: (message) => add("warning", message),
271
+ info: (message) => add("info", message)
272
+ };
273
+ return /* @__PURE__ */ React3.createElement(ToastContext.Provider, { value: { toast } }, children, /* @__PURE__ */ React3.createElement(
274
+ "div",
275
+ {
276
+ className: "fixed bottom-4 right-4 z-[9999] flex flex-col gap-2 items-end",
277
+ "aria-live": "polite"
278
+ },
279
+ /* @__PURE__ */ React3.createElement("style", null, `
280
+ @keyframes toastSlideIn {
281
+ from { transform: translateX(calc(100% + 1rem)); opacity: 0; }
282
+ to { transform: translateX(0); opacity: 1; }
283
+ }
284
+ `),
285
+ toasts.map((t) => /* @__PURE__ */ React3.createElement(React3.Fragment, { key: t.id }, !t.sticky && DURATIONS[t.type] > 0 && /* @__PURE__ */ React3.createElement(AutoDismiss, { id: t.id, duration: DURATIONS[t.type], onDismiss: dismiss }), /* @__PURE__ */ React3.createElement(ToastItem, { toast: t, onDismiss: () => dismiss(t.id) })))
286
+ ));
287
+ }
288
+ function useToast() {
289
+ const ctx = useContext(ToastContext);
290
+ if (!ctx) throw new Error("useToast must be used within a ToastProvider");
291
+ return ctx;
292
+ }
293
+
294
+ // src/notification-provider.tsx
295
+ var NotificationContext = createContext2(null);
296
+ var emptyValue = {
297
+ unreadCount: 0,
298
+ notifications: [],
299
+ markAsRead: async () => {
300
+ },
301
+ markAllAsRead: async () => {
302
+ },
303
+ refresh: async () => {
304
+ }
305
+ };
306
+ function NotificationProvider({ children }) {
307
+ const notifyUrl = process.env.NEXT_PUBLIC_NOTIFY_URL;
308
+ const [unreadCount, setUnreadCount] = useState3(0);
309
+ const [notifications, setNotifications] = useState3([]);
310
+ const hasWarnedRef = useRef2(false);
311
+ const lastCountRef = useRef2(-1);
312
+ const lastNewIdRef = useRef2(null);
313
+ const { toast } = useToast();
314
+ const toastRef = useRef2(toast);
315
+ toastRef.current = toast;
316
+ const fetchAndMaybeToast = useCallback2(async () => {
317
+ var _a;
318
+ if (!notifyUrl) return;
319
+ try {
320
+ const res = await fetch(`${notifyUrl}/api/notifications/unread`, { credentials: "include" });
321
+ if (!res.ok) return;
322
+ const data = await res.json();
323
+ const count = data.count ?? 0;
324
+ if (lastCountRef.current >= 0 && count > lastCountRef.current) {
325
+ try {
326
+ const listRes = await fetch(`${notifyUrl}/api/notifications?limit=1`, { credentials: "include" });
327
+ if (listRes.ok) {
328
+ const listData = await listRes.json();
329
+ const latest = ((_a = listData.notifications) == null ? void 0 : _a[0]) ?? listData[0];
330
+ if (latest && latest.id !== lastNewIdRef.current) {
331
+ lastNewIdRef.current = latest.id;
332
+ if (latest.urgency === "urgent") {
333
+ toastRef.current.warning(latest.title);
334
+ } else {
335
+ toastRef.current.info(latest.title);
336
+ }
337
+ }
338
+ }
339
+ } catch {
340
+ }
341
+ }
342
+ lastCountRef.current = count;
343
+ setUnreadCount(count);
344
+ } catch {
345
+ if (!hasWarnedRef.current) {
346
+ console.warn("[notifications] notify service unavailable \u2014 bell will show 0 unread");
347
+ hasWarnedRef.current = true;
348
+ }
349
+ }
350
+ }, [notifyUrl]);
351
+ useEffect4(() => {
352
+ if (!notifyUrl) return;
353
+ fetchAndMaybeToast();
354
+ const interval = setInterval(fetchAndMaybeToast, 3e4);
355
+ return () => clearInterval(interval);
356
+ }, [notifyUrl, fetchAndMaybeToast]);
357
+ const refresh = useCallback2(async () => {
358
+ if (!notifyUrl) return;
359
+ try {
360
+ const res = await fetch(`${notifyUrl}/api/notifications?limit=20`, { credentials: "include" });
361
+ if (!res.ok) return;
362
+ const data = await res.json();
363
+ setNotifications(data.notifications ?? data ?? []);
364
+ } catch {
365
+ }
366
+ }, [notifyUrl]);
367
+ const markAsRead = useCallback2(async (id) => {
368
+ if (!notifyUrl) return;
369
+ try {
370
+ await fetch(`${notifyUrl}/api/notifications/${id}/read`, {
371
+ method: "POST",
372
+ credentials: "include"
373
+ });
374
+ setNotifications((prev) => prev.map((n) => n.id === id ? { ...n, read: true } : n));
375
+ setUnreadCount((prev) => Math.max(0, prev - 1));
376
+ } catch {
377
+ }
378
+ }, [notifyUrl]);
379
+ const markAllAsRead = useCallback2(async () => {
380
+ if (!notifyUrl) return;
381
+ try {
382
+ await fetch(`${notifyUrl}/api/notifications/read-all`, {
383
+ method: "POST",
384
+ credentials: "include"
385
+ });
386
+ setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
387
+ setUnreadCount(0);
388
+ lastCountRef.current = 0;
389
+ } catch {
390
+ }
391
+ }, [notifyUrl]);
392
+ return /* @__PURE__ */ React4.createElement(NotificationContext.Provider, { value: { unreadCount, notifications, markAsRead, markAllAsRead, refresh } }, children);
393
+ }
394
+ function useNotifications() {
395
+ return useContext2(NotificationContext) ?? emptyValue;
396
+ }
397
+
398
+ // src/notification-bell.tsx
399
+ function relativeTime(dateStr) {
400
+ const diff = Date.now() - new Date(dateStr).getTime();
401
+ const minutes = Math.floor(diff / 6e4);
402
+ if (minutes < 1) return "just now";
403
+ if (minutes < 60) return `${minutes}m ago`;
404
+ const hours = Math.floor(minutes / 60);
405
+ if (hours < 24) return `${hours}h ago`;
406
+ const days = Math.floor(hours / 24);
407
+ if (days === 1) return "Yesterday";
408
+ return `${days}d ago`;
409
+ }
410
+ function BellIcon({ className }) {
411
+ return /* @__PURE__ */ React5.createElement(
412
+ "svg",
413
+ {
414
+ className,
415
+ xmlns: "http://www.w3.org/2000/svg",
416
+ viewBox: "0 0 24 24",
417
+ fill: "none",
418
+ stroke: "currentColor",
419
+ strokeWidth: 2,
420
+ strokeLinecap: "round",
421
+ strokeLinejoin: "round",
422
+ "aria-hidden": "true"
423
+ },
424
+ /* @__PURE__ */ React5.createElement("path", { d: "M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" }),
425
+ /* @__PURE__ */ React5.createElement("path", { d: "M13.73 21a2 2 0 0 1-3.46 0" })
426
+ );
427
+ }
428
+ function NotificationBell() {
429
+ const { unreadCount, notifications, markAsRead, markAllAsRead, refresh } = useNotifications();
430
+ const [open, setOpen] = useState4(false);
431
+ const [loading, setLoading] = useState4(false);
432
+ const panelRef = useRef3(null);
433
+ const handleOpen = useCallback3(async () => {
434
+ if (!open) {
435
+ setOpen(true);
436
+ setLoading(true);
437
+ await refresh();
438
+ setLoading(false);
439
+ } else {
440
+ setOpen(false);
441
+ }
442
+ }, [open, refresh]);
443
+ useEffect5(() => {
444
+ if (!open) return;
445
+ function handleClickOutside(e) {
446
+ if (panelRef.current && !panelRef.current.contains(e.target)) {
447
+ setOpen(false);
448
+ }
449
+ }
450
+ function handleKeyDown(e) {
451
+ if (e.key === "Escape") setOpen(false);
452
+ }
453
+ document.addEventListener("mousedown", handleClickOutside);
454
+ document.addEventListener("keydown", handleKeyDown);
455
+ return () => {
456
+ document.removeEventListener("mousedown", handleClickOutside);
457
+ document.removeEventListener("keydown", handleKeyDown);
458
+ };
459
+ }, [open]);
460
+ const hasUnread = notifications.some((n) => !n.read);
461
+ return /* @__PURE__ */ React5.createElement("div", { className: "relative", ref: panelRef }, /* @__PURE__ */ React5.createElement(
462
+ "button",
463
+ {
464
+ onClick: handleOpen,
465
+ className: `relative p-2 rounded-lg transition ${open ? "bg-gray-100 dark:bg-gray-800" : "hover:bg-gray-100 dark:hover:bg-gray-800"}`,
466
+ title: "Notifications",
467
+ "aria-label": `Notifications${unreadCount > 0 ? `, ${unreadCount} unread` : ""}`,
468
+ "aria-expanded": open
469
+ },
470
+ /* @__PURE__ */ React5.createElement(BellIcon, { className: "w-5 h-5 text-gray-600 dark:text-gray-300" }),
471
+ unreadCount > 0 && /* @__PURE__ */ React5.createElement("span", { className: "absolute -top-0.5 -right-0.5 bg-red-500 text-white text-[10px] font-bold rounded-full min-w-[1.1rem] h-[1.1rem] flex items-center justify-center px-1 leading-none pointer-events-none" }, unreadCount > 99 ? "99+" : unreadCount)
472
+ ), open && /* @__PURE__ */ React5.createElement("div", { className: "absolute right-0 mt-2 w-80 bg-gray-900 border border-gray-700 rounded-lg shadow-lg z-50 flex flex-col max-h-[24rem]" }, /* @__PURE__ */ React5.createElement("div", { className: "flex items-center justify-between px-4 py-3 border-b border-gray-700 shrink-0" }, /* @__PURE__ */ React5.createElement("span", { className: "text-sm font-semibold text-white" }, "Notifications"), hasUnread && /* @__PURE__ */ React5.createElement(
473
+ "button",
474
+ {
475
+ onClick: () => markAllAsRead(),
476
+ className: "text-xs text-blue-400 hover:text-blue-300 transition"
477
+ },
478
+ "Mark all as read"
479
+ )), /* @__PURE__ */ React5.createElement("div", { className: "overflow-y-auto flex-1" }, loading ? /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-6 text-center text-sm text-gray-400" }, "Loading\u2026") : notifications.length === 0 ? /* @__PURE__ */ React5.createElement("div", { className: "px-4 py-6 text-center text-sm text-gray-400" }, "No notifications yet") : notifications.map((notification) => /* @__PURE__ */ React5.createElement(
480
+ "button",
481
+ {
482
+ key: notification.id,
483
+ onClick: () => markAsRead(notification.id),
484
+ className: `w-full text-left px-4 py-3 border-b border-gray-800 last:border-0 hover:bg-gray-800 transition flex items-start gap-3 ${!notification.read ? "bg-gray-800/50" : ""}`
485
+ },
486
+ /* @__PURE__ */ React5.createElement("span", { className: "shrink-0 w-2 h-2 rounded-full mt-1.5 flex items-center justify-center" }, !notification.read && /* @__PURE__ */ React5.createElement("span", { className: "block w-2 h-2 rounded-full bg-blue-500", "aria-hidden": "true" })),
487
+ /* @__PURE__ */ React5.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React5.createElement("p", { className: "text-sm font-medium text-white truncate" }, notification.title), notification.body && /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-gray-400 mt-0.5 line-clamp-2" }, notification.body), /* @__PURE__ */ React5.createElement("p", { className: "text-xs text-gray-500 mt-1" }, relativeTime(notification.createdAt)))
488
+ )))));
489
+ }
490
+
491
+ // src/nav-bar.tsx
492
+ import { getPort } from "@imajin/config";
493
+ function buildUrl(service, prefix, domain, overrides) {
494
+ const url = overrides == null ? void 0 : overrides[service];
495
+ if (url) return url;
496
+ if (domain.includes("localhost") || prefix.includes("localhost")) {
497
+ const port = getPort(service, "dev");
498
+ return port ? `http://localhost:${port}` : `http://localhost:3000`;
499
+ }
500
+ const stripped = prefix.replace(/^https?:\/\//, "");
501
+ if (stripped.includes(".")) {
502
+ const base = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
503
+ if (service === "www" || service === "kernel") return base;
504
+ return `${base}/${service}`;
505
+ }
506
+ return `${prefix}${service}.${domain}`;
507
+ }
508
+ function scopeIcon2(scope) {
509
+ if (scope === "community") return "\u{1F3DB}\uFE0F";
510
+ if (scope === "org") return "\u{1F3E2}";
511
+ if (scope === "family") return "\u{1F468}\u200D\u{1F469}\u200D\u{1F466}";
512
+ if (scope === "node") return "\u{1F5A5}\uFE0F";
513
+ if (scope === "agent") return "\u{1F916}";
514
+ if (scope === "device") return "\u{1F4F1}";
515
+ return "\u{1F464}";
516
+ }
517
+ function buildUserLinks(prefix, domain, overrides) {
518
+ return {
519
+ connections: buildUrl("connections", prefix, domain, overrides),
520
+ messages: buildUrl("chat", prefix, domain, overrides),
521
+ profile: buildUrl("profile", prefix, domain, overrides)
522
+ };
523
+ }
524
+ function getLauncherTier(identity) {
525
+ if (!(identity == null ? void 0 : identity.isLoggedIn)) return "anonymous";
526
+ if (identity.tier === "soft") return "soft";
527
+ return "hard";
528
+ }
529
+ function useAutoIdentity(servicePrefix, domain, overrides) {
530
+ const [identity, setIdentity] = useState5(null);
531
+ useEffect6(() => {
532
+ if (typeof window === "undefined") return;
533
+ const authUrl = buildUrl("auth", servicePrefix, domain, overrides);
534
+ const profileUrl = buildUrl("profile", servicePrefix, domain, overrides);
535
+ async function checkSession() {
536
+ try {
537
+ const res = await fetch(`${authUrl}/api/session`, {
538
+ credentials: "include"
539
+ });
540
+ if (res.ok) {
541
+ const data = await res.json();
542
+ setIdentity({
543
+ isLoggedIn: true,
544
+ handle: data.handle || null,
545
+ did: data.did || null,
546
+ name: data.name || null,
547
+ tier: data.tier || null,
548
+ onLogout: async () => {
549
+ try {
550
+ await fetch(`${authUrl}/api/logout`, {
551
+ method: "POST",
552
+ credentials: "include"
553
+ });
554
+ } catch {
555
+ }
556
+ setIdentity({
557
+ isLoggedIn: false,
558
+ onLogin: () => {
559
+ window.location.href = `${authUrl}/login?next=${encodeURIComponent(window.location.href)}`;
560
+ },
561
+ onRegister: () => {
562
+ window.location.href = `${authUrl}/register`;
563
+ }
564
+ });
565
+ },
566
+ onViewProfile: data.handle ? () => {
567
+ window.location.href = `${profileUrl}/${data.handle}`;
568
+ } : data.did ? () => {
569
+ window.location.href = `${profileUrl}/${data.did}`;
570
+ } : void 0,
571
+ onEditProfile: () => {
572
+ window.location.href = `${profileUrl}/edit`;
573
+ },
574
+ onLogin: () => {
575
+ window.location.href = `${authUrl}/login?next=${encodeURIComponent(window.location.href)}`;
576
+ },
577
+ onRegister: () => {
578
+ window.location.href = `${authUrl}/register`;
579
+ }
580
+ });
581
+ } else {
582
+ setIdentity({
583
+ isLoggedIn: false,
584
+ onLogin: () => {
585
+ window.location.href = `${authUrl}/login?next=${encodeURIComponent(window.location.href)}`;
586
+ },
587
+ onRegister: () => {
588
+ window.location.href = `${authUrl}/register`;
589
+ }
590
+ });
591
+ }
592
+ } catch {
593
+ setIdentity(null);
594
+ }
595
+ }
596
+ checkSession();
597
+ const handler = () => checkSession();
598
+ window.addEventListener("imajin:session-changed", handler);
599
+ return () => window.removeEventListener("imajin:session-changed", handler);
600
+ }, [servicePrefix, domain, overrides]);
601
+ return identity;
602
+ }
603
+ function NavBar({
604
+ currentService,
605
+ servicePrefix = "https://",
606
+ domain = "imajin.ai",
607
+ identity: identityProp,
608
+ unreadMessages = 0,
609
+ serviceUrls,
610
+ children
611
+ }) {
612
+ var _a;
613
+ const [isEmbed, setIsEmbed] = useState5(false);
614
+ useEffect6(() => {
615
+ if (typeof window !== "undefined") {
616
+ setIsEmbed(new URLSearchParams(window.location.search).get("embed") === "hub");
617
+ }
618
+ }, []);
619
+ const userLinks = buildUserLinks(servicePrefix, domain, serviceUrls);
620
+ const isDev = servicePrefix.includes("dev-");
621
+ const [showDropdown, setShowDropdown] = useState5(false);
622
+ const [showMobileMenu, setShowMobileMenu] = useState5(false);
623
+ const dropdownRef = useRef4(null);
624
+ const [theme, setTheme] = useState5("dark");
625
+ useEffect6(() => {
626
+ const saved = typeof window !== "undefined" ? localStorage.getItem("theme") : null;
627
+ setTheme(saved === "light" ? "light" : "dark");
628
+ }, []);
629
+ function toggleTheme() {
630
+ const next = theme === "dark" ? "light" : "dark";
631
+ setTheme(next);
632
+ localStorage.setItem("theme", next);
633
+ if (next === "dark") {
634
+ document.documentElement.classList.add("dark");
635
+ } else {
636
+ document.documentElement.classList.remove("dark");
637
+ }
638
+ }
639
+ const autoIdentity = useAutoIdentity(servicePrefix, domain, serviceUrls);
640
+ const identity = identityProp ?? autoIdentity;
641
+ const registryUrl = buildUrl("registry", servicePrefix, domain, serviceUrls);
642
+ const launcherTier = getLauncherTier(identity);
643
+ const [cashBalance, setCashBalance] = useState5(null);
644
+ const [mjnBalance, setMjnBalance] = useState5(null);
645
+ const [scopeVersion, setScopeVersion] = useState5(0);
646
+ useEffect6(() => {
647
+ const handler = () => setScopeVersion((v) => v + 1);
648
+ window.addEventListener("imajin:acting-as-changed", handler);
649
+ return () => window.removeEventListener("imajin:acting-as-changed", handler);
650
+ }, []);
651
+ useEffect6(() => {
652
+ if (!(identity == null ? void 0 : identity.isLoggedIn) || !(identity == null ? void 0 : identity.did)) {
653
+ setCashBalance(null);
654
+ setMjnBalance(null);
655
+ return;
656
+ }
657
+ const actingAs = typeof window !== "undefined" ? localStorage.getItem("imajin:acting-as") : null;
658
+ const effectiveDid = actingAs || identity.did;
659
+ const payUrl = buildUrl("pay", servicePrefix, domain, serviceUrls);
660
+ fetch(`${payUrl}/api/balance/${encodeURIComponent(effectiveDid)}`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((data) => {
661
+ if (data) {
662
+ setCashBalance(data.cashAmount != null ? parseFloat(data.cashAmount) : null);
663
+ setMjnBalance(data.creditAmount != null ? parseFloat(data.creditAmount) : null);
664
+ } else {
665
+ setCashBalance(null);
666
+ setMjnBalance(null);
667
+ }
668
+ }).catch(() => {
669
+ });
670
+ }, [identity == null ? void 0 : identity.isLoggedIn, identity == null ? void 0 : identity.did, servicePrefix, domain, serviceUrls, scopeVersion]);
671
+ const [unread, setUnread] = useState5(unreadMessages);
672
+ useEffect6(() => {
673
+ if (!(identity == null ? void 0 : identity.isLoggedIn) || (identity == null ? void 0 : identity.tier) === "soft") {
674
+ setUnread(0);
675
+ return;
676
+ }
677
+ const chatUrl = buildUrl("chat", servicePrefix, domain, serviceUrls);
678
+ function fetchUnread() {
679
+ fetch(`${chatUrl}/api/conversations/unread`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((data) => {
680
+ if ((data == null ? void 0 : data.total) != null) setUnread(data.total);
681
+ }).catch(() => {
682
+ });
683
+ }
684
+ fetchUnread();
685
+ const interval = setInterval(fetchUnread, 6e4);
686
+ return () => clearInterval(interval);
687
+ }, [identity == null ? void 0 : identity.isLoggedIn, identity == null ? void 0 : identity.tier, servicePrefix, domain, serviceUrls]);
688
+ const authUrl = buildUrl("auth", servicePrefix, domain, serviceUrls);
689
+ const profileUrl = buildUrl("profile", servicePrefix, domain, serviceUrls);
690
+ const { identities, activeIdentity, activeConfig, setActiveIdentity } = useIdentities(
691
+ (identity == null ? void 0 : identity.isLoggedIn) && (identity == null ? void 0 : identity.tier) !== "soft" ? authUrl : null,
692
+ (identity == null ? void 0 : identity.isLoggedIn) && (identity == null ? void 0 : identity.tier) !== "soft" ? profileUrl : null
693
+ );
694
+ const activeIdentityData = identities.find((f) => f.groupDid === activeIdentity) ?? null;
695
+ useEffect6(() => {
696
+ function handleClickOutside(event) {
697
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
698
+ setShowDropdown(false);
699
+ }
700
+ }
701
+ if (showDropdown) {
702
+ document.addEventListener("mousedown", handleClickOutside);
703
+ return () => document.removeEventListener("mousedown", handleClickOutside);
704
+ }
705
+ }, [showDropdown]);
706
+ return !isEmbed ? /* @__PURE__ */ React6.createElement("nav", { className: "w-full border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm relative z-50" }, isDev && /* @__PURE__ */ React6.createElement("div", { className: "w-full bg-amber-500/90 text-black text-xs font-bold text-center py-1 tracking-wide" }, "\u26A0 DEVELOPMENT ENVIRONMENT"), /* @__PURE__ */ React6.createElement("div", { className: "max-w-6xl mx-auto px-4 py-3 flex items-center gap-2" }, /* @__PURE__ */ React6.createElement(
707
+ "a",
708
+ {
709
+ href: buildUrl("www", servicePrefix, domain, serviceUrls),
710
+ className: "flex items-center hover:opacity-80 transition shrink-0"
711
+ },
712
+ /* @__PURE__ */ React6.createElement("span", { className: "w-8 h-8 rounded-lg bg-amber-500/10 dark:bg-amber-500/20 flex items-center justify-center" }, /* @__PURE__ */ React6.createElement("span", { className: "text-xl font-bold text-amber-500" }, "\u4EBA"))
713
+ ), /* @__PURE__ */ React6.createElement("div", { className: "flex-1 min-w-0" }, children), /* @__PURE__ */ React6.createElement("div", { className: "hidden sm:flex items-center gap-1" }, /* @__PURE__ */ React6.createElement(
714
+ AppLauncher,
715
+ {
716
+ registryUrl,
717
+ currentService,
718
+ tier: launcherTier,
719
+ variant: "grid",
720
+ authUrl: (identity == null ? void 0 : identity.isLoggedIn) && (identity == null ? void 0 : identity.tier) !== "soft" ? authUrl : void 0,
721
+ enabledServices: activeConfig == null ? void 0 : activeConfig.enabledServices
722
+ }
723
+ ), (identity == null ? void 0 : identity.isLoggedIn) && (identity == null ? void 0 : identity.tier) !== "soft" && /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(
724
+ "a",
725
+ {
726
+ href: userLinks.messages,
727
+ className: "relative p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition no-underline",
728
+ title: "Messages"
729
+ },
730
+ /* @__PURE__ */ React6.createElement("span", { className: "text-lg" }, "\u{1F4AC}"),
731
+ unread > 0 && /* @__PURE__ */ React6.createElement("span", { className: "absolute -top-0.5 -right-0.5 bg-orange-500 text-white text-[10px] font-bold rounded-full min-w-[1.1rem] h-[1.1rem] flex items-center justify-center px-1" }, unread > 99 ? "99+" : unread)
732
+ ), /* @__PURE__ */ React6.createElement(
733
+ "a",
734
+ {
735
+ href: userLinks.connections,
736
+ className: "p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition no-underline",
737
+ title: "Connections"
738
+ },
739
+ /* @__PURE__ */ React6.createElement("span", { className: "text-lg" }, "\u{1F91D}")
740
+ ))), process.env.NEXT_PUBLIC_NOTIFY_URL && /* @__PURE__ */ React6.createElement("div", { className: "hidden sm:flex items-center" }, /* @__PURE__ */ React6.createElement(NotificationBell, null)), /* @__PURE__ */ React6.createElement(
741
+ "button",
742
+ {
743
+ onClick: () => setShowMobileMenu(!showMobileMenu),
744
+ className: "sm:hidden p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
745
+ },
746
+ /* @__PURE__ */ React6.createElement("span", { className: "text-xl" }, showMobileMenu ? "\u2715" : "\u2630")
747
+ ), /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-2" }, (identity == null ? void 0 : identity.isLoggedIn) && (identity == null ? void 0 : identity.tier) === "soft" ? (
748
+ /* Soft DID — just a logout button, no dropdown */
749
+ /* @__PURE__ */ React6.createElement(
750
+ "button",
751
+ {
752
+ onClick: () => {
753
+ var _a2;
754
+ return (_a2 = identity.onLogout) == null ? void 0 : _a2.call(identity);
755
+ },
756
+ className: "px-3 py-1.5 rounded-lg text-sm text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition"
757
+ },
758
+ "Logout"
759
+ )
760
+ ) : (identity == null ? void 0 : identity.isLoggedIn) ? /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-2" }, cashBalance !== null && cashBalance > 0 && /* @__PURE__ */ React6.createElement(
761
+ "a",
762
+ {
763
+ href: buildUrl("pay", servicePrefix, domain, serviceUrls),
764
+ className: "text-sm font-medium text-green-600 dark:text-green-400 bg-green-50 dark:bg-green-900/20 px-2.5 py-1 rounded-full hover:bg-green-100 dark:hover:bg-green-900/40 transition no-underline"
765
+ },
766
+ "$",
767
+ cashBalance.toFixed(2)
768
+ ), mjnBalance !== null && mjnBalance > 0 && /* @__PURE__ */ React6.createElement(
769
+ "a",
770
+ {
771
+ href: buildUrl("pay", servicePrefix, domain, serviceUrls),
772
+ className: "text-sm font-medium text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 px-2.5 py-1 rounded-full hover:bg-amber-100 dark:hover:bg-amber-900/40 transition no-underline"
773
+ },
774
+ "\u4EBA",
775
+ Math.round(mjnBalance)
776
+ ), /* @__PURE__ */ React6.createElement("div", { className: "relative", ref: dropdownRef }, /* @__PURE__ */ React6.createElement(
777
+ "button",
778
+ {
779
+ onClick: () => setShowDropdown(!showDropdown),
780
+ className: "flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition"
781
+ },
782
+ /* @__PURE__ */ React6.createElement("span", { className: "text-xl" }, activeIdentityData ? scopeIcon2(activeIdentityData.scope) : "\u{1F464}"),
783
+ /* @__PURE__ */ React6.createElement("span", { className: "flex flex-col items-start", style: { gap: "2px" } }, activeIdentityData && /* @__PURE__ */ React6.createElement("span", { className: "text-[10px] text-amber-600 dark:text-amber-400 font-medium leading-none" }, "acting as"), /* @__PURE__ */ React6.createElement("span", { className: "text-sm font-medium leading-none" }, activeIdentityData ? activeIdentityData.name || activeIdentityData.handle || "Identity" : identity.handle ? `@${identity.handle}` : identity.name ? identity.name : ((_a = identity.did) == null ? void 0 : _a.slice(0, 12)) + "..."))
784
+ ), showDropdown && /* @__PURE__ */ React6.createElement("div", { className: "absolute right-0 mt-2 w-52 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg py-1 z-50" }, identity.tier !== "soft" && /* @__PURE__ */ React6.createElement(React6.Fragment, null, identity.onViewProfile && /* @__PURE__ */ React6.createElement(
785
+ "button",
786
+ {
787
+ onClick: () => {
788
+ var _a2;
789
+ (_a2 = identity.onViewProfile) == null ? void 0 : _a2.call(identity);
790
+ setShowDropdown(false);
791
+ },
792
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2"
793
+ },
794
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F464}"),
795
+ " View Profile"
796
+ ), identity.onEditProfile && /* @__PURE__ */ React6.createElement(
797
+ "button",
798
+ {
799
+ onClick: () => {
800
+ var _a2;
801
+ (_a2 = identity.onEditProfile) == null ? void 0 : _a2.call(identity);
802
+ setShowDropdown(false);
803
+ },
804
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2"
805
+ },
806
+ /* @__PURE__ */ React6.createElement("span", null, "\u270F\uFE0F"),
807
+ " Edit Profile"
808
+ ), /* @__PURE__ */ React6.createElement(
809
+ "a",
810
+ {
811
+ href: `${buildUrl("auth", servicePrefix, domain, serviceUrls)}/settings/security`,
812
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
813
+ },
814
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F512}"),
815
+ " Security"
816
+ ), /* @__PURE__ */ React6.createElement(
817
+ "a",
818
+ {
819
+ href: `${buildUrl("notify", servicePrefix, domain, serviceUrls)}/settings`,
820
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
821
+ },
822
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F514}"),
823
+ " Notifications"
824
+ ), /* @__PURE__ */ React6.createElement("hr", { className: "my-1 border-gray-200 dark:border-gray-800" }), /* @__PURE__ */ React6.createElement(
825
+ "a",
826
+ {
827
+ href: userLinks.messages,
828
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
829
+ },
830
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F4AC}"),
831
+ " Messages",
832
+ unread > 0 && /* @__PURE__ */ React6.createElement("span", { className: "ml-auto bg-orange-500 text-white text-xs font-bold rounded-full px-2 py-0.5 min-w-[1.25rem] text-center" }, unread)
833
+ ), /* @__PURE__ */ React6.createElement(
834
+ "a",
835
+ {
836
+ href: userLinks.connections,
837
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
838
+ },
839
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F91D}"),
840
+ " Connections"
841
+ ), /* @__PURE__ */ React6.createElement(
842
+ "a",
843
+ {
844
+ href: buildUrl("pay", servicePrefix, domain, serviceUrls),
845
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
846
+ },
847
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F4B0}"),
848
+ " Wallet"
849
+ ), /* @__PURE__ */ React6.createElement(
850
+ "a",
851
+ {
852
+ href: buildUrl("media", servicePrefix, domain, serviceUrls),
853
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
854
+ },
855
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F4C1}"),
856
+ " Media"
857
+ ), /* @__PURE__ */ React6.createElement(
858
+ "a",
859
+ {
860
+ href: buildUrl("auth", servicePrefix, domain, serviceUrls),
861
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
862
+ },
863
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F511}"),
864
+ " Identities"
865
+ ), /* @__PURE__ */ React6.createElement("hr", { className: "my-1 border-gray-200 dark:border-gray-800" }), /* @__PURE__ */ React6.createElement("div", { className: "px-4 py-1 text-[10px] font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500" }, "Switch To"), activeIdentity && identity && /* @__PURE__ */ React6.createElement("div", { className: "flex items-center group/identity" }, /* @__PURE__ */ React6.createElement(
866
+ "button",
867
+ {
868
+ onClick: () => {
869
+ setActiveIdentity(null);
870
+ setShowDropdown(false);
871
+ },
872
+ className: "flex-1 text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2"
873
+ },
874
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F464}"),
875
+ /* @__PURE__ */ React6.createElement("span", null, identity.handle ? `@${identity.handle}` : identity.name || "Personal")
876
+ )), identities.slice(0, 5).map((ident) => {
877
+ const isActive = ident.groupDid === activeIdentity;
878
+ return /* @__PURE__ */ React6.createElement("div", { key: ident.groupDid, className: "flex items-center group/identity" }, /* @__PURE__ */ React6.createElement(
879
+ "button",
880
+ {
881
+ onClick: () => {
882
+ setActiveIdentity(isActive ? null : ident.groupDid);
883
+ setShowDropdown(false);
884
+ },
885
+ className: "flex-1 text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2"
886
+ },
887
+ /* @__PURE__ */ React6.createElement("span", null, scopeIcon2(ident.scope)),
888
+ /* @__PURE__ */ React6.createElement("span", { className: isActive ? "font-medium" : "" }, ident.name || ident.handle || ident.groupDid.slice(0, 12)),
889
+ isActive && /* @__PURE__ */ React6.createElement("span", { className: "ml-auto text-amber-600 dark:text-amber-400 font-bold text-xs" }, "\u2713")
890
+ ), /* @__PURE__ */ React6.createElement(
891
+ "a",
892
+ {
893
+ href: `${authUrl}/groups/${encodeURIComponent(ident.groupDid)}/settings`,
894
+ onClick: (e) => e.stopPropagation(),
895
+ className: "pr-3 py-2 text-gray-400 dark:text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 transition opacity-0 group-hover/identity:opacity-100 no-underline text-sm",
896
+ title: "Settings"
897
+ },
898
+ "\u2699\uFE0F"
899
+ ));
900
+ }), identities.length > 5 && /* @__PURE__ */ React6.createElement(
901
+ "a",
902
+ {
903
+ href: buildUrl("auth", servicePrefix, domain, serviceUrls),
904
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit text-gray-500 dark:text-gray-400"
905
+ },
906
+ "View all \u2192"
907
+ ), /* @__PURE__ */ React6.createElement("hr", { className: "my-1 border-gray-200 dark:border-gray-800" }), /* @__PURE__ */ React6.createElement(
908
+ "button",
909
+ {
910
+ onClick: toggleTheme,
911
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2"
912
+ },
913
+ /* @__PURE__ */ React6.createElement("span", null, theme === "dark" ? "\u2600\uFE0F" : "\u{1F319}"),
914
+ " ",
915
+ theme === "dark" ? "Light Mode" : "Dark Mode"
916
+ ), /* @__PURE__ */ React6.createElement(
917
+ "a",
918
+ {
919
+ href: `${buildUrl("www", servicePrefix, domain, serviceUrls)}/bugs`,
920
+ className: "w-full text-left px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2 no-underline text-inherit"
921
+ },
922
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F41B}"),
923
+ " Report a Bug"
924
+ )), identity.onLogout && /* @__PURE__ */ React6.createElement(React6.Fragment, null, identity.tier !== "soft" && /* @__PURE__ */ React6.createElement("hr", { className: "my-1 border-gray-200 dark:border-gray-800" }), /* @__PURE__ */ React6.createElement(
925
+ "button",
926
+ {
927
+ onClick: () => {
928
+ var _a2;
929
+ (_a2 = identity.onLogout) == null ? void 0 : _a2.call(identity);
930
+ setShowDropdown(false);
931
+ },
932
+ className: "w-full text-left px-4 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-800 transition flex items-center gap-2"
933
+ },
934
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F6AA}"),
935
+ " Logout"
936
+ ))))) : identity ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, identity.onLogin && /* @__PURE__ */ React6.createElement(
937
+ "button",
938
+ {
939
+ onClick: identity.onLogin,
940
+ className: "px-3 py-1.5 rounded-lg text-sm bg-[#F59E0B] text-black hover:bg-[#D97706] transition font-medium"
941
+ },
942
+ "Login"
943
+ )) : null)), showMobileMenu && /* @__PURE__ */ React6.createElement("div", { className: "sm:hidden border-t border-gray-200 dark:border-gray-800 px-4 py-3" }, children && /* @__PURE__ */ React6.createElement("div", { className: "mb-3" }, children), /* @__PURE__ */ React6.createElement(
944
+ AppLauncher,
945
+ {
946
+ registryUrl,
947
+ currentService,
948
+ tier: launcherTier,
949
+ inline: true,
950
+ variant: "grid",
951
+ authUrl: (identity == null ? void 0 : identity.isLoggedIn) && (identity == null ? void 0 : identity.tier) !== "soft" ? authUrl : void 0,
952
+ enabledServices: activeConfig == null ? void 0 : activeConfig.enabledServices
953
+ }
954
+ ), (identity == null ? void 0 : identity.isLoggedIn) && (identity == null ? void 0 : identity.tier) !== "soft" && /* @__PURE__ */ React6.createElement("div", { className: "flex items-center gap-2 mt-3 pt-3 border-t border-gray-200 dark:border-gray-800" }, /* @__PURE__ */ React6.createElement(
955
+ "a",
956
+ {
957
+ href: userLinks.messages,
958
+ className: "flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition no-underline text-sm text-inherit"
959
+ },
960
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F4AC}"),
961
+ " Messages",
962
+ unread > 0 && /* @__PURE__ */ React6.createElement("span", { className: "bg-orange-500 text-white text-xs font-bold rounded-full px-2 py-0.5 min-w-[1.25rem] text-center" }, unread)
963
+ ), /* @__PURE__ */ React6.createElement(
964
+ "a",
965
+ {
966
+ href: userLinks.connections,
967
+ className: "flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition no-underline text-sm text-inherit"
968
+ },
969
+ /* @__PURE__ */ React6.createElement("span", null, "\u{1F91D}"),
970
+ " Connections"
971
+ )))) : null;
972
+ }
973
+
974
+ // src/button.tsx
975
+ import React7 from "react";
976
+ function Button({ variant = "primary", className = "", ...props }) {
977
+ const base = "px-4 py-2 rounded font-medium transition-colors";
978
+ const variants = {
979
+ primary: "bg-orange-500 text-white hover:bg-orange-600",
980
+ secondary: "bg-gray-200 text-gray-800 hover:bg-gray-300"
981
+ };
982
+ return /* @__PURE__ */ React7.createElement("button", { className: `${base} ${variants[variant]} ${className}`, ...props });
983
+ }
984
+
985
+ // src/balance-badge.tsx
986
+ import React8, { useState as useState6, useEffect as useEffect7 } from "react";
987
+ function BalanceBadge({ did, payUrl, authToken, className = "" }) {
988
+ const [balance, setBalance] = useState6(null);
989
+ const [loading, setLoading] = useState6(false);
990
+ useEffect7(() => {
991
+ if (!did || !authToken) {
992
+ setBalance(null);
993
+ return;
994
+ }
995
+ let cancelled = false;
996
+ async function fetchBalance() {
997
+ setLoading(true);
998
+ try {
999
+ const res = await fetch(`${payUrl}/api/balance/${did}`, {
1000
+ headers: {
1001
+ "Authorization": `Bearer ${authToken}`
1002
+ },
1003
+ credentials: "include"
1004
+ });
1005
+ if (res.ok) {
1006
+ const data = await res.json();
1007
+ if (!cancelled) {
1008
+ setBalance(data);
1009
+ }
1010
+ } else {
1011
+ if (!cancelled) {
1012
+ setBalance(null);
1013
+ }
1014
+ }
1015
+ } catch (error) {
1016
+ console.error("Failed to fetch balance:", error);
1017
+ if (!cancelled) {
1018
+ setBalance(null);
1019
+ }
1020
+ } finally {
1021
+ if (!cancelled) {
1022
+ setLoading(false);
1023
+ }
1024
+ }
1025
+ }
1026
+ fetchBalance();
1027
+ return () => {
1028
+ cancelled = true;
1029
+ };
1030
+ }, [did, authToken, payUrl]);
1031
+ if (!did || !authToken) {
1032
+ return null;
1033
+ }
1034
+ if (loading) {
1035
+ return null;
1036
+ }
1037
+ if (!balance || balance.amount <= 0) {
1038
+ return null;
1039
+ }
1040
+ return /* @__PURE__ */ React8.createElement(
1041
+ "div",
1042
+ {
1043
+ className: `inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-gradient-to-r from-orange-500/10 to-amber-500/10 border border-orange-500/20 ${className}`
1044
+ },
1045
+ /* @__PURE__ */ React8.createElement("span", { className: "text-sm font-medium text-green-600 dark:text-green-400" }, "$", balance.amount.toFixed(2))
1046
+ );
1047
+ }
1048
+
1049
+ // src/footer.tsx
1050
+ import { useEffect as useEffect8, useState as useState7 } from "react";
1051
+
1052
+ // src/BuildInfo.tsx
1053
+ var WWW_URL = process.env.NEXT_PUBLIC_WWW_URL || "https://imajin.ai";
1054
+ function BuildInfo() {
1055
+ const version = process.env.NEXT_PUBLIC_VERSION || "dev";
1056
+ const hash = process.env.NEXT_PUBLIC_BUILD_HASH || "local";
1057
+ const commitCount = process.env.NEXT_PUBLIC_COMMIT_COUNT || "";
1058
+ const isDev = version === "dev" || version.includes("dev");
1059
+ const display = commitCount ? `${version}+${commitCount}` : version;
1060
+ return /* @__PURE__ */ React.createElement(
1061
+ "a",
1062
+ {
1063
+ href: `${WWW_URL}/build`,
1064
+ className: `text-xs hover:underline ${isDev ? "text-yellow-600" : "text-gray-500"}`
1065
+ },
1066
+ "imajin ",
1067
+ display,
1068
+ " \xB7 build ",
1069
+ hash.slice(0, 7)
1070
+ );
1071
+ }
1072
+
1073
+ // src/footer.tsx
1074
+ function getServiceFromPathname(pathname) {
1075
+ const segment = pathname.split("/").filter(Boolean)[0];
1076
+ if (!segment) return "landing";
1077
+ return segment;
1078
+ }
1079
+ function ImajinFooter({ className }) {
1080
+ const [subscribeHref, setSubscribeHref] = useState7("/subscribe");
1081
+ useEffect8(() => {
1082
+ const service = getServiceFromPathname(window.location.pathname);
1083
+ setSubscribeHref(`/subscribe?from=${service}`);
1084
+ }, []);
1085
+ return /* @__PURE__ */ React.createElement("div", { className: `flex flex-col items-center gap-2 ${className || ""}` }, /* @__PURE__ */ React.createElement("p", { className: "text-center text-sm text-gray-500" }, "Part of the", " ", /* @__PURE__ */ React.createElement("a", { href: "https://imajin.ai", className: "text-orange-500 hover:underline" }, "Imajin"), " ", "sovereign network"), /* @__PURE__ */ React.createElement("p", { className: "text-center text-sm text-gray-500" }, /* @__PURE__ */ React.createElement("a", { href: "https://app.dfos.com/j/c3rff6e96e4ca9hncc43en", className: "hover:underline" }, "Community"), " \xB7 ", /* @__PURE__ */ React.createElement("a", { href: "https://github.com/ima-jin/imajin-ai", className: "hover:underline" }, "GitHub"), " \xB7 ", /* @__PURE__ */ React.createElement("a", { href: "/privacy", className: "hover:underline" }, "Privacy"), " \xB7 ", /* @__PURE__ */ React.createElement("a", { href: subscribeHref, className: "hover:underline" }, "Subscribe")), /* @__PURE__ */ React.createElement(BuildInfo, null));
1086
+ }
1087
+
1088
+ // src/brand.ts
1089
+ var BRAND = {
1090
+ name: "Imajin",
1091
+ nameJp: "\u4ECA\u4EBA",
1092
+ pronunciation: "eema-gin",
1093
+ /** Primary tagline — used on homepage hero */
1094
+ tagline: "The internet that pays you back",
1095
+ /** Footer line — used across all services */
1096
+ footer: "Part of the Imajin sovereign network",
1097
+ /** Short sovereign message — used in emails, receipts */
1098
+ sovereign: "No platform. No middleman. Yours.",
1099
+ /** Links */
1100
+ url: "https://imajin.ai",
1101
+ community: "https://app.dfos.com/j/c3rff6e96e4ca9hncc43en",
1102
+ github: "https://github.com/ima-jin/imajin-ai"
1103
+ };
1104
+
1105
+ // src/MarkdownEditor.tsx
1106
+ import React9 from "react";
1107
+ import {
1108
+ MDXEditor,
1109
+ headingsPlugin,
1110
+ listsPlugin,
1111
+ quotePlugin,
1112
+ linkPlugin,
1113
+ linkDialogPlugin,
1114
+ toolbarPlugin,
1115
+ markdownShortcutPlugin,
1116
+ BoldItalicUnderlineToggles,
1117
+ BlockTypeSelect,
1118
+ CreateLink,
1119
+ ListsToggle,
1120
+ Separator
1121
+ } from "@mdxeditor/editor";
1122
+ import "@mdxeditor/editor/style.css";
1123
+ function MarkdownEditor({ value, onChange, placeholder, maxLength }) {
1124
+ const handleChange = (md) => {
1125
+ if (maxLength !== void 0 && md.length > maxLength) return;
1126
+ onChange(md);
1127
+ };
1128
+ return /* @__PURE__ */ React9.createElement(
1129
+ "div",
1130
+ {
1131
+ className: "rounded-lg border border-gray-600 overflow-hidden",
1132
+ style: {
1133
+ "--baseBg": "#1a1a1a",
1134
+ "--basePageBg": "#1a1a1a",
1135
+ "--baseTextContrast": "#e5e7eb",
1136
+ "--baseText": "#d1d5db",
1137
+ "--baseBorder": "#374151",
1138
+ "--accentBase": "#f97316",
1139
+ "--accentBgHover": "rgba(249,115,22,0.15)",
1140
+ "--accentTextContrast": "#f97316"
1141
+ }
1142
+ },
1143
+ /* @__PURE__ */ React9.createElement(
1144
+ MDXEditor,
1145
+ {
1146
+ markdown: value,
1147
+ onChange: handleChange,
1148
+ placeholder,
1149
+ className: "dark-theme dark-editor",
1150
+ plugins: [
1151
+ headingsPlugin({ allowedHeadingLevels: [1, 2, 3] }),
1152
+ listsPlugin(),
1153
+ quotePlugin(),
1154
+ linkPlugin(),
1155
+ linkDialogPlugin(),
1156
+ markdownShortcutPlugin(),
1157
+ toolbarPlugin({
1158
+ toolbarContents: () => /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement(BlockTypeSelect, null), /* @__PURE__ */ React9.createElement(Separator, null), /* @__PURE__ */ React9.createElement(BoldItalicUnderlineToggles, { options: ["Bold", "Italic"] }), /* @__PURE__ */ React9.createElement(Separator, null), /* @__PURE__ */ React9.createElement(CreateLink, null), /* @__PURE__ */ React9.createElement(Separator, null), /* @__PURE__ */ React9.createElement(ListsToggle, null))
1159
+ })
1160
+ ]
1161
+ }
1162
+ )
1163
+ );
1164
+ }
1165
+
1166
+ // src/MarkdownContent.tsx
1167
+ import React10 from "react";
1168
+ import ReactMarkdown from "react-markdown";
1169
+ function MarkdownContent({ content }) {
1170
+ return /* @__PURE__ */ React10.createElement("div", { className: "prose dark:prose-invert max-w-none" }, /* @__PURE__ */ React10.createElement(
1171
+ ReactMarkdown,
1172
+ {
1173
+ components: {
1174
+ h1: ({ children }) => /* @__PURE__ */ React10.createElement("h1", { className: "text-2xl font-bold mb-4 text-gray-900 dark:text-white" }, children),
1175
+ h2: ({ children }) => /* @__PURE__ */ React10.createElement("h2", { className: "text-xl font-bold mb-3 text-gray-900 dark:text-white" }, children),
1176
+ h3: ({ children }) => /* @__PURE__ */ React10.createElement("h3", { className: "text-lg font-semibold mb-2 text-gray-900 dark:text-white" }, children),
1177
+ p: ({ children }) => /* @__PURE__ */ React10.createElement("p", { className: "mb-4 leading-relaxed text-gray-700 dark:text-gray-200" }, children),
1178
+ a: ({ href, children }) => /* @__PURE__ */ React10.createElement(
1179
+ "a",
1180
+ {
1181
+ href,
1182
+ className: "text-orange-500 dark:text-orange-400 hover:text-orange-600 dark:hover:text-orange-300 underline",
1183
+ target: "_blank",
1184
+ rel: "noopener noreferrer"
1185
+ },
1186
+ children
1187
+ ),
1188
+ ul: ({ children }) => /* @__PURE__ */ React10.createElement("ul", { className: "list-disc list-inside mb-4 space-y-1 text-gray-700 dark:text-gray-200" }, children),
1189
+ ol: ({ children }) => /* @__PURE__ */ React10.createElement("ol", { className: "list-decimal list-inside mb-4 space-y-1 text-gray-700 dark:text-gray-200" }, children),
1190
+ li: ({ children }) => /* @__PURE__ */ React10.createElement("li", { className: "text-gray-700 dark:text-gray-200" }, children),
1191
+ blockquote: ({ children }) => /* @__PURE__ */ React10.createElement("blockquote", { className: "border-l-4 border-orange-500 pl-4 my-4 italic text-gray-500 dark:text-gray-400" }, children),
1192
+ strong: ({ children }) => /* @__PURE__ */ React10.createElement("strong", { className: "font-bold text-gray-900 dark:text-white" }, children),
1193
+ em: ({ children }) => /* @__PURE__ */ React10.createElement("em", { className: "italic text-gray-700 dark:text-gray-200" }, children),
1194
+ code: ({ children }) => /* @__PURE__ */ React10.createElement("code", { className: "bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded text-sm font-mono text-orange-600 dark:text-orange-300" }, children)
1195
+ }
1196
+ },
1197
+ content
1198
+ ));
1199
+ }
1200
+
1201
+ // src/connection-picker.tsx
1202
+ import { useState as useState8, useEffect as useEffect9 } from "react";
1203
+ function ConnectionPicker({
1204
+ connectionsUrl,
1205
+ excludeDids = [],
1206
+ onSelect,
1207
+ placeholder = "Search connections...",
1208
+ disabled = false
1209
+ }) {
1210
+ const [connections, setConnections] = useState8([]);
1211
+ const [loading, setLoading] = useState8(true);
1212
+ const [error, setError] = useState8(null);
1213
+ const [search, setSearch] = useState8("");
1214
+ useEffect9(() => {
1215
+ fetch(connectionsUrl).then((r) => r.json()).then((data) => {
1216
+ setConnections(data.connections || []);
1217
+ setLoading(false);
1218
+ }).catch(() => {
1219
+ setError("Failed to load connections");
1220
+ setLoading(false);
1221
+ });
1222
+ }, [connectionsUrl]);
1223
+ const excludeSet = new Set(excludeDids);
1224
+ const available = connections.filter((c) => !excludeSet.has(c.did));
1225
+ const filtered = search ? available.filter(
1226
+ (c) => (c.handle || "").toLowerCase().includes(search.toLowerCase()) || (c.name || "").toLowerCase().includes(search.toLowerCase())
1227
+ ) : available;
1228
+ return /* @__PURE__ */ React.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React.createElement(
1229
+ "input",
1230
+ {
1231
+ type: "text",
1232
+ value: search,
1233
+ onChange: (e) => setSearch(e.target.value),
1234
+ placeholder,
1235
+ disabled: disabled || loading,
1236
+ className: "w-full px-3 py-2 text-sm border border-gray-600 rounded-lg bg-gray-900 text-white placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-orange-500 disabled:opacity-50"
1237
+ }
1238
+ ), loading ? /* @__PURE__ */ React.createElement("p", { className: "text-sm text-gray-500 px-1" }, "Loading connections...") : error ? /* @__PURE__ */ React.createElement("p", { className: "text-sm text-red-400 px-1" }, error) : filtered.length === 0 ? /* @__PURE__ */ React.createElement("p", { className: "text-sm text-gray-500 px-1" }, available.length === 0 ? "No connections available." : "No matching connections.") : /* @__PURE__ */ React.createElement("div", { className: "space-y-0 max-h-48 overflow-y-auto rounded-lg border border-gray-700 bg-gray-900" }, filtered.map((conn) => /* @__PURE__ */ React.createElement(
1239
+ "button",
1240
+ {
1241
+ key: conn.did,
1242
+ onClick: () => {
1243
+ onSelect(conn);
1244
+ setSearch("");
1245
+ },
1246
+ disabled,
1247
+ className: "w-full flex items-center gap-3 px-3 py-2 hover:bg-gray-800 transition text-left disabled:opacity-50"
1248
+ },
1249
+ conn.avatar ? /* @__PURE__ */ React.createElement(
1250
+ "img",
1251
+ {
1252
+ src: conn.avatar,
1253
+ alt: conn.name || conn.handle || conn.did,
1254
+ className: "w-8 h-8 rounded-full object-cover flex-shrink-0"
1255
+ }
1256
+ ) : /* @__PURE__ */ React.createElement("div", { className: "w-8 h-8 rounded-full bg-gray-700 flex items-center justify-center text-gray-400 text-sm font-semibold flex-shrink-0" }, (conn.name || conn.handle || conn.did).charAt(0).toUpperCase()),
1257
+ /* @__PURE__ */ React.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-medium text-white truncate" }, conn.name || (conn.handle ? `@${conn.handle}` : conn.did.slice(0, 20) + "...")), conn.handle && conn.name && /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-400 truncate" }, "@", conn.handle))
1258
+ ))));
1259
+ }
1260
+
1261
+ // src/PayoutSetupBanner.tsx
1262
+ import { useState as useState9, useEffect as useEffect10 } from "react";
1263
+ function PayoutSetupBanner({
1264
+ did,
1265
+ payUrl,
1266
+ message = "Set up payouts to receive funds from events, tips, and sales",
1267
+ viewerDid,
1268
+ ownerHandle,
1269
+ ownerName,
1270
+ ownerRole = "Event creator"
1271
+ }) {
1272
+ const [shouldShow, setShouldShow] = useState9(false);
1273
+ const [loading, setLoading] = useState9(true);
1274
+ const isThirdParty = !!viewerDid && viewerDid !== did;
1275
+ const ownerLabel = ownerHandle ? `@${ownerHandle}` : ownerName || "they";
1276
+ useEffect10(() => {
1277
+ const dismissedKey = `payout-banner-dismissed-${did}`;
1278
+ if (typeof window !== "undefined" && localStorage.getItem(dismissedKey) === "true") {
1279
+ setLoading(false);
1280
+ return;
1281
+ }
1282
+ const check = async () => {
1283
+ try {
1284
+ const res = await fetch(`${payUrl}/api/connect/status?did=${encodeURIComponent(did)}`, {
1285
+ credentials: "include"
1286
+ });
1287
+ if (res.status === 404 || res.status === 403) {
1288
+ setShouldShow(true);
1289
+ } else if (res.ok) {
1290
+ const data = await res.json();
1291
+ setShouldShow(!data.onboardingComplete);
1292
+ }
1293
+ } catch {
1294
+ } finally {
1295
+ setLoading(false);
1296
+ }
1297
+ };
1298
+ if (did) check();
1299
+ else setLoading(false);
1300
+ }, [did, payUrl]);
1301
+ const handleDismiss = () => {
1302
+ localStorage.setItem(`payout-banner-dismissed-${did}`, "true");
1303
+ setShouldShow(false);
1304
+ };
1305
+ if (loading || !shouldShow) return null;
1306
+ const displayMessage = isThirdParty ? `${ownerRole} ${ownerLabel} needs to connect Stripe before tickets can be sold.` : message;
1307
+ return /* @__PURE__ */ React.createElement("div", { className: "bg-orange-900/20 border border-orange-800/50 rounded-xl p-4 mb-6" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between gap-4" }, /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 min-w-0" }, /* @__PURE__ */ React.createElement("div", { className: "w-2 h-2 bg-orange-500 rounded-full animate-pulse shrink-0" }), /* @__PURE__ */ React.createElement("p", { className: "text-sm text-orange-200" }, displayMessage)), /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-3 shrink-0" }, !isThirdParty && /* @__PURE__ */ React.createElement(
1308
+ "a",
1309
+ {
1310
+ href: `${payUrl}/payouts`,
1311
+ className: "text-sm text-orange-400 hover:text-orange-300 font-medium transition-colors whitespace-nowrap"
1312
+ },
1313
+ "Set up payouts \u2192"
1314
+ ), /* @__PURE__ */ React.createElement(
1315
+ "button",
1316
+ {
1317
+ onClick: handleDismiss,
1318
+ className: "text-orange-400/60 hover:text-orange-400 text-lg leading-none transition-colors",
1319
+ "aria-label": "Dismiss"
1320
+ },
1321
+ "\xD7"
1322
+ ))));
1323
+ }
1324
+
1325
+ // src/action-sheet.tsx
1326
+ import React11, { useEffect as useEffect11 } from "react";
1327
+ function Reactions({ emojis, onSelect }) {
1328
+ return /* @__PURE__ */ React11.createElement("div", { className: "flex justify-around px-4 py-3 border-b border-gray-700" }, emojis.map((emoji) => /* @__PURE__ */ React11.createElement(
1329
+ "button",
1330
+ {
1331
+ key: emoji,
1332
+ onClick: () => onSelect(emoji),
1333
+ className: "w-11 h-11 flex items-center justify-center text-2xl hover:bg-gray-700 rounded-full transition",
1334
+ "aria-label": emoji
1335
+ },
1336
+ emoji
1337
+ )));
1338
+ }
1339
+ function Actions({ children }) {
1340
+ return /* @__PURE__ */ React11.createElement("div", { className: "border-b border-gray-700 last:border-b-0" }, children);
1341
+ }
1342
+ function Action({ icon, label, onPress, variant = "default" }) {
1343
+ return /* @__PURE__ */ React11.createElement(
1344
+ "button",
1345
+ {
1346
+ onClick: onPress,
1347
+ className: `w-full flex items-center gap-3 px-5 py-3.5 text-left text-sm transition hover:bg-gray-800 ${variant === "danger" ? "text-red-400" : "text-white"}`
1348
+ },
1349
+ icon && /* @__PURE__ */ React11.createElement("span", { className: "text-lg w-6 text-center" }, icon),
1350
+ /* @__PURE__ */ React11.createElement("span", null, label)
1351
+ );
1352
+ }
1353
+ function ActionSheet({ open, onClose, title, children }) {
1354
+ useEffect11(() => {
1355
+ if (!open) return;
1356
+ const handleKeyDown = (e) => {
1357
+ if (e.key === "Escape") onClose();
1358
+ };
1359
+ document.addEventListener("keydown", handleKeyDown);
1360
+ return () => document.removeEventListener("keydown", handleKeyDown);
1361
+ }, [open, onClose]);
1362
+ if (!open) return null;
1363
+ return /* @__PURE__ */ React11.createElement("div", { className: "fixed inset-0 z-[9998] flex items-end" }, /* @__PURE__ */ React11.createElement("style", null, `
1364
+ @keyframes actionSheetSlideUp {
1365
+ from { transform: translateY(100%); }
1366
+ to { transform: translateY(0); }
1367
+ }
1368
+ `), /* @__PURE__ */ React11.createElement(
1369
+ "div",
1370
+ {
1371
+ className: "absolute inset-0 bg-black/60",
1372
+ onClick: onClose,
1373
+ "aria-hidden": "true"
1374
+ }
1375
+ ), /* @__PURE__ */ React11.createElement(
1376
+ "div",
1377
+ {
1378
+ role: "dialog",
1379
+ "aria-modal": "true",
1380
+ "aria-label": title ?? "Actions",
1381
+ className: "relative w-full bg-gray-900 rounded-t-2xl border-t border-gray-700 max-h-[70vh] overflow-y-auto",
1382
+ style: { animation: "actionSheetSlideUp 0.25s ease-out" }
1383
+ },
1384
+ /* @__PURE__ */ React11.createElement("div", { className: "flex justify-center pt-3 pb-1" }, /* @__PURE__ */ React11.createElement("div", { className: "w-10 h-1 rounded-full bg-gray-600" })),
1385
+ title && /* @__PURE__ */ React11.createElement("div", { className: "px-5 py-2 border-b border-gray-700" }, /* @__PURE__ */ React11.createElement("p", { className: "text-sm font-medium text-gray-400 text-center" }, title)),
1386
+ children
1387
+ ));
1388
+ }
1389
+ ActionSheet.Reactions = Reactions;
1390
+ ActionSheet.Actions = Actions;
1391
+ ActionSheet.Action = Action;
1392
+
1393
+ // src/app-shell.tsx
1394
+ import React12 from "react";
1395
+ var AppShell = React12.forwardRef(
1396
+ function AppShell2({ className = "", children, ...props }, ref) {
1397
+ return /* @__PURE__ */ React12.createElement(
1398
+ "div",
1399
+ {
1400
+ ref,
1401
+ className: `h-dvh flex flex-col overflow-hidden ${className}`,
1402
+ ...props
1403
+ },
1404
+ children
1405
+ );
1406
+ }
1407
+ );
1408
+ var AppShellHeader = React12.forwardRef(
1409
+ function AppShellHeader2({ className = "", children, ...props }, ref) {
1410
+ return /* @__PURE__ */ React12.createElement("div", { ref, className: `shrink-0 ${className}`, ...props }, children);
1411
+ }
1412
+ );
1413
+ var AppShellBody = React12.forwardRef(
1414
+ function AppShellBody2({ className = "", children, ...props }, ref) {
1415
+ return /* @__PURE__ */ React12.createElement(
1416
+ "div",
1417
+ {
1418
+ ref,
1419
+ className: `flex-1 min-h-0 overflow-auto ${className}`,
1420
+ ...props
1421
+ },
1422
+ children
1423
+ );
1424
+ }
1425
+ );
1426
+ var AppShellFooter = React12.forwardRef(
1427
+ function AppShellFooter2({ className = "", children, ...props }, ref) {
1428
+ return /* @__PURE__ */ React12.createElement("div", { ref, className: `shrink-0 ${className}`, ...props }, children);
1429
+ }
1430
+ );
1431
+ var AppShellSplit = React12.forwardRef(
1432
+ function AppShellSplit2({ className = "", children, ...props }, ref) {
1433
+ return /* @__PURE__ */ React12.createElement(
1434
+ "div",
1435
+ {
1436
+ ref,
1437
+ className: `flex-1 min-h-0 flex flex-row overflow-hidden ${className}`,
1438
+ ...props
1439
+ },
1440
+ children
1441
+ );
1442
+ }
1443
+ );
1444
+ var AppShellPane = React12.forwardRef(
1445
+ function AppShellPane2({ width, className = "", style, children, ...props }, ref) {
1446
+ const widthStyle = width != null ? { width, ...style } : style;
1447
+ return /* @__PURE__ */ React12.createElement(
1448
+ "div",
1449
+ {
1450
+ ref,
1451
+ className: `flex flex-col min-h-0 overflow-hidden ${className}`,
1452
+ style: widthStyle,
1453
+ ...props
1454
+ },
1455
+ children
1456
+ );
1457
+ }
1458
+ );
1459
+ AppShell.displayName = "AppShell";
1460
+ AppShellHeader.displayName = "AppShell.Header";
1461
+ AppShellBody.displayName = "AppShell.Body";
1462
+ AppShellFooter.displayName = "AppShell.Footer";
1463
+ AppShellSplit.displayName = "AppShell.Split";
1464
+ AppShellPane.displayName = "AppShell.Split.Pane";
1465
+ AppShell.Header = AppShellHeader;
1466
+ AppShell.Body = AppShellBody;
1467
+ AppShell.Footer = AppShellFooter;
1468
+ AppShell.Split = AppShellSplit;
1469
+ AppShellSplit.Pane = AppShellPane;
1470
+
1471
+ // src/DidShareListEditor.tsx
1472
+ import React13, { useEffect as useEffect12, useMemo, useRef as useRef5, useState as useState10 } from "react";
1473
+ var ROLE_OPTIONS = [
1474
+ "creator",
1475
+ "collaborator",
1476
+ "producer",
1477
+ "performer",
1478
+ "platform",
1479
+ "venue",
1480
+ "distributor",
1481
+ "label",
1482
+ "other"
1483
+ ];
1484
+ var SUM_TOLERANCE = 1e-6;
1485
+ function ResolvedDidChip({
1486
+ did,
1487
+ profile,
1488
+ onClear,
1489
+ readOnly
1490
+ }) {
1491
+ const displayName = (profile == null ? void 0 : profile.name) || did.slice(0, 16) + "\u2026";
1492
+ const handle = profile == null ? void 0 : profile.handle;
1493
+ const avatar = profile == null ? void 0 : profile.avatar;
1494
+ const handleCopy = () => {
1495
+ navigator.clipboard.writeText(did).catch(() => {
1496
+ });
1497
+ };
1498
+ return /* @__PURE__ */ React13.createElement(
1499
+ "div",
1500
+ {
1501
+ className: "flex-1 flex items-center gap-2 bg-[#1a1a1a] border border-gray-700 rounded px-2 py-1 min-w-0 cursor-pointer hover:border-gray-600 transition",
1502
+ title: did,
1503
+ onClick: handleCopy
1504
+ },
1505
+ avatar ? /* @__PURE__ */ React13.createElement(
1506
+ "img",
1507
+ {
1508
+ src: avatar,
1509
+ alt: "",
1510
+ className: "w-5 h-5 rounded-full object-cover flex-shrink-0"
1511
+ }
1512
+ ) : /* @__PURE__ */ React13.createElement("div", { className: "w-5 h-5 rounded-full bg-gray-700 flex items-center justify-center text-gray-400 text-[10px] font-semibold flex-shrink-0" }, displayName.charAt(0).toUpperCase()),
1513
+ /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-gray-200 truncate" }, displayName),
1514
+ handle && /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-gray-500 truncate" }, "@", handle),
1515
+ !readOnly && /* @__PURE__ */ React13.createElement(
1516
+ "button",
1517
+ {
1518
+ onClick: (e) => {
1519
+ e.stopPropagation();
1520
+ onClear();
1521
+ },
1522
+ className: "ml-auto text-gray-600 hover:text-red-400 transition text-xs px-1",
1523
+ title: "Clear",
1524
+ type: "button"
1525
+ },
1526
+ "\u2715"
1527
+ )
1528
+ );
1529
+ }
1530
+ function InlineDidPicker({
1531
+ connectionsUrl,
1532
+ onSelect,
1533
+ readOnly
1534
+ }) {
1535
+ const [query, setQuery] = useState10("");
1536
+ const [open, setOpen] = useState10(false);
1537
+ const [connections, setConnections] = useState10([]);
1538
+ const [loading, setLoading] = useState10(false);
1539
+ const wrapperRef = useRef5(null);
1540
+ const inputRef = useRef5(null);
1541
+ useEffect12(() => {
1542
+ if (!connectionsUrl || connections.length > 0) return;
1543
+ setLoading(true);
1544
+ fetch(connectionsUrl).then((r) => r.json()).then((data) => {
1545
+ const list = (data.connections || []).map((c) => ({
1546
+ did: String(c.did || ""),
1547
+ name: c.name ? String(c.name) : null,
1548
+ handle: c.handle ? String(c.handle) : null,
1549
+ avatar: c.avatar ? String(c.avatar) : null
1550
+ }));
1551
+ setConnections(list);
1552
+ }).catch(() => {
1553
+ setConnections([]);
1554
+ }).finally(() => setLoading(false));
1555
+ }, [connectionsUrl, connections.length]);
1556
+ useEffect12(() => {
1557
+ function handleClick(e) {
1558
+ if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
1559
+ setOpen(false);
1560
+ }
1561
+ }
1562
+ document.addEventListener("mousedown", handleClick);
1563
+ return () => document.removeEventListener("mousedown", handleClick);
1564
+ }, []);
1565
+ const isRawDid = query.trim().startsWith("did:");
1566
+ const filtered = useMemo(() => {
1567
+ const q = query.trim().toLowerCase();
1568
+ if (!q) return connections;
1569
+ return connections.filter(
1570
+ (c) => (c.handle || "").toLowerCase().includes(q) || (c.name || "").toLowerCase().includes(q) || c.did.toLowerCase().includes(q)
1571
+ );
1572
+ }, [query, connections]);
1573
+ const handleSelect = (did) => {
1574
+ onSelect(did);
1575
+ setQuery("");
1576
+ setOpen(false);
1577
+ };
1578
+ const handleKeyDown = (e) => {
1579
+ if (e.key === "Enter") {
1580
+ e.preventDefault();
1581
+ const trimmed = query.trim();
1582
+ if (isRawDid) {
1583
+ handleSelect(trimmed);
1584
+ } else if (filtered.length > 0) {
1585
+ handleSelect(filtered[0].did);
1586
+ }
1587
+ } else if (e.key === "Escape") {
1588
+ setOpen(false);
1589
+ }
1590
+ };
1591
+ const showDropdown = open && (filtered.length > 0 || isRawDid || loading);
1592
+ return /* @__PURE__ */ React13.createElement("div", { ref: wrapperRef, className: "flex-1 relative" }, /* @__PURE__ */ React13.createElement(
1593
+ "input",
1594
+ {
1595
+ ref: inputRef,
1596
+ type: "text",
1597
+ value: query,
1598
+ onChange: (e) => {
1599
+ setQuery(e.target.value);
1600
+ setOpen(true);
1601
+ },
1602
+ onFocus: () => setOpen(true),
1603
+ onKeyDown: handleKeyDown,
1604
+ placeholder: connectionsUrl ? "Search connections or paste DID\u2026" : "did:key:...",
1605
+ readOnly,
1606
+ className: "w-full bg-[#1a1a1a] border border-gray-700 rounded px-2 py-1 text-xs text-gray-200 placeholder-gray-600 focus:outline-none focus:border-orange-500 read-only:opacity-60"
1607
+ }
1608
+ ), showDropdown && /* @__PURE__ */ React13.createElement("div", { className: "absolute z-10 top-full left-0 right-0 mt-1 bg-[#1a1a1a] border border-gray-700 rounded max-h-36 overflow-y-auto shadow-lg" }, loading ? /* @__PURE__ */ React13.createElement("div", { className: "px-3 py-2 text-xs text-gray-500" }, "Loading\u2026") : /* @__PURE__ */ React13.createElement(React13.Fragment, null, isRawDid && /* @__PURE__ */ React13.createElement(
1609
+ "button",
1610
+ {
1611
+ onClick: () => handleSelect(query.trim()),
1612
+ className: "w-full flex items-center gap-2 px-3 py-2 hover:bg-[#252525] transition text-left",
1613
+ type: "button"
1614
+ },
1615
+ /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-orange-400" }, "Use raw DID:"),
1616
+ /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-gray-300 truncate" }, query.trim())
1617
+ ), filtered.map((conn) => /* @__PURE__ */ React13.createElement(
1618
+ "button",
1619
+ {
1620
+ key: conn.did,
1621
+ onClick: () => handleSelect(conn.did),
1622
+ className: "w-full flex items-center gap-2 px-3 py-2 hover:bg-[#252525] transition text-left",
1623
+ type: "button"
1624
+ },
1625
+ conn.avatar ? /* @__PURE__ */ React13.createElement(
1626
+ "img",
1627
+ {
1628
+ src: conn.avatar,
1629
+ alt: "",
1630
+ className: "w-5 h-5 rounded-full object-cover flex-shrink-0"
1631
+ }
1632
+ ) : /* @__PURE__ */ React13.createElement("div", { className: "w-5 h-5 rounded-full bg-gray-700 flex items-center justify-center text-gray-400 text-[10px] font-semibold flex-shrink-0" }, (conn.name || conn.handle || conn.did).charAt(0).toUpperCase()),
1633
+ /* @__PURE__ */ React13.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-gray-200 truncate" }, conn.name || (conn.handle ? `@${conn.handle}` : conn.did.slice(0, 20) + "\u2026")), conn.handle && conn.name && /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-gray-500 ml-1" }, "@", conn.handle))
1634
+ )), filtered.length === 0 && !isRawDid && /* @__PURE__ */ React13.createElement("div", { className: "px-3 py-2 text-xs text-gray-500" }, connections.length === 0 ? "No connections available." : "No matches."))));
1635
+ }
1636
+ function DidShareListEditor({
1637
+ value,
1638
+ onChange,
1639
+ readOnly = false,
1640
+ className = "",
1641
+ defaultDid,
1642
+ showFixed = false,
1643
+ connectionsUrl,
1644
+ resolveProfile
1645
+ }) {
1646
+ const [resolvedCache, setResolvedCache] = useState10({});
1647
+ const totalShare = useMemo(
1648
+ () => value.reduce((sum, e) => sum + e.share, 0),
1649
+ [value]
1650
+ );
1651
+ const isValid = Math.abs(totalShare - 1) <= SUM_TOLERANCE;
1652
+ const isOver = totalShare > 1 + SUM_TOLERANCE;
1653
+ const update = (i, patch) => {
1654
+ const next = value.map((e, idx) => idx === i ? { ...e, ...patch } : e);
1655
+ onChange(next);
1656
+ };
1657
+ const remove = (i) => {
1658
+ onChange(value.filter((_e, idx) => idx !== i));
1659
+ };
1660
+ const add = () => {
1661
+ onChange([
1662
+ ...value,
1663
+ {
1664
+ did: defaultDid ?? "",
1665
+ role: "collaborator",
1666
+ share: 0
1667
+ }
1668
+ ]);
1669
+ };
1670
+ useEffect12(() => {
1671
+ if (!resolveProfile) return;
1672
+ const dids = value.map((e) => e.did).filter(Boolean);
1673
+ const uniqueDids = [...new Set(dids)].filter((did) => !(did in resolvedCache));
1674
+ if (uniqueDids.length === 0) return;
1675
+ Promise.all(
1676
+ uniqueDids.map(async (did) => {
1677
+ try {
1678
+ const profile = await resolveProfile(did);
1679
+ return { did, profile };
1680
+ } catch {
1681
+ return { did, profile: null };
1682
+ }
1683
+ })
1684
+ ).then((results) => {
1685
+ setResolvedCache((prev) => {
1686
+ const next = { ...prev };
1687
+ for (const { did, profile } of results) {
1688
+ next[did] = profile;
1689
+ }
1690
+ return next;
1691
+ });
1692
+ });
1693
+ }, [resolveProfile, value.map((e) => e.did).join(",")]);
1694
+ return /* @__PURE__ */ React13.createElement("div", { className: `space-y-3 ${className}` }, value.map((entry, i) => /* @__PURE__ */ React13.createElement("div", { key: i, className: "bg-[#252525] rounded-lg p-3 space-y-2" }, /* @__PURE__ */ React13.createElement("div", { className: "flex items-center gap-2" }, entry.did ? /* @__PURE__ */ React13.createElement(
1695
+ ResolvedDidChip,
1696
+ {
1697
+ did: entry.did,
1698
+ profile: resolvedCache[entry.did] ?? null,
1699
+ onClear: () => update(i, { did: "" }),
1700
+ readOnly
1701
+ }
1702
+ ) : /* @__PURE__ */ React13.createElement(
1703
+ InlineDidPicker,
1704
+ {
1705
+ connectionsUrl,
1706
+ onSelect: (did) => update(i, { did }),
1707
+ readOnly
1708
+ }
1709
+ ), /* @__PURE__ */ React13.createElement(
1710
+ "select",
1711
+ {
1712
+ value: entry.role,
1713
+ onChange: (e) => update(i, { role: e.target.value }),
1714
+ disabled: readOnly,
1715
+ className: "bg-[#1a1a1a] border border-gray-700 rounded px-2 py-1 text-xs text-gray-200 focus:outline-none focus:border-orange-500 disabled:opacity-60"
1716
+ },
1717
+ ROLE_OPTIONS.map((r) => /* @__PURE__ */ React13.createElement("option", { key: r, value: r }, r))
1718
+ ), !readOnly && /* @__PURE__ */ React13.createElement(
1719
+ "button",
1720
+ {
1721
+ onClick: () => remove(i),
1722
+ className: "text-gray-600 hover:text-red-400 transition text-sm px-1",
1723
+ title: "Remove",
1724
+ type: "button"
1725
+ },
1726
+ "\u2715"
1727
+ )), /* @__PURE__ */ React13.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React13.createElement(
1728
+ "input",
1729
+ {
1730
+ type: "range",
1731
+ min: 0,
1732
+ max: 100,
1733
+ step: 0.5,
1734
+ value: Math.round(entry.share * 1e3) / 10,
1735
+ onChange: (e) => update(i, { share: parseFloat(e.target.value) / 100 }),
1736
+ disabled: readOnly,
1737
+ className: "flex-1 accent-orange-500 disabled:opacity-60"
1738
+ }
1739
+ ), /* @__PURE__ */ React13.createElement(
1740
+ "input",
1741
+ {
1742
+ type: "number",
1743
+ min: 0,
1744
+ max: 100,
1745
+ step: 0.5,
1746
+ value: (entry.share * 100).toFixed(1),
1747
+ onChange: (e) => update(i, { share: parseFloat(e.target.value) / 100 }),
1748
+ readOnly,
1749
+ className: "w-16 bg-[#1a1a1a] border border-gray-700 rounded px-2 py-1 text-xs text-gray-200 focus:outline-none focus:border-orange-500 text-right read-only:opacity-60"
1750
+ }
1751
+ ), /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-gray-500" }, "%")), /* @__PURE__ */ React13.createElement(
1752
+ "input",
1753
+ {
1754
+ type: "text",
1755
+ value: entry.name ?? "",
1756
+ onChange: (e) => update(i, { name: e.target.value || void 0 }),
1757
+ placeholder: "Name (optional)",
1758
+ readOnly,
1759
+ className: "w-full bg-[#1a1a1a] border border-gray-700 rounded px-2 py-1 text-xs text-gray-500 placeholder-gray-700 focus:outline-none focus:border-orange-500 read-only:opacity-60"
1760
+ }
1761
+ ))), !readOnly && /* @__PURE__ */ React13.createElement(
1762
+ "button",
1763
+ {
1764
+ onClick: add,
1765
+ type: "button",
1766
+ className: "w-full py-1.5 rounded border border-dashed border-gray-700 text-xs text-gray-500 hover:border-orange-500 hover:text-orange-400 transition"
1767
+ },
1768
+ "+ Add contributor"
1769
+ ), /* @__PURE__ */ React13.createElement("div", { className: "flex items-center justify-between text-xs" }, /* @__PURE__ */ React13.createElement(
1770
+ "span",
1771
+ {
1772
+ className: isValid ? "text-gray-500" : isOver ? "text-red-400 font-medium" : "text-orange-400 font-medium"
1773
+ },
1774
+ "Total: ",
1775
+ (totalShare * 100).toFixed(1),
1776
+ "%",
1777
+ !isValid && /* @__PURE__ */ React13.createElement("span", { className: "ml-1" }, isOver ? "(must be \u2264 100%)" : "(must equal 100%)")
1778
+ ), /* @__PURE__ */ React13.createElement("span", { className: "text-gray-600" }, value.length, " ", value.length === 1 ? "entry" : "entries")));
1779
+ }
1780
+
1781
+ // src/MoneyInput.tsx
1782
+ import React14, { useMemo as useMemo2 } from "react";
1783
+ var DEFAULT_CURRENCIES = ["USD", "EUR", "GBP", "CAD", "MJNX"];
1784
+ var CURRENCY_SYMBOLS = {
1785
+ USD: "$",
1786
+ EUR: "\u20AC",
1787
+ GBP: "\xA3",
1788
+ CAD: "C$",
1789
+ MJNX: "\u24C2"
1790
+ };
1791
+ function formatCents(cents) {
1792
+ return (cents / 100).toFixed(2);
1793
+ }
1794
+ function parseDecimalInput(raw) {
1795
+ const cleaned = raw.replace(/,/g, "");
1796
+ const parsed = parseFloat(cleaned);
1797
+ if (Number.isNaN(parsed) || parsed < 0) return null;
1798
+ return Math.round(parsed * 100);
1799
+ }
1800
+ function MoneyInput({
1801
+ value,
1802
+ onChange,
1803
+ readOnly = false,
1804
+ className = "",
1805
+ currencies = DEFAULT_CURRENCIES
1806
+ }) {
1807
+ const symbol = value ? CURRENCY_SYMBOLS[value.currency] ?? value.currency : "$";
1808
+ const formattedPreview = useMemo2(() => {
1809
+ if (!value) return "";
1810
+ const amt = (value.amount / 100).toFixed(2);
1811
+ return `${symbol}${amt} ${value.currency}`;
1812
+ }, [value, symbol]);
1813
+ return /* @__PURE__ */ React14.createElement("div", { className: `space-y-1 ${className}` }, /* @__PURE__ */ React14.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React14.createElement("div", { className: "relative flex-1" }, /* @__PURE__ */ React14.createElement("span", { className: "absolute left-2 top-1/2 -translate-y-1/2 text-xs text-gray-500 pointer-events-none" }, symbol), /* @__PURE__ */ React14.createElement(
1814
+ "input",
1815
+ {
1816
+ type: "text",
1817
+ inputMode: "decimal",
1818
+ value: value ? formatCents(value.amount) : "",
1819
+ onChange: (e) => {
1820
+ const cents = parseDecimalInput(e.target.value);
1821
+ if (cents === null) {
1822
+ onChange(void 0);
1823
+ return;
1824
+ }
1825
+ onChange({
1826
+ amount: cents,
1827
+ currency: (value == null ? void 0 : value.currency) ?? currencies[0]
1828
+ });
1829
+ },
1830
+ placeholder: "0.00",
1831
+ readOnly,
1832
+ className: "w-full bg-[#1a1a1a] border border-gray-700 rounded pl-6 pr-2 py-1.5 text-xs text-gray-200 placeholder-gray-600 focus:outline-none focus:border-orange-500 read-only:opacity-60"
1833
+ }
1834
+ )), /* @__PURE__ */ React14.createElement(
1835
+ "select",
1836
+ {
1837
+ value: (value == null ? void 0 : value.currency) ?? currencies[0],
1838
+ onChange: (e) => {
1839
+ const currency = e.target.value;
1840
+ const amount = (value == null ? void 0 : value.amount) ?? 0;
1841
+ if (amount === 0 && !value) {
1842
+ onChange(void 0);
1843
+ } else {
1844
+ onChange({ amount, currency });
1845
+ }
1846
+ },
1847
+ disabled: readOnly,
1848
+ className: "bg-[#1a1a1a] border border-gray-700 rounded px-2 py-1.5 text-xs text-gray-200 focus:outline-none focus:border-orange-500 disabled:opacity-60"
1849
+ },
1850
+ currencies.map((c) => /* @__PURE__ */ React14.createElement("option", { key: c, value: c }, c))
1851
+ )), value && value.amount > 0 && /* @__PURE__ */ React14.createElement("p", { className: "text-[10px] text-gray-500" }, formattedPreview));
1852
+ }
1853
+
1854
+ // src/theme-init.ts
1855
+ var themeInitScript = `
1856
+ (function() {
1857
+ var theme = localStorage.getItem('theme');
1858
+ if (theme === 'light') {
1859
+ document.documentElement.classList.remove('dark');
1860
+ } else {
1861
+ document.documentElement.classList.add('dark');
1862
+ }
1863
+ })()
1864
+ `;
1865
+ export {
1866
+ ActionSheet,
1867
+ AppLauncher,
1868
+ AppShell,
1869
+ BRAND,
1870
+ BalanceBadge,
1871
+ BuildInfo,
1872
+ Button,
1873
+ ConnectionPicker,
1874
+ DidShareListEditor,
1875
+ ImajinFooter,
1876
+ MarkdownContent,
1877
+ MarkdownEditor,
1878
+ MoneyInput,
1879
+ NavBar,
1880
+ NotificationBell,
1881
+ NotificationProvider,
1882
+ PayoutSetupBanner,
1883
+ ToastProvider,
1884
+ getActingAs,
1885
+ getActingAsHeaders,
1886
+ setActingAs,
1887
+ themeInitScript,
1888
+ useIdentities,
1889
+ useNotifications,
1890
+ useToast
1891
+ };