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