ns-auth-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1234 @@
1
+ 'use strict';
2
+
3
+ var nosskeySdk = require('nosskey-sdk');
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
6
+ var reactZxing = require('react-zxing');
7
+ var zustand = require('zustand');
8
+
9
+ // src/services/auth.service.ts
10
+ var AuthService = class {
11
+ constructor(config = {}) {
12
+ this.manager = null;
13
+ this.config = {
14
+ rpId: config.rpId || (typeof window !== "undefined" ? window.location.hostname.replace(/^www\./, "") : "localhost"),
15
+ rpName: config.rpName || this.getDefaultRpName(),
16
+ storageKey: config.storageKey || "nsauth_keyinfo",
17
+ cacheTimeoutMs: config.cacheTimeoutMs || 60 * 1e3
18
+ };
19
+ }
20
+ getDefaultRpName() {
21
+ if (typeof window === "undefined") return "localhost";
22
+ const hostname = window.location.hostname;
23
+ if (hostname.includes("nosskey.app")) return "nosskey.app";
24
+ return hostname.replace(/^www\./, "");
25
+ }
26
+ /**
27
+ * Initialize the NosskeyManager instance
28
+ */
29
+ getManager() {
30
+ if (!this.manager) {
31
+ this.manager = new nosskeySdk.NosskeyManager({
32
+ cacheOptions: {
33
+ enabled: true,
34
+ timeoutMs: this.config.cacheTimeoutMs
35
+ },
36
+ storageOptions: {
37
+ enabled: true,
38
+ storageKey: this.config.storageKey
39
+ }
40
+ });
41
+ }
42
+ return this.manager;
43
+ }
44
+ /**
45
+ * Create a new passkey
46
+ * Uses platform authenticator only (Touch ID, Face ID, Windows Hello)
47
+ */
48
+ async createPasskey(username) {
49
+ const manager = this.getManager();
50
+ const rpId = this.config.rpId === "localhost" ? "localhost" : this.config.rpId;
51
+ const rpName = this.config.rpName;
52
+ const trimmedUsername = username?.trim();
53
+ const uniqueUsername = trimmedUsername ? trimmedUsername : `user-${Date.now()}@example.com`;
54
+ const options = {
55
+ rp: {
56
+ id: rpId,
57
+ name: rpName
58
+ },
59
+ user: {
60
+ name: uniqueUsername,
61
+ displayName: trimmedUsername || "User"
62
+ },
63
+ authenticatorSelection: {
64
+ authenticatorAttachment: "platform",
65
+ residentKey: "preferred",
66
+ userVerification: "preferred"
67
+ },
68
+ extensions: {
69
+ prf: {}
70
+ }
71
+ };
72
+ return await manager.createPasskey(options);
73
+ }
74
+ /**
75
+ * Create a new Nostr key from a credential ID
76
+ */
77
+ async createNostrKey(credentialId) {
78
+ const manager = this.getManager();
79
+ return await manager.createNostrKey(credentialId);
80
+ }
81
+ /**
82
+ * Get the current public key
83
+ */
84
+ async getPublicKey() {
85
+ const manager = this.getManager();
86
+ return await manager.getPublicKey();
87
+ }
88
+ /**
89
+ * Sign a Nostr event
90
+ */
91
+ async signEvent(event) {
92
+ const manager = this.getManager();
93
+ return await manager.signEvent(event);
94
+ }
95
+ /**
96
+ * Get current key info
97
+ */
98
+ getCurrentKeyInfo() {
99
+ const manager = this.getManager();
100
+ return manager.getCurrentKeyInfo();
101
+ }
102
+ /**
103
+ * Set current key info
104
+ */
105
+ setCurrentKeyInfo(keyInfo) {
106
+ const manager = this.getManager();
107
+ manager.setCurrentKeyInfo(keyInfo);
108
+ }
109
+ /**
110
+ * Check if key info exists
111
+ */
112
+ hasKeyInfo() {
113
+ const manager = this.getManager();
114
+ return manager.hasKeyInfo();
115
+ }
116
+ /**
117
+ * Clear stored key info
118
+ */
119
+ clearStoredKeyInfo() {
120
+ const manager = this.getManager();
121
+ manager.clearStoredKeyInfo();
122
+ }
123
+ /**
124
+ * Check if PRF is supported
125
+ */
126
+ async isPrfSupported() {
127
+ const { isPrfSupported } = await import('nosskey-sdk');
128
+ return await isPrfSupported();
129
+ }
130
+ };
131
+
132
+ // src/services/relay.service.ts
133
+ var RelayService = class {
134
+ constructor(config = {}) {
135
+ this.eventStore = null;
136
+ this.defaultRelays = ["wss://relay.damus.io"];
137
+ this.relayUrls = config.relayUrls || this.defaultRelays;
138
+ }
139
+ /**
140
+ * Initialize with applesauce EventStore
141
+ */
142
+ initialize(eventStore) {
143
+ this.eventStore = eventStore;
144
+ if (eventStore && "setRelays" in eventStore && typeof eventStore.setRelays === "function") {
145
+ eventStore.setRelays(this.relayUrls);
146
+ }
147
+ }
148
+ /**
149
+ * Set relay URLs
150
+ */
151
+ setRelays(urls) {
152
+ this.relayUrls = urls;
153
+ if (this.eventStore && "setRelays" in this.eventStore && typeof this.eventStore.setRelays === "function") {
154
+ this.eventStore.setRelays(urls);
155
+ }
156
+ }
157
+ /**
158
+ * Publish an event to relays
159
+ */
160
+ async publishEvent(event, timeoutMs = 1e3) {
161
+ if (!this.eventStore) {
162
+ throw new Error("RelayService not initialized. Call initialize() with an EventStore instance.");
163
+ }
164
+ return new Promise((resolve, reject) => {
165
+ if (this.relayUrls.length === 0) {
166
+ reject(new Error("No relays configured"));
167
+ return;
168
+ }
169
+ const eventStore = this.eventStore;
170
+ if (!eventStore || typeof eventStore.publish !== "function") {
171
+ reject(new Error("EventStore does not support publish method"));
172
+ return;
173
+ }
174
+ const subscription = eventStore.publish(event).subscribe({
175
+ next: (response) => {
176
+ if (response?.type === "OK") {
177
+ subscription.unsubscribe();
178
+ resolve(true);
179
+ }
180
+ },
181
+ error: (error) => {
182
+ subscription.unsubscribe();
183
+ reject(error);
184
+ }
185
+ });
186
+ setTimeout(() => {
187
+ subscription.unsubscribe();
188
+ resolve(false);
189
+ }, timeoutMs);
190
+ });
191
+ }
192
+ /**
193
+ * Fetch a profile (Kind 0 event)
194
+ */
195
+ async fetchProfile(pubkey) {
196
+ if (!this.eventStore) {
197
+ throw new Error("RelayService not initialized. Call initialize() with an EventStore instance.");
198
+ }
199
+ return new Promise((resolve) => {
200
+ const filter = {
201
+ kinds: [0],
202
+ authors: [pubkey],
203
+ limit: 1
204
+ };
205
+ const eventStore = this.eventStore;
206
+ if (!eventStore || typeof eventStore.query !== "function") {
207
+ resolve(null);
208
+ return;
209
+ }
210
+ const subscription = eventStore.query(filter).subscribe({
211
+ next: (packet) => {
212
+ if (packet?.event && packet.event.kind === 0) {
213
+ try {
214
+ const metadata = JSON.parse(packet.event.content);
215
+ subscription.unsubscribe();
216
+ resolve(metadata);
217
+ } catch (error) {
218
+ console.error("Failed to parse profile metadata:", error);
219
+ }
220
+ }
221
+ },
222
+ complete: () => {
223
+ subscription.unsubscribe();
224
+ resolve(null);
225
+ },
226
+ error: (error) => {
227
+ console.error("Error fetching profile:", error);
228
+ subscription.unsubscribe();
229
+ resolve(null);
230
+ }
231
+ });
232
+ setTimeout(() => {
233
+ subscription.unsubscribe();
234
+ resolve(null);
235
+ }, 5e3);
236
+ });
237
+ }
238
+ /**
239
+ * Fetch role tag from profile event (Kind 0)
240
+ */
241
+ async fetchProfileRoleTag(pubkey) {
242
+ if (!this.eventStore) {
243
+ throw new Error("RelayService not initialized. Call initialize() with an EventStore instance.");
244
+ }
245
+ return new Promise((resolve) => {
246
+ const filter = {
247
+ kinds: [0],
248
+ authors: [pubkey],
249
+ limit: 1
250
+ };
251
+ const eventStore = this.eventStore;
252
+ if (!eventStore || typeof eventStore.query !== "function") {
253
+ resolve(null);
254
+ return;
255
+ }
256
+ const subscription = eventStore.query(filter).subscribe({
257
+ next: (packet) => {
258
+ if (packet?.event && packet.event.kind === 0) {
259
+ const tags = packet.event.tags || [];
260
+ for (const tag of tags) {
261
+ if (tag[0] === "role" && tag[1]) {
262
+ subscription.unsubscribe();
263
+ resolve(tag[1]);
264
+ return;
265
+ }
266
+ }
267
+ subscription.unsubscribe();
268
+ resolve(null);
269
+ }
270
+ },
271
+ complete: () => {
272
+ subscription.unsubscribe();
273
+ resolve(null);
274
+ },
275
+ error: (error) => {
276
+ console.error("Error fetching profile role tag:", error);
277
+ subscription.unsubscribe();
278
+ resolve(null);
279
+ }
280
+ });
281
+ setTimeout(() => {
282
+ subscription.unsubscribe();
283
+ resolve(null);
284
+ }, 5e3);
285
+ });
286
+ }
287
+ /**
288
+ * Fetch a follow list (Kind 3 event)
289
+ */
290
+ async fetchFollowList(pubkey) {
291
+ if (!this.eventStore) {
292
+ throw new Error("RelayService not initialized. Call initialize() with an EventStore instance.");
293
+ }
294
+ return new Promise((resolve) => {
295
+ const filter = {
296
+ kinds: [3],
297
+ authors: [pubkey],
298
+ limit: 1
299
+ };
300
+ const eventStore = this.eventStore;
301
+ if (!eventStore || typeof eventStore.query !== "function") {
302
+ resolve([]);
303
+ return;
304
+ }
305
+ const subscription = eventStore.query(filter).subscribe({
306
+ next: (packet) => {
307
+ if (packet?.event && packet.event.kind === 3) {
308
+ const followList = [];
309
+ const tags = packet.event.tags || [];
310
+ for (const tag of tags) {
311
+ if (tag[0] === "p" && tag[1]) {
312
+ followList.push({
313
+ pubkey: tag[1],
314
+ relay: tag[2] || void 0,
315
+ petname: tag[3] || void 0
316
+ });
317
+ }
318
+ }
319
+ subscription.unsubscribe();
320
+ resolve(followList);
321
+ }
322
+ },
323
+ complete: () => {
324
+ subscription.unsubscribe();
325
+ resolve([]);
326
+ },
327
+ error: (error) => {
328
+ console.error("Error fetching follow list:", error);
329
+ subscription.unsubscribe();
330
+ resolve([]);
331
+ }
332
+ });
333
+ setTimeout(() => {
334
+ subscription.unsubscribe();
335
+ resolve([]);
336
+ }, 1e4);
337
+ });
338
+ }
339
+ /**
340
+ * Fetch multiple profiles in batch
341
+ */
342
+ async fetchMultipleProfiles(pubkeys) {
343
+ if (!this.eventStore) {
344
+ throw new Error("RelayService not initialized. Call initialize() with an EventStore instance.");
345
+ }
346
+ if (pubkeys.length === 0) {
347
+ return /* @__PURE__ */ new Map();
348
+ }
349
+ return new Promise((resolve) => {
350
+ const profiles = /* @__PURE__ */ new Map();
351
+ const filter = {
352
+ kinds: [0],
353
+ authors: pubkeys
354
+ };
355
+ const eventStore = this.eventStore;
356
+ if (!eventStore || typeof eventStore.query !== "function") {
357
+ resolve(/* @__PURE__ */ new Map());
358
+ return;
359
+ }
360
+ const subscription = eventStore.query(filter).subscribe({
361
+ next: (packet) => {
362
+ if (packet?.event && packet.event.kind === 0 && packet.event.pubkey) {
363
+ try {
364
+ const metadata = JSON.parse(packet.event.content);
365
+ profiles.set(packet.event.pubkey, metadata);
366
+ } catch (error) {
367
+ console.error("Failed to parse profile metadata:", error);
368
+ }
369
+ }
370
+ },
371
+ complete: () => {
372
+ subscription.unsubscribe();
373
+ resolve(profiles);
374
+ },
375
+ error: (error) => {
376
+ console.error("Error fetching profiles:", error);
377
+ subscription.unsubscribe();
378
+ resolve(profiles);
379
+ }
380
+ });
381
+ setTimeout(() => {
382
+ subscription.unsubscribe();
383
+ resolve(profiles);
384
+ }, 1e3);
385
+ });
386
+ }
387
+ /**
388
+ * Query kind 0 events (profiles) by pubkey
389
+ * If pubkeys array is empty, fetches recent kind 0 events
390
+ */
391
+ async queryProfiles(pubkeys = [], limit = 100) {
392
+ if (!this.eventStore) {
393
+ throw new Error("RelayService not initialized. Call initialize() with an EventStore instance.");
394
+ }
395
+ return new Promise((resolve) => {
396
+ const profiles = /* @__PURE__ */ new Map();
397
+ const filter = {
398
+ kinds: [0],
399
+ limit
400
+ };
401
+ if (pubkeys.length > 0) {
402
+ filter.authors = pubkeys;
403
+ }
404
+ const eventStore = this.eventStore;
405
+ if (!eventStore || typeof eventStore.query !== "function") {
406
+ resolve(/* @__PURE__ */ new Map());
407
+ return;
408
+ }
409
+ const subscription = eventStore.query(filter).subscribe({
410
+ next: (packet) => {
411
+ if (packet?.event && packet.event.kind === 0 && packet.event.pubkey) {
412
+ try {
413
+ const metadata = JSON.parse(packet.event.content);
414
+ const timestamp = packet.event.created_at || 0;
415
+ const existing = profiles.get(packet.event.pubkey);
416
+ if (!existing || timestamp > existing.timestamp) {
417
+ profiles.set(packet.event.pubkey, { metadata, timestamp });
418
+ }
419
+ } catch (error) {
420
+ console.error("Failed to parse profile metadata:", error);
421
+ }
422
+ }
423
+ },
424
+ complete: () => {
425
+ subscription.unsubscribe();
426
+ const result = /* @__PURE__ */ new Map();
427
+ profiles.forEach((value, pubkey) => {
428
+ result.set(pubkey, value.metadata);
429
+ });
430
+ resolve(result);
431
+ },
432
+ error: (error) => {
433
+ console.error("Error querying profiles:", error);
434
+ subscription.unsubscribe();
435
+ const result = /* @__PURE__ */ new Map();
436
+ profiles.forEach((value, pubkey) => {
437
+ result.set(pubkey, value.metadata);
438
+ });
439
+ resolve(result);
440
+ }
441
+ });
442
+ setTimeout(() => {
443
+ subscription.unsubscribe();
444
+ const result = /* @__PURE__ */ new Map();
445
+ profiles.forEach((value, pubkey) => {
446
+ result.set(pubkey, value.metadata);
447
+ });
448
+ resolve(result);
449
+ }, 1e4);
450
+ });
451
+ }
452
+ /**
453
+ * Publish or update a kind 3 event (follow list/contacts)
454
+ */
455
+ async publishFollowList(pubkey, followList, signEvent) {
456
+ const tags = followList.map((entry) => {
457
+ const tag = ["p", entry.pubkey];
458
+ if (entry.relay) {
459
+ tag.push(entry.relay);
460
+ }
461
+ if (entry.petname) {
462
+ tag.push(entry.petname);
463
+ }
464
+ return tag;
465
+ });
466
+ const event = {
467
+ kind: 3,
468
+ content: "",
469
+ created_at: Math.floor(Date.now() / 1e3),
470
+ tags
471
+ };
472
+ const signedEvent = await signEvent(event);
473
+ return await this.publishEvent(signedEvent);
474
+ }
475
+ };
476
+ function LoginButton({
477
+ authService,
478
+ setAuthenticated,
479
+ setLoginError,
480
+ onSuccess
481
+ }) {
482
+ const [isLoading, setIsLoading] = react.useState(false);
483
+ const handleLogin = async () => {
484
+ setIsLoading(true);
485
+ setLoginError(null);
486
+ try {
487
+ if (!authService.hasKeyInfo()) {
488
+ throw new Error("No account found. Please register first.");
489
+ }
490
+ const keyInfo = authService.getCurrentKeyInfo();
491
+ if (!keyInfo) {
492
+ throw new Error("Failed to load account information.");
493
+ }
494
+ await authService.getPublicKey();
495
+ setAuthenticated(keyInfo);
496
+ if (onSuccess) {
497
+ onSuccess();
498
+ }
499
+ } catch (err) {
500
+ console.error("Login error:", err);
501
+ const errorMessage = err instanceof Error ? err.message : "Failed to login";
502
+ setLoginError(errorMessage);
503
+ setIsLoading(false);
504
+ }
505
+ };
506
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "login-button-container", children: /* @__PURE__ */ jsxRuntime.jsx(
507
+ "button",
508
+ {
509
+ className: "auth-button secondary",
510
+ onClick: handleLogin,
511
+ disabled: isLoading,
512
+ children: isLoading ? "Logging in..." : "Login"
513
+ }
514
+ ) });
515
+ }
516
+ function RegistrationFlow({
517
+ authService,
518
+ setAuthenticated,
519
+ onSuccess
520
+ }) {
521
+ const [isLoading, setIsLoading] = react.useState(false);
522
+ const [error, setError] = react.useState(null);
523
+ const [step, setStep] = react.useState("info");
524
+ const [username, setUsername] = react.useState("");
525
+ const handleRegister = async () => {
526
+ setIsLoading(true);
527
+ setError(null);
528
+ setStep("creating");
529
+ try {
530
+ authService.clearStoredKeyInfo();
531
+ let credentialId;
532
+ const passkeyUsername = username.trim() || void 0;
533
+ try {
534
+ credentialId = await authService.createPasskey(passkeyUsername);
535
+ } catch (passkeyError) {
536
+ console.error("[RegistrationFlow] Passkey creation failed:", passkeyError);
537
+ const errorMessage = passkeyError instanceof Error ? passkeyError.message : String(passkeyError);
538
+ if (errorMessage.includes("NotAllowedError") || errorMessage.includes("user")) {
539
+ throw new Error(
540
+ "Passkey creation was cancelled or not allowed. Please try again and complete the authentication prompt."
541
+ );
542
+ } else if (errorMessage.includes("NotSupportedError") || errorMessage.includes("PRF")) {
543
+ throw new Error(
544
+ "WebAuthn PRF extension is not supported in your browser. Please use Chrome 118+, Safari 17+, or a compatible browser."
545
+ );
546
+ }
547
+ throw new Error(`Failed to create passkey: ${errorMessage}`);
548
+ }
549
+ let keyInfo;
550
+ try {
551
+ keyInfo = await authService.createNostrKey(credentialId);
552
+ } catch (nostrKeyError) {
553
+ console.error("[RegistrationFlow] Failed to create Nostr key from passkey:", nostrKeyError);
554
+ const errorMessage = nostrKeyError instanceof Error ? nostrKeyError.message : String(nostrKeyError);
555
+ if (errorMessage.includes("PRF secret not available") || errorMessage.includes("PRF")) {
556
+ throw new Error(
557
+ "PRF extension is required but not available. This may mean:\n1. Your browser doesn't support WebAuthn PRF extension (Chrome 118+, Safari 17+)\n2. The passkey was created without PRF support\nPlease try again or use a compatible browser."
558
+ );
559
+ }
560
+ throw nostrKeyError;
561
+ }
562
+ authService.setCurrentKeyInfo(keyInfo);
563
+ setAuthenticated(keyInfo);
564
+ setStep("success");
565
+ if (onSuccess) {
566
+ setTimeout(() => {
567
+ onSuccess();
568
+ }, 1500);
569
+ }
570
+ } catch (err) {
571
+ console.error("[RegistrationFlow] Registration error:", err);
572
+ setError(err instanceof Error ? err.message : "Failed to create account");
573
+ setStep("info");
574
+ setIsLoading(false);
575
+ }
576
+ };
577
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "auth-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "auth-card", children: [
578
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: "Create Account" }),
579
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "auth-description", children: "Create a new decentralized Identity. Your identity will be securely derived from a passkey." }),
580
+ step === "info" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
581
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "auth-features", children: [
582
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "feature-item", children: [
583
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "feature-icon", children: "\u{1F510}" }),
584
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Phishing-resistant authentication" })
585
+ ] }),
586
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "feature-item", children: [
587
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "feature-icon", children: "\u{1F4F1}" }),
588
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Biometric authentication support" })
589
+ ] }),
590
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "feature-item", children: [
591
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "feature-icon", children: "\u{1F310}" }),
592
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Cross-device synchronization" })
593
+ ] })
594
+ ] }),
595
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "username-section", children: [
596
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "username", className: "username-label", children: "Name (Optional)" }),
597
+ /* @__PURE__ */ jsxRuntime.jsx(
598
+ "input",
599
+ {
600
+ id: "username",
601
+ type: "text",
602
+ className: "username-input",
603
+ placeholder: "Enter a name for this passkey",
604
+ value: username,
605
+ onChange: (e) => setUsername(e.target.value),
606
+ disabled: isLoading
607
+ }
608
+ )
609
+ ] }),
610
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "error-message", children: error }),
611
+ /* @__PURE__ */ jsxRuntime.jsx(
612
+ "button",
613
+ {
614
+ className: "auth-button primary",
615
+ onClick: handleRegister,
616
+ disabled: isLoading,
617
+ children: isLoading ? "Creating..." : "Create Account"
618
+ }
619
+ )
620
+ ] }),
621
+ step === "creating" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "loading-state", children: [
622
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner" }),
623
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Creating your passkey..." }),
624
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "loading-hint", children: "Please follow your browser's authentication prompt" }),
625
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "loading-hint-small", children: "\u{1F4A1} Using your system's native passkey manager (Touch ID, Face ID, Windows Hello, etc.)" })
626
+ ] }),
627
+ step === "success" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "success-state", children: [
628
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "success-icon", children: "\u2713" }),
629
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Account created successfully!" }),
630
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "success-hint", children: "Redirecting to profile setup..." })
631
+ ] })
632
+ ] }) });
633
+ }
634
+ var BarcodeScanner = ({ onDecode, active = true }) => {
635
+ const [error, setError] = react.useState(null);
636
+ const { ref } = reactZxing.useZxing({
637
+ onDecodeResult: (result) => {
638
+ onDecode(result.getText());
639
+ },
640
+ onError: (e) => {
641
+ const errorMessage = e instanceof Error ? e.message : "Camera error";
642
+ setError(errorMessage);
643
+ }
644
+ });
645
+ if (!active) return null;
646
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative", width: "100%", maxWidth: "400px" }, children: [
647
+ /* @__PURE__ */ jsxRuntime.jsx(
648
+ "video",
649
+ {
650
+ ref,
651
+ style: { width: "100%", borderRadius: "8px" },
652
+ playsInline: true,
653
+ muted: true
654
+ }
655
+ ),
656
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { style: { color: "red", marginTop: "0.5rem" }, children: error })
657
+ ] });
658
+ };
659
+ var TrustInfo = () => /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "trust-info", children: [
660
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Building Trust with Verifiable Relationship Credentials" }),
661
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
662
+ "Beyond the Personhood Credential, members can create ",
663
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Verifiable Relationship Credentials (VRCs)" }),
664
+ ". A VRC is issued directly between two members \u2013 for example, by scanning a QR\u2011code at a meetup \u2013 and certifies a first\u2011hand trust link."
665
+ ] }),
666
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
667
+ "Each VRC becomes a node in a decentralized trust graph. When you add a member, the system records a ",
668
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "role" }),
669
+ " tag that ties the new participant to your existing graph, enabling permissionless access to network\u2011state resources while keeping the underlying data private."
670
+ ] }),
671
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "The graph grows organically: trusted authorities issue PHCs, and members continuously enrich the network with peer\u2011generated VRCs, creating a resilient, scalable web of verified participants." })
672
+ ] });
673
+ function MembershipPage({
674
+ authService,
675
+ relayService,
676
+ publicKey,
677
+ onUnauthenticated
678
+ }) {
679
+ const [isLoading, setIsLoading] = react.useState(false);
680
+ const [isSaving, setIsSaving] = react.useState(false);
681
+ const [saveMessage, setSaveMessage] = react.useState(null);
682
+ const [searchQuery, setSearchQuery] = react.useState("");
683
+ const [profiles, setProfiles] = react.useState([]);
684
+ const [members, setMembers] = react.useState([]);
685
+ const [memberProfiles, setMemberProfiles] = react.useState(/* @__PURE__ */ new Map());
686
+ const [showScanner, setShowScanner] = react.useState(false);
687
+ react.useEffect(() => {
688
+ if (!publicKey) {
689
+ if (onUnauthenticated) {
690
+ onUnauthenticated();
691
+ }
692
+ return;
693
+ }
694
+ loadFollowList();
695
+ }, [publicKey]);
696
+ react.useEffect(() => {
697
+ if (members.length > 0) {
698
+ loadMemberProfiles();
699
+ }
700
+ }, [members]);
701
+ const handleDecoded = (decoded) => {
702
+ setSearchQuery(decoded);
703
+ setShowScanner(false);
704
+ handleSearch();
705
+ };
706
+ const loadFollowList = async () => {
707
+ if (!publicKey) return;
708
+ setIsLoading(true);
709
+ try {
710
+ const followList = await relayService.fetchFollowList(publicKey);
711
+ setMembers(followList);
712
+ } catch (error) {
713
+ console.error("Failed to load follow list:", error);
714
+ setSaveMessage("Failed to load membership list");
715
+ } finally {
716
+ setIsLoading(false);
717
+ }
718
+ };
719
+ const loadMemberProfiles = async () => {
720
+ if (members.length === 0) return;
721
+ try {
722
+ const pubkeys = members.map((m) => m.pubkey);
723
+ const profilesMap = await relayService.fetchMultipleProfiles(pubkeys);
724
+ setMemberProfiles(profilesMap);
725
+ } catch (error) {
726
+ console.error("Failed to load member profiles:", error);
727
+ }
728
+ };
729
+ const handleSearch = async () => {
730
+ if (!searchQuery.trim()) {
731
+ setIsLoading(true);
732
+ try {
733
+ const profilesMap = await relayService.queryProfiles([], 50);
734
+ const profilesList = [];
735
+ profilesMap.forEach((profile, pubkey) => {
736
+ profilesList.push({ ...profile, pubkey });
737
+ });
738
+ setProfiles(profilesList);
739
+ } catch (error) {
740
+ console.error("Failed to query profiles:", error);
741
+ setSaveMessage("Failed to search profiles");
742
+ } finally {
743
+ setIsLoading(false);
744
+ }
745
+ return;
746
+ }
747
+ const trimmedQuery = searchQuery.trim();
748
+ if (trimmedQuery.length !== 64 || !/^[0-9a-fA-F]+$/.test(trimmedQuery)) {
749
+ setSaveMessage("Invalid pubkey format. Must be 64 hex characters.");
750
+ setTimeout(() => setSaveMessage(null), 3e3);
751
+ return;
752
+ }
753
+ setIsLoading(true);
754
+ try {
755
+ const profilesMap = await relayService.queryProfiles([trimmedQuery], 1);
756
+ const profilesList = [];
757
+ profilesMap.forEach((profile, pubkey) => {
758
+ profilesList.push({ ...profile, pubkey });
759
+ });
760
+ setProfiles(profilesList);
761
+ if (profilesList.length === 0) {
762
+ setSaveMessage("No profile found for this pubkey");
763
+ setTimeout(() => setSaveMessage(null), 3e3);
764
+ }
765
+ } catch (error) {
766
+ console.error("Failed to query profile:", error);
767
+ setSaveMessage("Failed to search profile");
768
+ } finally {
769
+ setIsLoading(false);
770
+ }
771
+ };
772
+ const handleAddMember = async (pubkey) => {
773
+ if (!publicKey) return;
774
+ if (members.some((m) => m.pubkey === pubkey)) {
775
+ setSaveMessage("User is already a member");
776
+ setTimeout(() => setSaveMessage(null), 3e3);
777
+ return;
778
+ }
779
+ setIsSaving(true);
780
+ try {
781
+ const newMembers = [
782
+ ...members,
783
+ {
784
+ pubkey
785
+ }
786
+ ];
787
+ await relayService.publishFollowList(
788
+ publicKey,
789
+ newMembers,
790
+ (event) => authService.signEvent(event)
791
+ );
792
+ setMembers(newMembers);
793
+ setSaveMessage("Member added successfully!");
794
+ setTimeout(() => setSaveMessage(null), 3e3);
795
+ } catch (error) {
796
+ console.error("Failed to add member:", error);
797
+ setSaveMessage(
798
+ error instanceof Error ? `Error: ${error.message}` : "Failed to add member"
799
+ );
800
+ } finally {
801
+ setIsSaving(false);
802
+ }
803
+ };
804
+ const handleRemoveMember = async (pubkey) => {
805
+ if (!publicKey) return;
806
+ setIsSaving(true);
807
+ try {
808
+ const newMembers = members.filter((m) => m.pubkey !== pubkey);
809
+ await relayService.publishFollowList(
810
+ publicKey,
811
+ newMembers,
812
+ (event) => authService.signEvent(event)
813
+ );
814
+ setMembers(newMembers);
815
+ setSaveMessage("Member removed successfully!");
816
+ setTimeout(() => setSaveMessage(null), 3e3);
817
+ } catch (error) {
818
+ console.error("Failed to remove member:", error);
819
+ setSaveMessage(
820
+ error instanceof Error ? `Error: ${error.message}` : "Failed to remove member"
821
+ );
822
+ } finally {
823
+ setIsSaving(false);
824
+ }
825
+ };
826
+ const getProfileDisplayName = (profile, pubkey) => {
827
+ return profile.display_name || profile.name || pubkey.slice(0, 16) + "...";
828
+ };
829
+ const formatPubkey = (pubkey) => {
830
+ return `${pubkey.slice(0, 8)}...${pubkey.slice(-8)}`;
831
+ };
832
+ if (isLoading && members.length === 0) {
833
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "membership-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "loading-state", children: [
834
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner" }),
835
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading membership list..." })
836
+ ] }) });
837
+ }
838
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "membership-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "membership-card", children: [
839
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: "Membership Management" }),
840
+ /* @__PURE__ */ jsxRuntime.jsx(TrustInfo, {}),
841
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "membership-description", children: "Query applicants and manage the membership list." }),
842
+ saveMessage && /* @__PURE__ */ jsxRuntime.jsx(
843
+ "div",
844
+ {
845
+ className: `save-message ${saveMessage.includes("Error") || saveMessage.includes("Failed") ? "error" : "success"}`,
846
+ children: saveMessage
847
+ }
848
+ ),
849
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "search-section", children: [
850
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { children: "Add Member" }),
851
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "search-form", children: [
852
+ /* @__PURE__ */ jsxRuntime.jsx(
853
+ "input",
854
+ {
855
+ type: "text",
856
+ value: searchQuery,
857
+ onChange: (e) => setSearchQuery(e.target.value),
858
+ onKeyPress: (e) => e.key === "Enter" && handleSearch(),
859
+ placeholder: "Enter pubkey or leave empty for recent profiles",
860
+ className: "search-input",
861
+ disabled: isLoading
862
+ }
863
+ ),
864
+ /* @__PURE__ */ jsxRuntime.jsx(
865
+ "button",
866
+ {
867
+ onClick: handleSearch,
868
+ className: "search-button",
869
+ disabled: isLoading || isSaving,
870
+ children: isLoading ? "Searching..." : "Search"
871
+ }
872
+ ),
873
+ /* @__PURE__ */ jsxRuntime.jsx(
874
+ "button",
875
+ {
876
+ onClick: () => setShowScanner((prev) => !prev),
877
+ className: "scanner-toggle",
878
+ disabled: isLoading || isSaving,
879
+ children: showScanner ? "Close Scanner" : "Scan QR"
880
+ }
881
+ )
882
+ ] }),
883
+ showScanner && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { marginTop: "1rem" }, children: [
884
+ /* @__PURE__ */ jsxRuntime.jsx(BarcodeScanner, { onDecode: handleDecoded, active: true }),
885
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: { fontSize: "0.85rem", marginTop: "0.5rem" }, children: "Point at QR\u2011code containing a pubkey" })
886
+ ] }),
887
+ profiles.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profiles-list", children: [
888
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Search Results" }),
889
+ profiles.map((profile) => {
890
+ const isMember = members.some((m) => m.pubkey === profile.pubkey);
891
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profile-item", children: [
892
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profile-info", children: [
893
+ profile.picture && /* @__PURE__ */ jsxRuntime.jsx(
894
+ "img",
895
+ {
896
+ src: profile.picture,
897
+ alt: getProfileDisplayName(profile, profile.pubkey),
898
+ className: "profile-avatar"
899
+ }
900
+ ),
901
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profile-details", children: [
902
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-name", children: getProfileDisplayName(profile, profile.pubkey) }),
903
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-pubkey", children: formatPubkey(profile.pubkey) }),
904
+ profile.about && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-about", children: profile.about })
905
+ ] })
906
+ ] }),
907
+ /* @__PURE__ */ jsxRuntime.jsx(
908
+ "button",
909
+ {
910
+ onClick: () => isMember ? handleRemoveMember(profile.pubkey) : handleAddMember(profile.pubkey),
911
+ className: `member-button ${isMember ? "remove" : "add"}`,
912
+ disabled: isSaving,
913
+ children: isMember ? "Remove" : "Add Member"
914
+ }
915
+ )
916
+ ] }, profile.pubkey);
917
+ })
918
+ ] })
919
+ ] }),
920
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "members-section", children: [
921
+ /* @__PURE__ */ jsxRuntime.jsxs("h2", { children: [
922
+ "Current Members (",
923
+ members.length,
924
+ ")"
925
+ ] }),
926
+ members.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "empty-message", children: "No members yet. Add members from search results above." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "members-list", children: members.map((member) => {
927
+ const profile = memberProfiles.get(member.pubkey);
928
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "member-item", children: [
929
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profile-info", children: [
930
+ profile?.picture && /* @__PURE__ */ jsxRuntime.jsx(
931
+ "img",
932
+ {
933
+ src: profile.picture,
934
+ alt: getProfileDisplayName(profile || {}, member.pubkey),
935
+ className: "profile-avatar"
936
+ }
937
+ ),
938
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profile-details", children: [
939
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-name", children: profile ? getProfileDisplayName(profile, member.pubkey) : formatPubkey(member.pubkey) }),
940
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-pubkey", children: formatPubkey(member.pubkey) }),
941
+ profile?.about && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-about", children: profile.about }),
942
+ member.petname && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profile-petname", children: [
943
+ "Name: ",
944
+ member.petname
945
+ ] })
946
+ ] })
947
+ ] }),
948
+ /* @__PURE__ */ jsxRuntime.jsx(
949
+ "button",
950
+ {
951
+ onClick: () => handleRemoveMember(member.pubkey),
952
+ className: "member-button remove",
953
+ disabled: isSaving,
954
+ children: "Remove"
955
+ }
956
+ )
957
+ ] }, member.pubkey);
958
+ }) })
959
+ ] })
960
+ ] }) });
961
+ }
962
+ var PersonhoodInfo = () => /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "personhood-info", children: [
963
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
964
+ "Network\u2011state members receive a ",
965
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Personhood Credential (PHC)" }),
966
+ " from a trusted authority. The PHC attests that the holder is a unique, real individual. Because the credential lives in your passport, you can present a ",
967
+ /* @__PURE__ */ jsxRuntime.jsx("em", { children: "zero\u2011knowledge proof" }),
968
+ " that you are verified."
969
+ ] }),
970
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "In this form we compare the name you enter with the name disclosed by your verified passport proof. If the two match, the profile will be saved as your business card credential together with a passport tag that references the PHC's unique identifier." })
971
+ ] });
972
+ function ProfilePage({
973
+ authService,
974
+ relayService,
975
+ publicKey,
976
+ onUnauthenticated,
977
+ onSuccess,
978
+ onRoleSuggestion
979
+ }) {
980
+ const [isLoading, setIsLoading] = react.useState(false);
981
+ const [isSaving, setIsSaving] = react.useState(false);
982
+ const [saveMessage, setSaveMessage] = react.useState(null);
983
+ const [suggestedRole, setSuggestedRole] = react.useState(null);
984
+ const [isGettingRole, setIsGettingRole] = react.useState(false);
985
+ const [formData, setFormData] = react.useState({
986
+ name: "",
987
+ display_name: "",
988
+ about: "",
989
+ picture: "",
990
+ website: ""
991
+ });
992
+ react.useEffect(() => {
993
+ if (!publicKey) {
994
+ if (onUnauthenticated) {
995
+ onUnauthenticated();
996
+ }
997
+ return;
998
+ }
999
+ loadProfile();
1000
+ }, [publicKey]);
1001
+ const loadProfile = async () => {
1002
+ if (!publicKey) return;
1003
+ setIsLoading(true);
1004
+ try {
1005
+ const [profile, roleTag] = await Promise.all([
1006
+ relayService.fetchProfile(publicKey),
1007
+ relayService.fetchProfileRoleTag(publicKey)
1008
+ ]);
1009
+ if (profile) {
1010
+ setFormData({
1011
+ name: profile.name || "",
1012
+ display_name: profile.display_name || "",
1013
+ about: profile.about || "",
1014
+ picture: profile.picture || "",
1015
+ website: profile.website || ""
1016
+ });
1017
+ }
1018
+ if (roleTag) {
1019
+ setSuggestedRole(roleTag);
1020
+ }
1021
+ } catch (error) {
1022
+ console.error("Failed to load profile:", error);
1023
+ } finally {
1024
+ setIsLoading(false);
1025
+ }
1026
+ };
1027
+ const handleSubmit = async (e) => {
1028
+ e.preventDefault();
1029
+ if (!publicKey) return;
1030
+ setIsSaving(true);
1031
+ setSaveMessage(null);
1032
+ try {
1033
+ const tags = [];
1034
+ if (formData.about && formData.about.trim().length > 0 && onRoleSuggestion) {
1035
+ setIsGettingRole(true);
1036
+ try {
1037
+ const roleSuggestion = await onRoleSuggestion(formData.about);
1038
+ if (roleSuggestion) {
1039
+ tags.push(["role", roleSuggestion]);
1040
+ setSuggestedRole(roleSuggestion);
1041
+ } else {
1042
+ setSuggestedRole(null);
1043
+ }
1044
+ } catch (error) {
1045
+ console.error("Failed to get role suggestion:", error);
1046
+ setSuggestedRole(null);
1047
+ } finally {
1048
+ setIsGettingRole(false);
1049
+ }
1050
+ } else {
1051
+ setSuggestedRole(null);
1052
+ }
1053
+ const profile = {
1054
+ kind: 0,
1055
+ content: JSON.stringify(formData),
1056
+ created_at: Math.floor(Date.now() / 1e3),
1057
+ tags
1058
+ };
1059
+ const follows = {
1060
+ kind: 3,
1061
+ content: "",
1062
+ created_at: Math.floor(Date.now() / 1e3),
1063
+ tags: [["p", publicKey, "wss://relay.damus.io", formData.name || ""]]
1064
+ };
1065
+ const signedProfile = await authService.signEvent(profile);
1066
+ const signedFollows = await authService.signEvent(follows);
1067
+ await relayService.publishEvent(signedProfile);
1068
+ await relayService.publishEvent(signedFollows);
1069
+ setSaveMessage("Profile saved successfully!");
1070
+ setTimeout(() => {
1071
+ setSaveMessage(null);
1072
+ }, 3e3);
1073
+ if (onSuccess) {
1074
+ onSuccess();
1075
+ }
1076
+ } catch (error) {
1077
+ console.error("Failed to save profile:", error);
1078
+ setSaveMessage(
1079
+ error instanceof Error ? `Error: ${error.message}` : "Failed to save profile"
1080
+ );
1081
+ } finally {
1082
+ setIsSaving(false);
1083
+ }
1084
+ };
1085
+ const handleChange = (e) => {
1086
+ const { name, value } = e.target;
1087
+ setFormData((prev) => ({
1088
+ ...prev,
1089
+ [name]: value
1090
+ }));
1091
+ };
1092
+ if (isLoading) {
1093
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "loading-state", children: [
1094
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "spinner" }),
1095
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Loading profile..." })
1096
+ ] }) });
1097
+ }
1098
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "profile-container", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "profile-card", children: [
1099
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { children: "Profile Setup" }),
1100
+ /* @__PURE__ */ jsxRuntime.jsx(PersonhoodInfo, {}),
1101
+ /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "profile-form", children: [
1102
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "form-group", children: [
1103
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "name", children: "First Name" }),
1104
+ /* @__PURE__ */ jsxRuntime.jsx(
1105
+ "input",
1106
+ {
1107
+ type: "text",
1108
+ id: "name",
1109
+ name: "name",
1110
+ value: formData.name || "",
1111
+ onChange: handleChange,
1112
+ placeholder: "Your username"
1113
+ }
1114
+ )
1115
+ ] }),
1116
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "form-group", children: [
1117
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "display_name", children: "Last Name" }),
1118
+ /* @__PURE__ */ jsxRuntime.jsx(
1119
+ "input",
1120
+ {
1121
+ type: "text",
1122
+ id: "display_name",
1123
+ name: "display_name",
1124
+ value: formData.display_name || "",
1125
+ onChange: handleChange,
1126
+ placeholder: "Your Last Name"
1127
+ }
1128
+ )
1129
+ ] }),
1130
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "form-group", children: [
1131
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "about", children: "About" }),
1132
+ /* @__PURE__ */ jsxRuntime.jsx(
1133
+ "textarea",
1134
+ {
1135
+ id: "about",
1136
+ name: "about",
1137
+ value: formData.about || "",
1138
+ onChange: handleChange,
1139
+ placeholder: "Tell us about yourself",
1140
+ rows: 4
1141
+ }
1142
+ )
1143
+ ] }),
1144
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "form-group", children: [
1145
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "picture", children: "Profile Picture URL" }),
1146
+ /* @__PURE__ */ jsxRuntime.jsx(
1147
+ "input",
1148
+ {
1149
+ type: "url",
1150
+ id: "picture",
1151
+ name: "picture",
1152
+ value: formData.picture || "",
1153
+ onChange: handleChange,
1154
+ placeholder: "https://example.com/avatar.jpg"
1155
+ }
1156
+ )
1157
+ ] }),
1158
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "form-group", children: [
1159
+ /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: "website", children: "LinkedIn" }),
1160
+ /* @__PURE__ */ jsxRuntime.jsx(
1161
+ "input",
1162
+ {
1163
+ type: "url",
1164
+ id: "website",
1165
+ name: "website",
1166
+ value: formData.website || "",
1167
+ onChange: handleChange,
1168
+ placeholder: "https://linkedin.com/example"
1169
+ }
1170
+ )
1171
+ ] }),
1172
+ saveMessage && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `save-message ${saveMessage.includes("Error") ? "error" : "success"}`, children: saveMessage }),
1173
+ (isGettingRole || suggestedRole) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "role-tag-container", children: isGettingRole ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1174
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "role-tag-label", children: "Getting AI suggestion..." }),
1175
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "role-tag-loading", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "role-tag-spinner" }) })
1176
+ ] }) : suggestedRole ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1177
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "role-tag-label", children: "AI Suggested Role:" }),
1178
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "role-tag", children: suggestedRole })
1179
+ ] }) : null }),
1180
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "form-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", className: "save-button", disabled: isSaving, children: isSaving ? "Saving..." : "Save Profile" }) })
1181
+ ] })
1182
+ ] }) });
1183
+ }
1184
+ function useAuthInit(authService, setAuthenticated) {
1185
+ react.useEffect(() => {
1186
+ if (authService.hasKeyInfo()) {
1187
+ const keyInfo = authService.getCurrentKeyInfo();
1188
+ if (keyInfo) {
1189
+ setAuthenticated(keyInfo);
1190
+ }
1191
+ }
1192
+ }, [authService, setAuthenticated]);
1193
+ }
1194
+ var createAuthStore = () => {
1195
+ return zustand.create((set) => ({
1196
+ isAuthenticated: false,
1197
+ publicKey: null,
1198
+ keyInfo: null,
1199
+ loginError: null,
1200
+ setAuthenticated: (keyInfo) => {
1201
+ set({
1202
+ isAuthenticated: !!keyInfo,
1203
+ publicKey: keyInfo?.pubkey || null,
1204
+ keyInfo,
1205
+ loginError: null
1206
+ });
1207
+ },
1208
+ setLoginError: (error) => {
1209
+ set({ loginError: error });
1210
+ },
1211
+ logout: () => {
1212
+ set({
1213
+ isAuthenticated: false,
1214
+ publicKey: null,
1215
+ keyInfo: null,
1216
+ loginError: null
1217
+ });
1218
+ }
1219
+ }));
1220
+ };
1221
+ var useAuthStore = createAuthStore();
1222
+
1223
+ exports.AuthService = AuthService;
1224
+ exports.BarcodeScanner = BarcodeScanner;
1225
+ exports.LoginButton = LoginButton;
1226
+ exports.MembershipPage = MembershipPage;
1227
+ exports.ProfilePage = ProfilePage;
1228
+ exports.RegistrationFlow = RegistrationFlow;
1229
+ exports.RelayService = RelayService;
1230
+ exports.createAuthStore = createAuthStore;
1231
+ exports.useAuthInit = useAuthInit;
1232
+ exports.useAuthStore = useAuthStore;
1233
+ //# sourceMappingURL=index.js.map
1234
+ //# sourceMappingURL=index.js.map