@waaskey/react 0.3.1 → 0.4.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 CHANGED
@@ -1,8 +1,144 @@
1
+ 'use client';
1
2
  import { Waaskey } from '@waaskey/sdk';
2
3
  export * from '@waaskey/sdk';
3
- import { createContext, useContext, useMemo, createElement, useState, useCallback, useEffect, useRef } from 'react';
4
+ import { createContext, useContext, useRef, useMemo, createElement, useState, useCallback, useEffect } from 'react';
4
5
 
5
6
  // src/provider.ts
7
+ var AuthContext = createContext(null);
8
+ var STORAGE_KEY = "waaskey.session";
9
+ function webSessionStore(area) {
10
+ const storage = () => {
11
+ try {
12
+ if (typeof window === "undefined") return void 0;
13
+ return area === "local" ? window.localStorage : window.sessionStorage;
14
+ } catch {
15
+ return void 0;
16
+ }
17
+ };
18
+ return {
19
+ load() {
20
+ try {
21
+ const raw = storage()?.getItem(STORAGE_KEY);
22
+ if (!raw) return void 0;
23
+ const parsed = JSON.parse(raw);
24
+ return parsed?.token ? parsed : void 0;
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ },
29
+ save(session) {
30
+ try {
31
+ storage()?.setItem(STORAGE_KEY, JSON.stringify(session));
32
+ } catch {
33
+ }
34
+ },
35
+ clear() {
36
+ try {
37
+ storage()?.removeItem(STORAGE_KEY);
38
+ } catch {
39
+ }
40
+ }
41
+ };
42
+ }
43
+ function resolveStore(persistence) {
44
+ if (persistence === "none") return void 0;
45
+ if (persistence === "session" || persistence === "local") return webSessionStore(persistence);
46
+ return persistence;
47
+ }
48
+ function isUnauthenticated(error) {
49
+ const code = error?.code;
50
+ const status = error?.status;
51
+ return code === "unauthenticated" || status === 401;
52
+ }
53
+ function useAuthStore(client, persistence) {
54
+ const store = useMemo(() => resolveStore(persistence), [persistence]);
55
+ const [session, setSessionState] = useState();
56
+ const [user, setUser] = useState();
57
+ const [error, setError] = useState();
58
+ const [loading, setLoading] = useState(false);
59
+ const [ready, setReady] = useState(!store);
60
+ const mounted = useRef(true);
61
+ useEffect(() => {
62
+ mounted.current = true;
63
+ return () => {
64
+ mounted.current = false;
65
+ };
66
+ }, []);
67
+ const setSession = useCallback(
68
+ (next) => {
69
+ if (next) {
70
+ client.auth.restore(next);
71
+ void store?.save(next);
72
+ } else {
73
+ client.auth.logout();
74
+ void store?.clear();
75
+ }
76
+ setSessionState(next);
77
+ setUser(next?.endUser);
78
+ setError(void 0);
79
+ },
80
+ [client, store]
81
+ );
82
+ const logout = useCallback(() => setSession(void 0), [setSession]);
83
+ const refreshUser = useCallback(async () => {
84
+ if (!client.auth.isAuthenticated) return;
85
+ setLoading(true);
86
+ try {
87
+ const me = await client.auth.me();
88
+ if (mounted.current) setUser(me);
89
+ } catch (e) {
90
+ if (!mounted.current) return;
91
+ if (isUnauthenticated(e)) setSession(void 0);
92
+ else setError(e);
93
+ } finally {
94
+ if (mounted.current) setLoading(false);
95
+ }
96
+ }, [client, setSession]);
97
+ useEffect(() => {
98
+ if (session && !client.auth.isAuthenticated) client.auth.restore(session);
99
+ }, [client, session]);
100
+ useEffect(() => {
101
+ if (!store) {
102
+ setReady(true);
103
+ return;
104
+ }
105
+ let cancelled = false;
106
+ setReady(false);
107
+ void (async () => {
108
+ let restored;
109
+ try {
110
+ restored = await store.load();
111
+ } catch {
112
+ restored = void 0;
113
+ }
114
+ if (cancelled) return;
115
+ if (restored) {
116
+ client.auth.restore(restored);
117
+ setSessionState(restored);
118
+ setUser(restored.endUser);
119
+ }
120
+ setReady(true);
121
+ if (restored) {
122
+ try {
123
+ const me = await client.auth.me();
124
+ if (!cancelled && mounted.current) setUser(me);
125
+ } catch (e) {
126
+ if (cancelled || !mounted.current) return;
127
+ if (isUnauthenticated(e)) {
128
+ client.auth.logout();
129
+ void store.clear();
130
+ setSessionState(void 0);
131
+ setUser(void 0);
132
+ }
133
+ }
134
+ }
135
+ })();
136
+ return () => {
137
+ cancelled = true;
138
+ };
139
+ }, [client, store]);
140
+ return useMemo(() => ({ session, user, ready, loading, error, setSession, refreshUser, logout }), [session, user, ready, loading, error, setSession, refreshUser, logout]);
141
+ }
6
142
  var WaaskeyContext = createContext(null);
7
143
 
8
144
  // src/theme.ts
@@ -39,12 +175,89 @@ function useTheme() {
39
175
 
40
176
  // src/provider.ts
41
177
  function WaaskeyProvider(props) {
42
- const source = "client" in props ? props.client : props.options;
43
- const client = useMemo(() => "client" in props ? props.client : new Waaskey(props.options), [source]);
178
+ const externalClient = "client" in props ? props.client : void 0;
179
+ const options = "client" in props ? void 0 : props.options;
180
+ const optionsKey = options ? optionsIdentity(options) : "";
181
+ const held = useRef(void 0);
182
+ if (!held.current || held.current.key !== optionsKey || externalClient !== void 0 && held.current.client !== externalClient) {
183
+ if (held.current) warnClientReplaced(externalClient !== void 0);
184
+ held.current = { key: optionsKey, client: externalClient ?? new Waaskey(options) };
185
+ }
186
+ const { client } = held.current;
44
187
  const theme = useMemo(() => resolveTheme(props.theme), [props.theme]);
45
- return createElement(WaaskeyContext.Provider, { value: client }, createElement(ThemeContext.Provider, { value: theme }, props.children));
188
+ const auth = useAuthStore(client, props.persistSession ?? "none");
189
+ return createElement(
190
+ WaaskeyContext.Provider,
191
+ { value: client },
192
+ createElement(AuthContext.Provider, { value: auth }, createElement(ThemeContext.Provider, { value: theme }, props.children))
193
+ );
46
194
  }
47
195
  var WaasProvider = WaaskeyProvider;
196
+ var objectIds = /* @__PURE__ */ new WeakMap();
197
+ var nextObjectId = 0;
198
+ function idOf(value) {
199
+ let id = objectIds.get(value);
200
+ if (id === void 0) {
201
+ id = ++nextObjectId;
202
+ objectIds.set(value, id);
203
+ }
204
+ return id;
205
+ }
206
+ function optionsIdentity(options) {
207
+ const record = options;
208
+ return Object.keys(record).sort().map((key) => {
209
+ const value = record[key];
210
+ if (value === null || typeof value !== "object" && typeof value !== "function") return `${key}=${String(value)}`;
211
+ return `${key}#${idOf(value)}`;
212
+ }).join("|");
213
+ }
214
+ function warnClientReplaced(external) {
215
+ try {
216
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return;
217
+ } catch {
218
+ }
219
+ const cause = external ? "the `client` prop changed" : "an option value changed identity";
220
+ console.warn(
221
+ `[waaskey] WaaskeyProvider replaced its client because ${cause}. Any in-flight ceremony is abandoned and cached state is lost (the end-user session is re-adopted automatically). Enriching the options once after login is expected; a replacement on every render means the options are rebuilt inline \u2014 hoist or memoize them.`
222
+ );
223
+ }
224
+ function useAsyncResource(load) {
225
+ const [state, setState] = useState({ loading: load !== void 0 });
226
+ const active = useRef(void 0);
227
+ const mounted = useRef(true);
228
+ useEffect(() => {
229
+ mounted.current = true;
230
+ return () => {
231
+ mounted.current = false;
232
+ active.current?.abort();
233
+ };
234
+ }, []);
235
+ const refresh = useCallback(async () => {
236
+ if (!load) return;
237
+ active.current?.abort();
238
+ const controller = new AbortController();
239
+ active.current = controller;
240
+ setState((prev) => ({ ...prev, loading: true, error: void 0 }));
241
+ try {
242
+ const data = await load(controller.signal);
243
+ if (controller.signal.aborted || !mounted.current) return;
244
+ setState({ loading: false, data });
245
+ } catch (error) {
246
+ if (controller.signal.aborted || !mounted.current) return;
247
+ setState({ loading: false, error });
248
+ }
249
+ }, [load]);
250
+ useEffect(() => {
251
+ if (!load) {
252
+ setState((prev) => prev.data === void 0 && prev.error === void 0 && !prev.loading ? prev : { loading: false });
253
+ return;
254
+ }
255
+ void refresh();
256
+ }, [load, refresh]);
257
+ return { ...state, refresh };
258
+ }
259
+
260
+ // src/hooks.ts
48
261
  function useWaaskey() {
49
262
  const client = useContext(WaaskeyContext);
50
263
  if (!client) throw new Error("useWaaskey must be used within a <WaaskeyProvider>.");
@@ -76,70 +289,41 @@ function useCreateWallet() {
76
289
  }
77
290
  function useWallet(id) {
78
291
  const waaskey = useWaaskey();
79
- const [state, setState] = useState({ loading: Boolean(id) });
80
- const refresh = useCallback(async () => {
81
- if (!id) return;
82
- setState({ loading: true });
83
- try {
84
- setState({ loading: false, data: await waaskey.wallets.get(id) });
85
- } catch (error) {
86
- setState({ loading: false, error });
87
- }
88
- }, [waaskey, id]);
89
- useEffect(() => {
90
- void refresh();
91
- }, [refresh]);
92
- return { ...state, refresh };
292
+ const load = useCallback((signal) => waaskey.wallets.get(id, { signal }), [waaskey, id]);
293
+ return useAsyncResource(id ? load : void 0);
294
+ }
295
+ function useWallets(query = {}) {
296
+ const waaskey = useWaaskey();
297
+ const { page, limit } = query;
298
+ const load = useCallback((signal) => waaskey.wallets.list({ page, limit }, { signal }), [waaskey, page, limit]);
299
+ const { data, ...rest } = useAsyncResource(load);
300
+ return { ...rest, data: data?.items, total: data?.total };
93
301
  }
94
302
  function useBalance(chain, address) {
95
303
  const waaskey = useWaaskey();
96
- const [state, setState] = useState({ loading: Boolean(chain && address) });
97
- const refresh = useCallback(async () => {
98
- if (!chain || !address) return;
99
- setState({ loading: true });
100
- try {
101
- setState({ loading: false, data: await waaskey.balances.getBalance(chain, address) });
102
- } catch (error) {
103
- setState({ loading: false, error });
104
- }
105
- }, [waaskey, chain, address]);
106
- useEffect(() => {
107
- void refresh();
108
- }, [refresh]);
109
- return { ...state, refresh };
304
+ const load = useCallback(() => waaskey.balances.getBalance(chain, address), [waaskey, chain, address]);
305
+ return useAsyncResource(chain && address ? load : void 0);
110
306
  }
111
307
  function useBalances(chains, address) {
112
308
  const waaskey = useWaaskey();
113
- const [state, setState] = useState({ loading: Boolean(address && chains.length) });
114
309
  const key = chains.join(",");
115
- const refresh = useCallback(async () => {
116
- if (!address || !key) return;
117
- setState({ loading: true });
118
- try {
119
- const data = await Promise.all(key.split(",").map((chain) => waaskey.balances.getBalance(chain, address)));
120
- setState({ loading: false, data });
121
- } catch (error) {
122
- setState({ loading: false, error });
123
- }
124
- }, [waaskey, key, address]);
125
- useEffect(() => {
126
- void refresh();
127
- }, [refresh]);
128
- return { ...state, refresh };
310
+ const list = useMemo(() => key ? key.split(",") : [], [key]);
311
+ const load = useCallback(() => Promise.all(list.map((chain) => waaskey.balances.getBalance(chain, address))), [waaskey, list, address]);
312
+ return useAsyncResource(address && list.length ? load : void 0);
129
313
  }
130
- function useSend(walletId) {
314
+ function useSend(wallet) {
131
315
  const waaskey = useWaaskey();
132
316
  const [status, setStatus] = useState("idle");
133
317
  const [result, setResult] = useState();
134
318
  const [error, setError] = useState();
135
319
  const send = useCallback(
136
320
  async (params) => {
137
- if (!walletId) throw new Error("useSend: no wallet id.");
321
+ if (!wallet) throw new Error("useSend: no wallet.");
138
322
  setStatus("pending");
139
323
  setError(void 0);
140
324
  try {
141
- const wallet = await waaskey.wallets.get(walletId);
142
- const res = await wallet.send(params);
325
+ const target = typeof wallet === "string" ? await waaskey.wallets.get(wallet) : wallet;
326
+ const res = await target.send(params);
143
327
  setResult(res);
144
328
  setStatus("sent");
145
329
  return res;
@@ -149,7 +333,7 @@ function useSend(walletId) {
149
333
  throw e;
150
334
  }
151
335
  },
152
- [waaskey, walletId]
336
+ [waaskey, wallet]
153
337
  );
154
338
  const reset = useCallback(() => {
155
339
  setStatus("idle");
@@ -158,69 +342,124 @@ function useSend(walletId) {
158
342
  }, []);
159
343
  return { send, status, result, error, reset };
160
344
  }
161
- function useSignatures(walletId) {
345
+ function useSignatures(wallet) {
162
346
  const waaskey = useWaaskey();
163
- const [state, setState] = useState({ loading: Boolean(walletId) });
164
- const refresh = useCallback(async () => {
165
- if (!walletId) return;
166
- setState({ loading: true });
167
- try {
168
- const wallet = await waaskey.wallets.get(walletId);
169
- const page = await wallet.signatures();
170
- setState({ loading: false, data: page.items });
171
- } catch (error) {
172
- setState({ loading: false, error });
173
- }
174
- }, [waaskey, walletId]);
175
- useEffect(() => {
176
- void refresh();
177
- }, [refresh]);
178
- return { ...state, refresh };
347
+ const load = useCallback(
348
+ async (signal) => {
349
+ const target = typeof wallet === "string" ? await waaskey.wallets.get(wallet, { signal }) : wallet;
350
+ const page = await target.signatures({}, { signal });
351
+ return page.items;
352
+ },
353
+ [waaskey, wallet]
354
+ );
355
+ return useAsyncResource(wallet ? load : void 0);
356
+ }
357
+ function useAuth() {
358
+ const store = useAuthStoreContext();
359
+ return {
360
+ session: store.session,
361
+ user: store.user,
362
+ isAuthenticated: store.session !== void 0,
363
+ ready: store.ready,
364
+ loading: store.loading,
365
+ error: store.error,
366
+ setSession: store.setSession,
367
+ refreshUser: store.refreshUser,
368
+ logout: store.logout
369
+ };
370
+ }
371
+ function useUser() {
372
+ return useAuthStoreContext().user;
373
+ }
374
+ function useAuthStoreContext() {
375
+ const store = useContext(AuthContext);
376
+ if (!store) throw new Error("useAuth must be used within a <WaaskeyProvider>.");
377
+ return store;
179
378
  }
180
379
  function useLogin() {
181
380
  const waaskey = useWaaskey();
381
+ const auth = useAuthStoreContext();
182
382
  const [step, setStep] = useState("email");
383
+ const [method, setMethod] = useState("email");
183
384
  const [email, setEmail] = useState("");
385
+ const [phone, setPhone] = useState("");
184
386
  const [session, setSession] = useState();
185
387
  const [loading, setLoading] = useState(false);
186
388
  const [error, setError] = useState();
187
- const start = useCallback(
188
- async (e) => {
189
- setLoading(true);
190
- setError(void 0);
191
- try {
192
- await waaskey.auth.email.start(e);
193
- setEmail(e);
194
- setStep("code");
195
- } catch (err) {
196
- setError(err);
197
- } finally {
198
- setLoading(false);
199
- }
389
+ const run = useCallback(async (action) => {
390
+ setLoading(true);
391
+ setError(void 0);
392
+ try {
393
+ await action();
394
+ } catch (err) {
395
+ setError(err);
396
+ } finally {
397
+ setLoading(false);
398
+ }
399
+ }, []);
400
+ const complete = useCallback(
401
+ (next) => {
402
+ setSession(next);
403
+ setStep("done");
404
+ auth.setSession(next);
200
405
  },
201
- [waaskey]
406
+ [auth]
407
+ );
408
+ const start = useCallback(
409
+ (value) => run(async () => {
410
+ await waaskey.auth.email.start(value);
411
+ setMethod("email");
412
+ setEmail(value);
413
+ setPhone("");
414
+ setStep("code");
415
+ }),
416
+ [run, waaskey]
417
+ );
418
+ const startPhone = useCallback(
419
+ (value) => run(async () => {
420
+ await waaskey.auth.phone.start(value);
421
+ setMethod("phone");
422
+ setPhone(value);
423
+ setEmail("");
424
+ setStep("code");
425
+ }),
426
+ [run, waaskey]
202
427
  );
203
428
  const verify = useCallback(
204
- async (code) => {
205
- setLoading(true);
206
- setError(void 0);
207
- try {
208
- const s = await waaskey.auth.email.verify(email, code);
209
- setSession(s);
210
- setStep("done");
211
- } catch (err) {
212
- setError(err);
213
- } finally {
214
- setLoading(false);
215
- }
216
- },
217
- [waaskey, email]
429
+ (code) => run(async () => {
430
+ complete(method === "phone" ? await waaskey.auth.phone.verify(phone, code) : await waaskey.auth.email.verify(email, code));
431
+ }),
432
+ [run, waaskey, method, email, phone, complete]
433
+ );
434
+ const loginWithGoogle = useCallback(
435
+ (idToken) => run(async () => {
436
+ setMethod("google");
437
+ complete(await waaskey.auth.social.google(idToken));
438
+ }),
439
+ [run, waaskey, complete]
440
+ );
441
+ const loginWithFirebase = useCallback(
442
+ (idToken) => run(async () => {
443
+ complete(await waaskey.auth.social.firebase(idToken));
444
+ }),
445
+ [run, waaskey, complete]
446
+ );
447
+ const loginWithPasskey = useCallback(
448
+ (ceremony) => run(async () => {
449
+ setMethod("passkey");
450
+ complete(await waaskey.auth.passkey.login(ceremony));
451
+ }),
452
+ [run, waaskey, complete]
218
453
  );
219
454
  const reset = useCallback(() => {
220
455
  setStep("email");
456
+ setMethod("email");
457
+ setEmail("");
458
+ setPhone("");
459
+ setSession(void 0);
221
460
  setError(void 0);
222
461
  }, []);
223
- return { step, email, session, loading, error, start, verify, reset };
462
+ return { step, method, email, phone, session, loading, error, start, startPhone, verify, loginWithGoogle, loginWithFirebase, loginWithPasskey, reset };
224
463
  }
225
464
  function useQrCode(text) {
226
465
  const [dataUrl, setDataUrl] = useState();
@@ -242,26 +481,54 @@ function useQrCode(text) {
242
481
  }, [text]);
243
482
  return dataUrl;
244
483
  }
245
- function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
484
+ var METHOD_LABEL = {
485
+ email: "Continue with email",
486
+ phone: "Continue with phone",
487
+ google: "Continue with Google",
488
+ passkey: "Continue with a passkey"
489
+ };
490
+ function ConnectModal({ open, onClose, onConnect, title = "Sign in", methods = ["email"], googleIdToken, theme }) {
246
491
  const base = useTheme();
247
492
  const t = { ...base, ...theme };
248
- const { step, email, session, loading, error, start, verify, reset } = useLogin();
249
- const [emailInput, setEmailInput] = useState("");
493
+ const login = useLogin();
494
+ const { step, method, email, phone, session, loading, error, start, startPhone, verify, loginWithGoogle, loginWithPasskey, reset } = login;
495
+ const [identifier, setIdentifier] = useState("");
250
496
  const [codeInput, setCodeInput] = useState("");
497
+ const [active, setActive] = useState(methods.includes("email") || !methods.includes("phone") ? "email" : "phone");
498
+ const [localError, setLocalError] = useState();
251
499
  const firstFieldRef = useRef(null);
500
+ const announced = useRef(void 0);
501
+ const onConnectRef = useRef(onConnect);
502
+ onConnectRef.current = onConnect;
252
503
  useEffect(() => {
253
- if (step === "done" && session) onConnect?.(session);
254
- }, [step, session, onConnect]);
504
+ if (step !== "done" || !session || announced.current === session.token) return;
505
+ announced.current = session.token;
506
+ onConnectRef.current?.(session);
507
+ }, [step, session]);
508
+ const onCloseRef = useRef(onClose);
509
+ onCloseRef.current = onClose;
255
510
  useEffect(() => {
256
511
  if (!open) return;
257
512
  const onKey = (e) => {
258
- if (e.key === "Escape") onClose();
513
+ if (e.key === "Escape") onCloseRef.current();
259
514
  };
260
515
  document.addEventListener("keydown", onKey);
261
- firstFieldRef.current?.focus();
262
516
  return () => document.removeEventListener("keydown", onKey);
263
- }, [open, step, onClose]);
517
+ }, [open]);
518
+ useEffect(() => {
519
+ if (open) firstFieldRef.current?.focus();
520
+ }, [open, step, active]);
521
+ const useGoogle = useCallback(async () => {
522
+ if (!googleIdToken) return;
523
+ setLocalError(void 0);
524
+ try {
525
+ await loginWithGoogle(await googleIdToken());
526
+ } catch (e) {
527
+ setLocalError(e);
528
+ }
529
+ }, [googleIdToken, loginWithGoogle]);
264
530
  if (!open) return null;
531
+ const shown = error ?? localError;
265
532
  const overlay = { position: "fixed", inset: 0, background: "rgba(0,0,0,0.4)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1e3 };
266
533
  const card = {
267
534
  background: t.background,
@@ -270,6 +537,7 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
270
537
  padding: 24,
271
538
  width: 360,
272
539
  maxWidth: "90vw",
540
+ fontFamily: t.fontFamily,
273
541
  boxShadow: "0 10px 40px rgba(0,0,0,0.2)"
274
542
  };
275
543
  const input = { width: "100%", boxSizing: "border-box", padding: "10px 12px", border: `1px solid ${t.border}`, borderRadius: "8px", fontSize: 14, marginTop: 8 };
@@ -286,43 +554,70 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
286
554
  cursor: loading ? "default" : "pointer",
287
555
  opacity: loading ? 0.6 : 1
288
556
  };
557
+ const alt = { ...button, background: "none", color: t.accent, border: `1px solid ${t.border}`, fontWeight: 500, marginTop: 8 };
289
558
  const link = { background: "none", border: "none", color: t.muted, fontSize: 13, marginTop: 12, cursor: "pointer" };
290
559
  const errText = { color: t.danger, fontSize: 13, marginTop: 8 };
291
- const onSubmitEmail = (e) => {
560
+ const onSubmitIdentifier = (e) => {
292
561
  e.preventDefault();
293
- if (emailInput.trim()) void start(emailInput.trim());
562
+ const value = identifier.trim();
563
+ if (!value) return;
564
+ void (active === "phone" ? startPhone(value) : start(value));
294
565
  };
295
566
  const onSubmitCode = (e) => {
296
567
  e.preventDefault();
297
568
  if (codeInput.trim()) void verify(codeInput.trim());
298
569
  };
299
- const close = () => {
300
- onClose();
301
- };
570
+ const close = () => onClose();
571
+ const alternatives = () => methods.filter((m) => m !== active && (m !== "google" || googleIdToken)).map(
572
+ (m) => createElement(
573
+ "button",
574
+ {
575
+ key: m,
576
+ type: "button",
577
+ style: alt,
578
+ disabled: loading,
579
+ onClick: () => {
580
+ setLocalError(void 0);
581
+ if (m === "email" || m === "phone") {
582
+ setActive(m);
583
+ setIdentifier("");
584
+ } else if (m === "passkey") void loginWithPasskey();
585
+ else void useGoogle();
586
+ }
587
+ },
588
+ METHOD_LABEL[m]
589
+ )
590
+ );
302
591
  let body;
303
592
  if (step === "email") {
593
+ const isPhone = active === "phone";
304
594
  body = createElement(
305
- "form",
306
- { onSubmit: onSubmitEmail, key: "email" },
307
- createElement("label", { style: { fontSize: 13, color: t.muted }, htmlFor: "waaskey-email" }, "Email"),
308
- createElement("input", {
309
- id: "waaskey-email",
310
- ref: firstFieldRef,
311
- style: input,
312
- type: "email",
313
- placeholder: "you@example.com",
314
- value: emailInput,
315
- onChange: (e) => setEmailInput(e.target.value),
316
- disabled: loading
317
- }),
318
- error ? createElement("div", { style: errText, role: "alert" }, error.message) : null,
319
- createElement("button", { style: button, type: "submit", disabled: loading }, loading ? "Sending\u2026" : "Send code")
595
+ "div",
596
+ { key: "identifier" },
597
+ createElement(
598
+ "form",
599
+ { onSubmit: onSubmitIdentifier },
600
+ createElement("label", { style: { fontSize: 13, color: t.muted }, htmlFor: "waaskey-identifier" }, isPhone ? "Phone" : "Email"),
601
+ createElement("input", {
602
+ id: "waaskey-identifier",
603
+ ref: firstFieldRef,
604
+ style: input,
605
+ type: isPhone ? "tel" : "email",
606
+ placeholder: isPhone ? "+15551234567" : "you@example.com",
607
+ value: identifier,
608
+ onChange: (e) => setIdentifier(e.target.value),
609
+ disabled: loading
610
+ }),
611
+ shown ? createElement("div", { style: errText, role: "alert" }, shown.message) : null,
612
+ createElement("button", { style: button, type: "submit", disabled: loading }, loading ? "Sending\u2026" : "Send code")
613
+ ),
614
+ ...alternatives()
320
615
  );
321
616
  } else if (step === "code") {
322
617
  body = createElement(
323
618
  "form",
324
619
  { onSubmit: onSubmitCode, key: "code" },
325
- createElement("div", { style: { fontSize: 13, color: t.muted } }, `Enter the code sent to ${email}`),
620
+ createElement("div", { style: { fontSize: 13, color: t.muted } }, `Enter the code sent to ${method === "phone" ? phone : email}`),
326
621
  createElement("input", {
327
622
  id: "waaskey-code",
328
623
  ref: firstFieldRef,
@@ -333,7 +628,7 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
333
628
  onChange: (e) => setCodeInput(e.target.value),
334
629
  disabled: loading
335
630
  }),
336
- error ? createElement("div", { style: errText, role: "alert" }, error.message) : null,
631
+ shown ? createElement("div", { style: errText, role: "alert" }, shown.message) : null,
337
632
  createElement("button", { style: button, type: "submit", disabled: loading }, loading ? "Verifying\u2026" : "Verify"),
338
633
  createElement(
339
634
  "button",
@@ -342,10 +637,11 @@ function ConnectModal({ open, onClose, onConnect, title = "Sign in", theme }) {
342
637
  type: "button",
343
638
  onClick: () => {
344
639
  setCodeInput("");
640
+ setIdentifier("");
345
641
  reset();
346
642
  }
347
643
  },
348
- "\u2190 Use a different email"
644
+ method === "phone" ? "\u2190 Use a different number" : "\u2190 Use a different email"
349
645
  )
350
646
  );
351
647
  } else {
@@ -398,8 +694,10 @@ function WalletWidget({ walletId, chains, sendChainId = "evm:1", sendDecimals =
398
694
  { style: s.body },
399
695
  tab === "assets" && createElement(AssetsTab, { chains, address, loading: wallet.loading, s }),
400
696
  tab === "receive" && createElement(ReceiveTab, { address, s }),
401
- tab === "send" && createElement(SendTab, { walletId, sendChainId, sendDecimals, onSent, s }),
402
- tab === "activity" && createElement(ActivityTab, { walletId, s })
697
+ // Pass the loaded wallet (not the id) so the Send/Activity tabs reuse it instead of
698
+ // each issuing its own `wallets.get` for the wallet this widget already holds.
699
+ tab === "send" && createElement(SendTab, { wallet: wallet.data ?? walletId, sendChainId, sendDecimals, onSent, s }),
700
+ tab === "activity" && createElement(ActivityTab, { wallet: wallet.data ?? walletId, s })
403
701
  )
404
702
  );
405
703
  }
@@ -428,13 +726,13 @@ function ReceiveTab({ address, s }) {
428
726
  );
429
727
  }
430
728
  function SendTab({
431
- walletId,
729
+ wallet,
432
730
  sendChainId,
433
731
  sendDecimals,
434
732
  onSent,
435
733
  s
436
734
  }) {
437
- const { send, status, result, error, reset } = useSend(walletId);
735
+ const { send, status, result, error, reset } = useSend(wallet);
438
736
  const [to, setTo] = useState("");
439
737
  const [amount, setAmount] = useState("");
440
738
  const onSubmit = (e) => {
@@ -446,8 +744,10 @@ function SendTab({
446
744
  return createElement(
447
745
  "div",
448
746
  { style: { textAlign: "center" } },
449
- createElement("div", { style: { fontWeight: 600, marginBottom: 8 } }, "Signed \u2713"),
747
+ createElement("div", { style: { fontWeight: 600, marginBottom: 4 } }, "Signed \u2713"),
748
+ createElement("div", { style: { ...s.muted, fontSize: 13, marginBottom: 8 } }, "Not broadcast yet \u2014 submit the signed transaction to a node."),
450
749
  createElement("div", { style: { ...s.muted, wordBreak: "break-all", fontFamily: "monospace" } }, result.txHash),
750
+ createElement("button", { type: "button", style: s.secondaryButton, onClick: () => void navigator.clipboard?.writeText(result.signedTx) }, "Copy signed tx"),
451
751
  createElement("button", { type: "button", style: s.secondaryButton, onClick: reset }, "Send another")
452
752
  );
453
753
  }
@@ -471,8 +771,8 @@ function SendTab({
471
771
  createElement("button", { type: "submit", style: s.button, disabled: pending }, pending ? "Sending\u2026" : "Send")
472
772
  );
473
773
  }
474
- function ActivityTab({ walletId, s }) {
475
- const { data, error, loading } = useSignatures(walletId);
774
+ function ActivityTab({ wallet, s }) {
775
+ const { data, error, loading } = useSignatures(wallet);
476
776
  if (loading) return createElement("div", { style: s.muted }, "Loading activity\u2026");
477
777
  if (error) return createElement("div", { style: s.error, role: "alert" }, error.message);
478
778
  if (!data || data.length === 0) return createElement("div", { style: s.empty }, "No signing activity yet.");
@@ -698,6 +998,6 @@ function FundWidget({ walletAddress, chainId, cryptoCurrency = "ETH", fiatCurren
698
998
  );
699
999
  }
700
1000
 
701
- export { ConnectModal, FundWidget, SignPrompt, ThemeContext, WaasProvider, WaaskeyContext, WaaskeyProvider, WalletWidget, darkTheme, defaultTheme, lightTheme, resolveTheme, useBalance, useBalances, useCreateWallet, useLogin, useQrCode, useSend, useSignPrompt, useSignatures, useTheme, useWaas, useWaaskey, useWallet };
1001
+ export { AuthContext, ConnectModal, FundWidget, SignPrompt, ThemeContext, WaasProvider, WaaskeyContext, WaaskeyProvider, WalletWidget, darkTheme, defaultTheme, lightTheme, resolveTheme, useAuth, useBalance, useBalances, useCreateWallet, useLogin, useQrCode, useSend, useSignPrompt, useSignatures, useTheme, useUser, useWaas, useWaaskey, useWallet, useWallets };
702
1002
  //# sourceMappingURL=index.js.map
703
1003
  //# sourceMappingURL=index.js.map