lens-convex-auth 0.1.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.
@@ -0,0 +1,1271 @@
1
+ "use client";
2
+ import {
3
+ CHALLENGE_MAX_AGE_MS,
4
+ CHALLENGE_REFRESH_MS,
5
+ OnboardingError,
6
+ TOKENS_CHANGED_EVENT,
7
+ authenticateOnboarding,
8
+ checkUsername,
9
+ createLensAccount,
10
+ createOnboardingClient,
11
+ isUsernameTaken,
12
+ opensWalletWithDeepLink,
13
+ requestOnboardingChallenge,
14
+ signInToCreatedAccount,
15
+ signLensChallenge,
16
+ uploadAccountMetadata,
17
+ usernameFormatError
18
+ } from "../chunk-2UCSL2CL.js";
19
+
20
+ // src/react/provider.tsx
21
+ import * as React4 from "react";
22
+ import { LensProvider } from "@lens-protocol/react";
23
+ import { ThirdwebProvider, useActiveAccount as useActiveAccount2, useActiveWallet as useActiveWallet2, useDisconnect } from "thirdweb/react";
24
+ import { ConvexProviderWithAuth, useConvexAuth } from "convex/react";
25
+
26
+ // src/react/AccountDialog.tsx
27
+ import * as React3 from "react";
28
+ import { useAccountsAvailable } from "@lens-protocol/react";
29
+ import { evmAddress } from "@lens-protocol/client";
30
+ import { Loader2 as Loader22 } from "lucide-react";
31
+
32
+ // src/react/CreateAccountForm.tsx
33
+ import * as React from "react";
34
+ import { useActiveAccount, useActiveWallet } from "thirdweb/react";
35
+ import { Check, Loader2 } from "lucide-react";
36
+
37
+ // src/react/utils.ts
38
+ import { clsx } from "clsx";
39
+ import { twMerge } from "tailwind-merge";
40
+ function cn(...inputs) {
41
+ return twMerge(clsx(inputs));
42
+ }
43
+
44
+ // src/react/ui/button.tsx
45
+ import { Slot } from "@radix-ui/react-slot";
46
+ import { cva } from "class-variance-authority";
47
+ import { jsx } from "react/jsx-runtime";
48
+ var buttonVariants = cva(
49
+ "cursor-pointer inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
50
+ {
51
+ variants: {
52
+ variant: {
53
+ default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
54
+ destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
55
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
56
+ secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
57
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
58
+ link: "text-primary underline-offset-4 hover:underline"
59
+ },
60
+ size: {
61
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
62
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
63
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
64
+ xl: "h-[50px] rounded-md px-8 has-[>svg]:px-6",
65
+ icon: "size-9"
66
+ }
67
+ },
68
+ defaultVariants: {
69
+ variant: "default",
70
+ size: "default"
71
+ }
72
+ }
73
+ );
74
+ function Button({
75
+ className,
76
+ variant,
77
+ size,
78
+ asChild = false,
79
+ ...props
80
+ }) {
81
+ const Comp = asChild ? Slot : "button";
82
+ return /* @__PURE__ */ jsx(
83
+ Comp,
84
+ {
85
+ "data-slot": "button",
86
+ className: cn(buttonVariants({ variant, size, className })),
87
+ ...props
88
+ }
89
+ );
90
+ }
91
+
92
+ // src/react/ui/input.tsx
93
+ import { jsx as jsx2 } from "react/jsx-runtime";
94
+ function Input({ className, type, ...props }) {
95
+ return /* @__PURE__ */ jsx2(
96
+ "input",
97
+ {
98
+ type,
99
+ "data-slot": "input",
100
+ className: cn(
101
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
102
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
103
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
104
+ className
105
+ ),
106
+ ...props
107
+ }
108
+ );
109
+ }
110
+
111
+ // src/react/ui/textarea.tsx
112
+ import { jsx as jsx3 } from "react/jsx-runtime";
113
+ function Textarea({ className, ...props }) {
114
+ return /* @__PURE__ */ jsx3(
115
+ "textarea",
116
+ {
117
+ "data-slot": "textarea",
118
+ className: cn(
119
+ "flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:ring-destructive/40",
120
+ className
121
+ ),
122
+ ...props
123
+ }
124
+ );
125
+ }
126
+
127
+ // src/react/ui/avatar.tsx
128
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
129
+ import { jsx as jsx4 } from "react/jsx-runtime";
130
+ function Avatar({
131
+ className,
132
+ ...props
133
+ }) {
134
+ return /* @__PURE__ */ jsx4(
135
+ AvatarPrimitive.Root,
136
+ {
137
+ "data-slot": "avatar",
138
+ className: cn(
139
+ "relative flex size-8 shrink-0 overflow-hidden rounded-full",
140
+ className
141
+ ),
142
+ ...props
143
+ }
144
+ );
145
+ }
146
+ function AvatarImage({
147
+ className,
148
+ ...props
149
+ }) {
150
+ return /* @__PURE__ */ jsx4(
151
+ AvatarPrimitive.Image,
152
+ {
153
+ "data-slot": "avatar-image",
154
+ className: cn("aspect-square size-full", className),
155
+ ...props
156
+ }
157
+ );
158
+ }
159
+ function AvatarFallback({
160
+ className,
161
+ ...props
162
+ }) {
163
+ return /* @__PURE__ */ jsx4(
164
+ AvatarPrimitive.Fallback,
165
+ {
166
+ "data-slot": "avatar-fallback",
167
+ className: cn(
168
+ "bg-muted flex size-full items-center justify-center rounded-full",
169
+ className
170
+ ),
171
+ ...props
172
+ }
173
+ );
174
+ }
175
+
176
+ // src/react/ui/field.tsx
177
+ import { useMemo } from "react";
178
+ import { cva as cva2 } from "class-variance-authority";
179
+
180
+ // src/react/ui/label.tsx
181
+ import * as LabelPrimitive from "@radix-ui/react-label";
182
+ import { jsx as jsx5 } from "react/jsx-runtime";
183
+ function Label({
184
+ className,
185
+ ...props
186
+ }) {
187
+ return /* @__PURE__ */ jsx5(
188
+ LabelPrimitive.Root,
189
+ {
190
+ "data-slot": "label",
191
+ className: cn(
192
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
193
+ className
194
+ ),
195
+ ...props
196
+ }
197
+ );
198
+ }
199
+
200
+ // src/react/ui/field.tsx
201
+ import { jsx as jsx6, jsxs } from "react/jsx-runtime";
202
+ function FieldGroup({ className, ...props }) {
203
+ return /* @__PURE__ */ jsx6(
204
+ "div",
205
+ {
206
+ "data-slot": "field-group",
207
+ className: cn(
208
+ "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
209
+ className
210
+ ),
211
+ ...props
212
+ }
213
+ );
214
+ }
215
+ var fieldVariants = cva2(
216
+ "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
217
+ {
218
+ variants: {
219
+ orientation: {
220
+ vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
221
+ horizontal: [
222
+ "flex-row items-center",
223
+ "[&>[data-slot=field-label]]:flex-auto",
224
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
225
+ ],
226
+ responsive: [
227
+ "flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
228
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
229
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px"
230
+ ]
231
+ }
232
+ },
233
+ defaultVariants: {
234
+ orientation: "vertical"
235
+ }
236
+ }
237
+ );
238
+ function Field({
239
+ className,
240
+ orientation = "vertical",
241
+ ...props
242
+ }) {
243
+ return /* @__PURE__ */ jsx6(
244
+ "div",
245
+ {
246
+ role: "group",
247
+ "data-slot": "field",
248
+ "data-orientation": orientation,
249
+ className: cn(fieldVariants({ orientation }), className),
250
+ ...props
251
+ }
252
+ );
253
+ }
254
+ function FieldContent({ className, ...props }) {
255
+ return /* @__PURE__ */ jsx6(
256
+ "div",
257
+ {
258
+ "data-slot": "field-content",
259
+ className: cn(
260
+ "group/field-content flex flex-1 flex-col gap-1.5 leading-snug",
261
+ className
262
+ ),
263
+ ...props
264
+ }
265
+ );
266
+ }
267
+ function FieldLabel({
268
+ className,
269
+ ...props
270
+ }) {
271
+ return /* @__PURE__ */ jsx6(
272
+ Label,
273
+ {
274
+ "data-slot": "field-label",
275
+ className: cn(
276
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
277
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
278
+ "has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
279
+ className
280
+ ),
281
+ ...props
282
+ }
283
+ );
284
+ }
285
+ function FieldDescription({ className, ...props }) {
286
+ return /* @__PURE__ */ jsx6(
287
+ "p",
288
+ {
289
+ "data-slot": "field-description",
290
+ className: cn(
291
+ "text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
292
+ "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
293
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
294
+ className
295
+ ),
296
+ ...props
297
+ }
298
+ );
299
+ }
300
+
301
+ // src/react/ui/input-group.tsx
302
+ import { cva as cva3 } from "class-variance-authority";
303
+ import { jsx as jsx7 } from "react/jsx-runtime";
304
+ function InputGroup({ className, ...props }) {
305
+ return /* @__PURE__ */ jsx7(
306
+ "div",
307
+ {
308
+ "data-slot": "input-group",
309
+ role: "group",
310
+ className: cn(
311
+ "group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
312
+ "h-9 min-w-0 has-[>textarea]:h-auto",
313
+ // Variants based on alignment.
314
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
315
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
316
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
317
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
318
+ // Focus state.
319
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
320
+ // Error state.
321
+ "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
322
+ className
323
+ ),
324
+ ...props
325
+ }
326
+ );
327
+ }
328
+ var inputGroupAddonVariants = cva3(
329
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
330
+ {
331
+ variants: {
332
+ align: {
333
+ "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
334
+ "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
335
+ "block-start": "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
336
+ "block-end": "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3"
337
+ }
338
+ },
339
+ defaultVariants: {
340
+ align: "inline-start"
341
+ }
342
+ }
343
+ );
344
+ function InputGroupAddon({
345
+ className,
346
+ align = "inline-start",
347
+ ...props
348
+ }) {
349
+ return /* @__PURE__ */ jsx7(
350
+ "div",
351
+ {
352
+ role: "group",
353
+ "data-slot": "input-group-addon",
354
+ "data-align": align,
355
+ className: cn(inputGroupAddonVariants({ align }), className),
356
+ onClick: (e) => {
357
+ if (e.target.closest("button")) {
358
+ return;
359
+ }
360
+ e.currentTarget.parentElement?.querySelector("input")?.focus();
361
+ },
362
+ ...props
363
+ }
364
+ );
365
+ }
366
+ var inputGroupButtonVariants = cva3(
367
+ "flex items-center gap-2 text-sm shadow-none",
368
+ {
369
+ variants: {
370
+ size: {
371
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
372
+ sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
373
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
374
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0"
375
+ }
376
+ },
377
+ defaultVariants: {
378
+ size: "xs"
379
+ }
380
+ }
381
+ );
382
+ function InputGroupText({ className, ...props }) {
383
+ return /* @__PURE__ */ jsx7(
384
+ "span",
385
+ {
386
+ className: cn(
387
+ "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
388
+ className
389
+ ),
390
+ ...props
391
+ }
392
+ );
393
+ }
394
+ function InputGroupInput({
395
+ className,
396
+ ...props
397
+ }) {
398
+ return /* @__PURE__ */ jsx7(
399
+ Input,
400
+ {
401
+ "data-slot": "input-group-control",
402
+ className: cn(
403
+ "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
404
+ className
405
+ ),
406
+ ...props
407
+ }
408
+ );
409
+ }
410
+
411
+ // src/react/CreateAccountForm.tsx
412
+ import { jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
413
+ var STEP_LABELS = {
414
+ signing: "Waiting for signature\u2026",
415
+ sign: "Sign in wallet",
416
+ checking: "Checking username\u2026",
417
+ uploading: "Uploading profile\u2026",
418
+ creating: "Creating account\u2026",
419
+ "signing-in": "Signing in\u2026"
420
+ };
421
+ var USERNAME_DEBOUNCE_MS = 300;
422
+ var MAX_PICTURE_BYTES = 10 * 1024 * 1024;
423
+ function CreateAccountForm({ ownerAddress, onCreated, onBack }) {
424
+ const { auth } = useLensAuth();
425
+ const activeAccount = useActiveAccount();
426
+ const wallet = useActiveWallet();
427
+ const [client] = React.useState(() => createOnboardingClient(auth));
428
+ const session = React.useRef(null);
429
+ const created = React.useRef(null);
430
+ const pictureInput = React.useRef(null);
431
+ const [username, setUsername] = React.useState("");
432
+ const [debouncedUsername, setDebouncedUsername] = React.useState("");
433
+ const [name, setName] = React.useState("");
434
+ const [bio, setBio] = React.useState("");
435
+ const [picture, setPicture] = React.useState(null);
436
+ const [pictureUrl, setPictureUrl] = React.useState(null);
437
+ const [availability, setAvailability] = React.useState(null);
438
+ const [usernameError, setUsernameError] = React.useState(null);
439
+ const [step, setStep] = React.useState(null);
440
+ const [error, setError] = React.useState(null);
441
+ const [prefetched, setPrefetched] = React.useState(null);
442
+ const formatError = username ? usernameFormatError(username) : null;
443
+ const busy = step !== null && step.status !== "sign";
444
+ React.useEffect(() => {
445
+ const refresh = () => {
446
+ if (document.visibilityState !== "visible" || session.current) return;
447
+ requestOnboardingChallenge(auth, client, ownerAddress).then((challenge) => setPrefetched({ challenge, fetchedAt: Date.now() })).catch(() => {
448
+ });
449
+ };
450
+ refresh();
451
+ const interval = setInterval(refresh, CHALLENGE_REFRESH_MS);
452
+ document.addEventListener("visibilitychange", refresh);
453
+ return () => {
454
+ clearInterval(interval);
455
+ document.removeEventListener("visibilitychange", refresh);
456
+ };
457
+ }, [auth, client, ownerAddress]);
458
+ React.useEffect(() => () => {
459
+ if (!created.current) void session.current?.logout();
460
+ }, []);
461
+ React.useEffect(() => {
462
+ if (!picture) return setPictureUrl(null);
463
+ const url = URL.createObjectURL(picture);
464
+ setPictureUrl(url);
465
+ return () => URL.revokeObjectURL(url);
466
+ }, [picture]);
467
+ React.useEffect(() => {
468
+ const timeout = setTimeout(() => setDebouncedUsername(username), USERNAME_DEBOUNCE_MS);
469
+ return () => clearTimeout(timeout);
470
+ }, [username]);
471
+ React.useEffect(() => {
472
+ if (!debouncedUsername || usernameFormatError(debouncedUsername)) return;
473
+ let cancelled = false;
474
+ setAvailability({ localName: debouncedUsername, status: "checking" });
475
+ isUsernameTaken(auth, debouncedUsername).then((taken) => !cancelled && setAvailability({ localName: debouncedUsername, status: taken ? "taken" : "available" })).catch(() => !cancelled && setAvailability({ localName: debouncedUsername, status: "unknown" }));
476
+ return () => {
477
+ cancelled = true;
478
+ };
479
+ }, [auth, debouncedUsername]);
480
+ const fail = (e) => {
481
+ setStep(null);
482
+ if (e?.name === "UnauthenticatedError") session.current = null;
483
+ setError(e instanceof OnboardingError ? e.message : "Couldn\u2019t create your account. Please try again.");
484
+ };
485
+ const create = async (onboarding) => {
486
+ const localName = username;
487
+ if (!created.current) {
488
+ setStep({ status: "checking" });
489
+ const reason = await checkUsername(onboarding, localName);
490
+ if (reason) {
491
+ setUsernameError({ localName, message: reason });
492
+ setStep(null);
493
+ return;
494
+ }
495
+ setStep({ status: "uploading" });
496
+ const metadataUri = await uploadAccountMetadata(auth, { name: name.trim(), bio: bio.trim(), picture });
497
+ setStep({ status: "creating" });
498
+ if (!wallet) throw new OnboardingError("Connect your wallet");
499
+ const account = await createLensAccount(auth, onboarding, wallet, { localName, metadataUri });
500
+ created.current = { address: account.address, localName: account.username?.localName ?? localName };
501
+ }
502
+ setStep({ status: "signing-in" });
503
+ await signInToCreatedAccount(auth, onboarding, created.current.address);
504
+ await onCreated(created.current);
505
+ };
506
+ const continueWith = (authenticating) => {
507
+ authenticating.then(async (onboarding) => {
508
+ if (!onboarding) return;
509
+ session.current = onboarding;
510
+ await create(onboarding);
511
+ }).catch(fail);
512
+ };
513
+ const readyChallenge = () => {
514
+ if (step?.status === "sign") return step.challenge;
515
+ if (!prefetched || Date.now() - prefetched.fetchedAt >= CHALLENGE_MAX_AGE_MS) return null;
516
+ return prefetched.challenge;
517
+ };
518
+ const handleSubmit = (e) => {
519
+ e.preventDefault();
520
+ if (busy || !activeAccount) return;
521
+ if (!username) return setUsernameError({ localName: username, message: "Choose a username" });
522
+ if (formatError || availability?.status === "taken") return;
523
+ setError(null);
524
+ setUsernameError(null);
525
+ if (session.current) {
526
+ setStep({ status: "checking" });
527
+ create(session.current).catch(fail);
528
+ return;
529
+ }
530
+ const challenge = readyChallenge();
531
+ if (challenge) {
532
+ const signing = signLensChallenge(challenge, activeAccount);
533
+ setPrefetched(null);
534
+ setStep({ status: "signing" });
535
+ continueWith(signing.then((signature) => authenticateOnboarding(client, challenge, signature)));
536
+ return;
537
+ }
538
+ setStep({ status: "signing" });
539
+ continueWith((async () => {
540
+ const fetched = await requestOnboardingChallenge(auth, client, ownerAddress);
541
+ if (opensWalletWithDeepLink()) {
542
+ setStep({ status: "sign", challenge: fetched });
543
+ return null;
544
+ }
545
+ const signature = await signLensChallenge(fetched, activeAccount);
546
+ return authenticateOnboarding(client, fetched, signature);
547
+ })());
548
+ };
549
+ const handlePictureChange = (e) => {
550
+ const file = e.target.files?.[0] ?? null;
551
+ e.target.value = "";
552
+ if (file && file.size > MAX_PICTURE_BYTES) return setError("Choose a picture under 10 MB");
553
+ setError(null);
554
+ setPicture(file);
555
+ };
556
+ const usernameMessage = (() => {
557
+ if (formatError) return { text: formatError, isError: true };
558
+ if (usernameError?.localName === username) return { text: usernameError.message, isError: true };
559
+ if (availability?.localName !== username) return null;
560
+ switch (availability.status) {
561
+ case "checking":
562
+ return { text: "Checking availability\u2026", isError: false };
563
+ case "available":
564
+ return { text: "Available", isError: false };
565
+ case "taken":
566
+ return { text: "This username is taken", isError: true };
567
+ default:
568
+ return null;
569
+ }
570
+ })();
571
+ const fallbackInitial = (name.trim() || username || "?").charAt(0).toUpperCase();
572
+ const usernameStatus = availability?.localName === username && !formatError ? availability.status : null;
573
+ return /* @__PURE__ */ jsx8("form", { className: "py-4", onSubmit: handleSubmit, noValidate: true, children: /* @__PURE__ */ jsxs2(FieldGroup, { className: "gap-5", children: [
574
+ /* @__PURE__ */ jsxs2(Field, { orientation: "horizontal", children: [
575
+ /* @__PURE__ */ jsxs2(Avatar, { className: "size-16", children: [
576
+ pictureUrl && /* @__PURE__ */ jsx8(AvatarImage, { src: pictureUrl, alt: "", className: "object-cover" }),
577
+ /* @__PURE__ */ jsx8(AvatarFallback, { className: "text-lg font-semibold", children: fallbackInitial })
578
+ ] }),
579
+ /* @__PURE__ */ jsxs2(FieldContent, { children: [
580
+ /* @__PURE__ */ jsxs2(FieldLabel, { htmlFor: "create-account-picture", children: [
581
+ "Profile picture ",
582
+ /* @__PURE__ */ jsx8("span", { className: "font-normal text-muted-foreground", children: "(optional)" })
583
+ ] }),
584
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
585
+ /* @__PURE__ */ jsx8(Button, { type: "button", variant: "outline", size: "sm", onClick: () => pictureInput.current?.click(), disabled: busy, children: picture ? "Change" : "Add photo" }),
586
+ picture && /* @__PURE__ */ jsx8(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setPicture(null), disabled: busy, children: "Remove" })
587
+ ] })
588
+ ] }),
589
+ /* @__PURE__ */ jsx8(
590
+ "input",
591
+ {
592
+ ref: pictureInput,
593
+ id: "create-account-picture",
594
+ type: "file",
595
+ accept: "image/*",
596
+ className: "sr-only",
597
+ onChange: handlePictureChange,
598
+ disabled: busy
599
+ }
600
+ )
601
+ ] }),
602
+ /* @__PURE__ */ jsxs2(Field, { "data-invalid": usernameMessage?.isError || void 0, children: [
603
+ /* @__PURE__ */ jsx8(FieldLabel, { htmlFor: "create-account-username", children: "Username" }),
604
+ /* @__PURE__ */ jsxs2(InputGroup, { children: [
605
+ /* @__PURE__ */ jsx8(InputGroupAddon, { children: /* @__PURE__ */ jsx8(InputGroupText, { children: "@" }) }),
606
+ /* @__PURE__ */ jsx8(
607
+ InputGroupInput,
608
+ {
609
+ id: "create-account-username",
610
+ value: username,
611
+ onChange: (e) => setUsername(e.target.value.trim().toLowerCase()),
612
+ autoComplete: "off",
613
+ autoCapitalize: "none",
614
+ autoCorrect: "off",
615
+ spellCheck: false,
616
+ "aria-invalid": usernameMessage?.isError || void 0,
617
+ "aria-describedby": "create-account-username-status",
618
+ disabled: busy
619
+ }
620
+ ),
621
+ /* @__PURE__ */ jsxs2(InputGroupAddon, { align: "inline-end", children: [
622
+ usernameStatus === "checking" && /* @__PURE__ */ jsx8(Loader2, { className: "animate-spin" }),
623
+ usernameStatus === "available" && /* @__PURE__ */ jsx8(Check, {})
624
+ ] })
625
+ ] }),
626
+ /* @__PURE__ */ jsx8(
627
+ FieldDescription,
628
+ {
629
+ id: "create-account-username-status",
630
+ "aria-live": "polite",
631
+ className: cn("min-h-5 leading-5", usernameMessage?.isError && "text-destructive"),
632
+ children: usernameMessage?.text
633
+ }
634
+ )
635
+ ] }),
636
+ /* @__PURE__ */ jsxs2(Field, { children: [
637
+ /* @__PURE__ */ jsxs2(FieldLabel, { htmlFor: "create-account-name", children: [
638
+ "Name ",
639
+ /* @__PURE__ */ jsx8("span", { className: "font-normal text-muted-foreground", children: "(optional)" })
640
+ ] }),
641
+ /* @__PURE__ */ jsx8(Input, { id: "create-account-name", value: name, onChange: (e) => setName(e.target.value), maxLength: 100, disabled: busy })
642
+ ] }),
643
+ /* @__PURE__ */ jsxs2(Field, { children: [
644
+ /* @__PURE__ */ jsxs2(FieldLabel, { htmlFor: "create-account-bio", children: [
645
+ "Bio ",
646
+ /* @__PURE__ */ jsx8("span", { className: "font-normal text-muted-foreground", children: "(optional)" })
647
+ ] }),
648
+ /* @__PURE__ */ jsx8(
649
+ Textarea,
650
+ {
651
+ id: "create-account-bio",
652
+ value: bio,
653
+ onChange: (e) => setBio(e.target.value),
654
+ maxLength: 300,
655
+ rows: 3,
656
+ className: "resize-none",
657
+ disabled: busy
658
+ }
659
+ )
660
+ ] }),
661
+ /* @__PURE__ */ jsxs2(Field, { className: "gap-2", children: [
662
+ /* @__PURE__ */ jsxs2(Button, { type: "submit", className: "w-full", disabled: busy || !activeAccount, children: [
663
+ busy && /* @__PURE__ */ jsx8(Loader2, { className: "animate-spin" }),
664
+ step ? STEP_LABELS[step.status] : "Create account"
665
+ ] }),
666
+ /* @__PURE__ */ jsx8(FieldDescription, { "aria-live": "polite", className: cn("min-h-5 leading-5", error && "text-destructive"), children: error ?? (step?.status === "sign" ? "Tap again to sign in your wallet." : "You\u2019ll sign a message in your wallet. It\u2019s free.") }),
667
+ onBack && /* @__PURE__ */ jsx8(Button, { type: "button", variant: "ghost", className: "w-full", onClick: onBack, disabled: busy, children: "Back to your accounts" })
668
+ ] })
669
+ ] }) });
670
+ }
671
+
672
+ // src/react/dom.ts
673
+ import * as React2 from "react";
674
+ function useMediaQuery(query) {
675
+ return React2.useSyncExternalStore(
676
+ (onChange) => {
677
+ const media = window.matchMedia(query);
678
+ media.addEventListener("change", onChange);
679
+ return () => media.removeEventListener("change", onChange);
680
+ },
681
+ () => window.matchMedia(query).matches,
682
+ () => false
683
+ );
684
+ }
685
+ function readTheme() {
686
+ const classes = document.documentElement.classList;
687
+ if (classes.contains("dark")) return "dark";
688
+ if (classes.contains("light")) return "light";
689
+ return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
690
+ }
691
+ function useDocumentTheme() {
692
+ return React2.useSyncExternalStore(
693
+ (onChange) => {
694
+ const observer = new MutationObserver(onChange);
695
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] });
696
+ const media = window.matchMedia("(prefers-color-scheme: dark)");
697
+ media.addEventListener("change", onChange);
698
+ return () => {
699
+ observer.disconnect();
700
+ media.removeEventListener("change", onChange);
701
+ };
702
+ },
703
+ readTheme,
704
+ () => "light"
705
+ );
706
+ }
707
+
708
+ // src/react/ui/dialog.tsx
709
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
710
+ import { XIcon } from "lucide-react";
711
+ import { jsx as jsx9, jsxs as jsxs3 } from "react/jsx-runtime";
712
+ function Dialog({
713
+ ...props
714
+ }) {
715
+ return /* @__PURE__ */ jsx9(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
716
+ }
717
+ function DialogPortal({
718
+ ...props
719
+ }) {
720
+ return /* @__PURE__ */ jsx9(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
721
+ }
722
+ function DialogOverlay({
723
+ className,
724
+ ...props
725
+ }) {
726
+ return /* @__PURE__ */ jsx9(
727
+ DialogPrimitive.Overlay,
728
+ {
729
+ "data-slot": "dialog-overlay",
730
+ className: cn(
731
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
732
+ className
733
+ ),
734
+ ...props
735
+ }
736
+ );
737
+ }
738
+ function DialogContent({
739
+ className,
740
+ children,
741
+ showCloseButton = true,
742
+ ...props
743
+ }) {
744
+ return /* @__PURE__ */ jsxs3(DialogPortal, { "data-slot": "dialog-portal", children: [
745
+ /* @__PURE__ */ jsx9(DialogOverlay, {}),
746
+ /* @__PURE__ */ jsxs3(
747
+ DialogPrimitive.Content,
748
+ {
749
+ "data-slot": "dialog-content",
750
+ className: cn(
751
+ "bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
752
+ className
753
+ ),
754
+ ...props,
755
+ children: [
756
+ children,
757
+ showCloseButton && /* @__PURE__ */ jsxs3(
758
+ DialogPrimitive.Close,
759
+ {
760
+ "data-slot": "dialog-close",
761
+ className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
762
+ children: [
763
+ /* @__PURE__ */ jsx9(XIcon, {}),
764
+ /* @__PURE__ */ jsx9("span", { className: "sr-only", children: "Close" })
765
+ ]
766
+ }
767
+ )
768
+ ]
769
+ }
770
+ )
771
+ ] });
772
+ }
773
+ function DialogHeader({ className, ...props }) {
774
+ return /* @__PURE__ */ jsx9(
775
+ "div",
776
+ {
777
+ "data-slot": "dialog-header",
778
+ className: cn("flex flex-col gap-2 text-center sm:text-left", className),
779
+ ...props
780
+ }
781
+ );
782
+ }
783
+ function DialogTitle({
784
+ className,
785
+ ...props
786
+ }) {
787
+ return /* @__PURE__ */ jsx9(
788
+ DialogPrimitive.Title,
789
+ {
790
+ "data-slot": "dialog-title",
791
+ className: cn("text-lg leading-none font-semibold", className),
792
+ ...props
793
+ }
794
+ );
795
+ }
796
+ function DialogDescription({
797
+ className,
798
+ ...props
799
+ }) {
800
+ return /* @__PURE__ */ jsx9(
801
+ DialogPrimitive.Description,
802
+ {
803
+ "data-slot": "dialog-description",
804
+ className: cn("text-muted-foreground text-sm", className),
805
+ ...props
806
+ }
807
+ );
808
+ }
809
+
810
+ // src/react/ui/drawer.tsx
811
+ import { Drawer as DrawerPrimitive } from "vaul";
812
+ import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
813
+ function Drawer({
814
+ ...props
815
+ }) {
816
+ return /* @__PURE__ */ jsx10(DrawerPrimitive.Root, { "data-slot": "drawer", ...props });
817
+ }
818
+ function DrawerPortal({
819
+ ...props
820
+ }) {
821
+ return /* @__PURE__ */ jsx10(DrawerPrimitive.Portal, { "data-slot": "drawer-portal", ...props });
822
+ }
823
+ function DrawerOverlay({
824
+ className,
825
+ ...props
826
+ }) {
827
+ return /* @__PURE__ */ jsx10(
828
+ DrawerPrimitive.Overlay,
829
+ {
830
+ "data-slot": "drawer-overlay",
831
+ className: cn(
832
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
833
+ className
834
+ ),
835
+ ...props
836
+ }
837
+ );
838
+ }
839
+ function DrawerContent({
840
+ className,
841
+ children,
842
+ ...props
843
+ }) {
844
+ return /* @__PURE__ */ jsxs4(DrawerPortal, { "data-slot": "drawer-portal", children: [
845
+ /* @__PURE__ */ jsx10(DrawerOverlay, {}),
846
+ /* @__PURE__ */ jsxs4(
847
+ DrawerPrimitive.Content,
848
+ {
849
+ "data-slot": "drawer-content",
850
+ className: cn(
851
+ "group/drawer-content bg-background fixed z-50 flex h-auto flex-col",
852
+ "data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
853
+ "data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
854
+ "data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
855
+ "data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
856
+ className
857
+ ),
858
+ ...props,
859
+ children: [
860
+ /* @__PURE__ */ jsx10("div", { className: "bg-muted mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block" }),
861
+ children
862
+ ]
863
+ }
864
+ )
865
+ ] });
866
+ }
867
+ function DrawerHeader({ className, ...props }) {
868
+ return /* @__PURE__ */ jsx10(
869
+ "div",
870
+ {
871
+ "data-slot": "drawer-header",
872
+ className: cn(
873
+ "flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
874
+ className
875
+ ),
876
+ ...props
877
+ }
878
+ );
879
+ }
880
+ function DrawerTitle({
881
+ className,
882
+ ...props
883
+ }) {
884
+ return /* @__PURE__ */ jsx10(
885
+ DrawerPrimitive.Title,
886
+ {
887
+ "data-slot": "drawer-title",
888
+ className: cn("text-foreground font-semibold", className),
889
+ ...props
890
+ }
891
+ );
892
+ }
893
+ function DrawerDescription({
894
+ className,
895
+ ...props
896
+ }) {
897
+ return /* @__PURE__ */ jsx10(
898
+ DrawerPrimitive.Description,
899
+ {
900
+ "data-slot": "drawer-description",
901
+ className: cn("text-muted-foreground text-sm", className),
902
+ ...props
903
+ }
904
+ );
905
+ }
906
+
907
+ // src/react/AccountDialog.tsx
908
+ import { Fragment, jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
909
+ function AccountDialog() {
910
+ const { wallet, signOut } = useLensAuth();
911
+ const { open, required, setOpen } = useAccountDialogState();
912
+ const isDesktop = useMediaQuery("(min-width: 768px)");
913
+ const [creating, setCreating] = React3.useState(false);
914
+ React3.useEffect(() => {
915
+ if (!open) setCreating(false);
916
+ }, [open]);
917
+ const handleOpenChange = (next) => {
918
+ if (!next && required) return;
919
+ setOpen(next);
920
+ };
921
+ const body = wallet ? /* @__PURE__ */ jsx11(AccountDialogBody, { wallet, creating, setCreating }, wallet.address) : null;
922
+ const disconnect = /* @__PURE__ */ jsx11(Button, { onClick: signOut, variant: "outline", className: "w-full", children: "Disconnect Wallet" });
923
+ if (isDesktop) {
924
+ return /* @__PURE__ */ jsx11(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs5(
925
+ DialogContent,
926
+ {
927
+ className: "sm:max-w-[425px] max-h-[80vh] overflow-y-auto",
928
+ showCloseButton: !required,
929
+ onEscapeKeyDown: (e) => required && e.preventDefault(),
930
+ onPointerDownOutside: (e) => required && e.preventDefault(),
931
+ children: [
932
+ body,
933
+ /* @__PURE__ */ jsx11("div", { className: "mt-4", children: disconnect })
934
+ ]
935
+ }
936
+ ) });
937
+ }
938
+ return /* @__PURE__ */ jsx11(Drawer, { open, onOpenChange: handleOpenChange, dismissible: !required, children: /* @__PURE__ */ jsxs5(DrawerContent, { className: "max-h-[80vh] overflow-y-auto", children: [
939
+ body,
940
+ /* @__PURE__ */ jsx11("div", { className: "p-4", children: disconnect })
941
+ ] }) });
942
+ }
943
+ function AccountDialogBody({ wallet, creating, setCreating }) {
944
+ const { auth } = useLensAuth();
945
+ const { completeSignIn } = useAccountDialogState();
946
+ const isDesktop = useMediaQuery("(min-width: 768px)");
947
+ const { data, loading } = useAccountsAvailable({ managedBy: evmAddress(wallet.address), includeOwned: true });
948
+ const accounts = data?.items;
949
+ const hasAccounts = !!accounts?.some((item) => item.account.username?.localName);
950
+ const showCreate = creating || !loading && !hasAccounts;
951
+ const signIn = async (account, challenge) => {
952
+ const signature = await auth.signChallenge(challenge, wallet);
953
+ await auth.authenticate(challenge, signature);
954
+ completeSignIn({ address: account.address.toLowerCase(), username: account.username?.localName ?? null });
955
+ };
956
+ const handleCreated = async (created) => {
957
+ completeSignIn({ address: created.address.toLowerCase(), username: created.localName });
958
+ };
959
+ const title = showCreate ? "Create Account" : "Select Account";
960
+ const description = showCreate ? hasAccounts ? "Pick a username for your new Lens account." : "You don\u2019t have a Lens account yet. Create one to continue." : "Select an account to continue.";
961
+ const Header = isDesktop ? DialogHeader : DrawerHeader;
962
+ const Title2 = isDesktop ? DialogTitle : DrawerTitle;
963
+ const Description2 = isDesktop ? DialogDescription : DrawerDescription;
964
+ return /* @__PURE__ */ jsxs5(Fragment, { children: [
965
+ /* @__PURE__ */ jsxs5(Header, { className: isDesktop ? void 0 : "text-left", children: [
966
+ /* @__PURE__ */ jsx11(Title2, { children: title }),
967
+ /* @__PURE__ */ jsx11(Description2, { children: description })
968
+ ] }),
969
+ /* @__PURE__ */ jsx11("div", { className: isDesktop || !showCreate ? void 0 : "px-4", children: showCreate ? /* @__PURE__ */ jsx11(
970
+ CreateAccountForm,
971
+ {
972
+ ownerAddress: wallet.address,
973
+ onCreated: handleCreated,
974
+ onBack: hasAccounts ? () => setCreating(false) : void 0
975
+ }
976
+ ) : /* @__PURE__ */ jsx11(AccountList, { auth, accounts, loading, ownerAddress: wallet.address, onSign: signIn, onCreate: () => setCreating(true) }) })
977
+ ] });
978
+ }
979
+ function fetchChallenge(auth, acc, ownerAddress) {
980
+ return auth.requestChallenge({
981
+ role: acc.isOwner ? "ACCOUNT_OWNER" : "ACCOUNT_MANAGER",
982
+ account: acc.address,
983
+ signer: ownerAddress
984
+ });
985
+ }
986
+ function AccountList({ auth, accounts, loading, ownerAddress, onSign, onCreate }) {
987
+ const [step, setStep] = React3.useState(null);
988
+ const [errorId, setErrorId] = React3.useState(null);
989
+ const [challenges, setChallenges] = React3.useState({});
990
+ const selectable = React3.useMemo(() => (accounts ?? []).map((item, index) => {
991
+ const acc = { ...item.account, isOwner: item.__typename === "AccountOwned" };
992
+ return { acc, id: `${acc.address}-${index}` };
993
+ }), [accounts]);
994
+ const prefetch = React3.useCallback((acc, id) => {
995
+ fetchChallenge(auth, acc, ownerAddress).then((challenge) => setChallenges((prev) => ({ ...prev, [id]: { owner: ownerAddress, challenge, fetchedAt: Date.now() } }))).catch(() => {
996
+ });
997
+ }, [auth, ownerAddress]);
998
+ React3.useEffect(() => {
999
+ const refresh = () => {
1000
+ if (document.visibilityState !== "visible") return;
1001
+ for (const { acc, id } of selectable) {
1002
+ if (acc.username?.localName) prefetch(acc, id);
1003
+ }
1004
+ };
1005
+ refresh();
1006
+ const interval = setInterval(refresh, CHALLENGE_REFRESH_MS);
1007
+ document.addEventListener("visibilitychange", refresh);
1008
+ return () => {
1009
+ clearInterval(interval);
1010
+ document.removeEventListener("visibilitychange", refresh);
1011
+ };
1012
+ }, [selectable, prefetch]);
1013
+ const readyChallenge = (id) => {
1014
+ if (step?.id === id && step.status === "sign") return step.challenge;
1015
+ const prefetched = challenges[id];
1016
+ if (!prefetched || prefetched.owner !== ownerAddress) return null;
1017
+ return Date.now() - prefetched.fetchedAt < CHALLENGE_MAX_AGE_MS ? prefetched.challenge : null;
1018
+ };
1019
+ const handleClick = (acc, id) => {
1020
+ setErrorId(null);
1021
+ const fail = () => {
1022
+ setStep(null);
1023
+ setErrorId(id);
1024
+ prefetch(acc, id);
1025
+ };
1026
+ const challenge = readyChallenge(id);
1027
+ if (challenge) {
1028
+ const signing = onSign(acc, challenge);
1029
+ setChallenges(({ [id]: _used, ...rest }) => rest);
1030
+ setStep({ id, status: "loading" });
1031
+ signing.then(() => setStep(null), fail);
1032
+ return;
1033
+ }
1034
+ setStep({ id, status: "loading" });
1035
+ (async () => {
1036
+ const fetched = await fetchChallenge(auth, acc, ownerAddress);
1037
+ if (opensWalletWithDeepLink()) return setStep({ id, status: "sign", challenge: fetched });
1038
+ await onSign(acc, fetched);
1039
+ setStep(null);
1040
+ })().catch(fail);
1041
+ };
1042
+ if (loading) {
1043
+ return /* @__PURE__ */ jsx11("div", { className: "p-4", children: /* @__PURE__ */ jsx11("p", { className: "text-sm text-muted-foreground", children: "Loading available profiles\u2026" }) });
1044
+ }
1045
+ return /* @__PURE__ */ jsxs5("div", { className: "py-4 space-y-3", children: [
1046
+ /* @__PURE__ */ jsx11("ul", { className: "space-y-2 max-h-80 overflow-y-auto", children: selectable.map(({ acc, id }) => {
1047
+ const username = acc.username?.localName;
1048
+ if (!username) return null;
1049
+ const isLoading = step?.id === id && step.status === "loading";
1050
+ const awaitingSignature = step?.id === id && step.status === "sign";
1051
+ return /* @__PURE__ */ jsxs5("li", { children: [
1052
+ /* @__PURE__ */ jsxs5(
1053
+ "button",
1054
+ {
1055
+ type: "button",
1056
+ className: "w-full text-left px-3 py-2 rounded-md border hover:bg-accent/50 flex items-center justify-between cursor-pointer disabled:cursor-default",
1057
+ onClick: () => handleClick(acc, id),
1058
+ disabled: step?.status === "loading",
1059
+ children: [
1060
+ /* @__PURE__ */ jsx11("span", { className: "truncate", children: username }),
1061
+ isLoading && /* @__PURE__ */ jsx11(Loader22, { className: "size-4 animate-spin text-muted-foreground" }),
1062
+ awaitingSignature && /* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-primary", children: "Sign in" })
1063
+ ]
1064
+ }
1065
+ ),
1066
+ awaitingSignature && /* @__PURE__ */ jsx11("p", { className: "px-1 pt-1 text-xs text-muted-foreground", children: "Tap again to sign the message in your wallet." }),
1067
+ errorId === id && /* @__PURE__ */ jsx11("p", { className: "px-1 pt-1 text-xs text-destructive", children: "Couldn\u2019t sign in. Please try again." })
1068
+ ] }, id);
1069
+ }) }),
1070
+ /* @__PURE__ */ jsx11(Button, { variant: "ghost", className: "w-full", onClick: onCreate, disabled: step?.status === "loading", children: "Create a new account" })
1071
+ ] });
1072
+ }
1073
+
1074
+ // src/react/provider.tsx
1075
+ import { jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
1076
+ var LensAuthContext = React4.createContext(null);
1077
+ var AccountDialogContext = React4.createContext(null);
1078
+ function useLensAuth() {
1079
+ const ctx = React4.useContext(LensAuthContext);
1080
+ if (!ctx) throw new Error("useLensAuth must be used within <LensAuthProvider>");
1081
+ return ctx;
1082
+ }
1083
+ function useAccountDialogState() {
1084
+ const ctx = React4.useContext(AccountDialogContext);
1085
+ if (!ctx) throw new Error("<AccountDialog> must be used within <LensAuthProvider>");
1086
+ return ctx;
1087
+ }
1088
+ function subscribeToTokens(onChange) {
1089
+ window.addEventListener(TOKENS_CHANGED_EVENT, onChange);
1090
+ window.addEventListener("storage", onChange);
1091
+ return () => {
1092
+ window.removeEventListener(TOKENS_CHANGED_EVENT, onChange);
1093
+ window.removeEventListener("storage", onChange);
1094
+ };
1095
+ }
1096
+ function useSessionKey(auth) {
1097
+ return React4.useSyncExternalStore(
1098
+ subscribeToTokens,
1099
+ () => {
1100
+ const session = auth.getSession();
1101
+ return session ? `${session.sessionId}:${session.account}` : null;
1102
+ },
1103
+ () => void 0
1104
+ );
1105
+ }
1106
+ function createUseConvexAuth(auth) {
1107
+ return function useLensConvexAuth() {
1108
+ const sessionKey = useSessionKey(auth);
1109
+ const fetchAccessToken = React4.useCallback(
1110
+ async ({ forceRefreshToken }) => sessionKey ? auth.fetchConvexToken(forceRefreshToken) : null,
1111
+ [sessionKey]
1112
+ );
1113
+ return { isLoading: sessionKey === void 0, isAuthenticated: !!sessionKey, fetchAccessToken };
1114
+ };
1115
+ }
1116
+ function LensAuthProvider({ auth, convex, onSignIn, accountDialog = true, children }) {
1117
+ const [useConvexAuthHook] = React4.useState(() => createUseConvexAuth(auth));
1118
+ const content = /* @__PURE__ */ jsxs6(LensAuthState, { auth, onSignIn, withConvex: !!convex, children: [
1119
+ children,
1120
+ accountDialog && /* @__PURE__ */ jsx12(AccountDialog, {})
1121
+ ] });
1122
+ return /* @__PURE__ */ jsx12(ThirdwebProvider, { children: /* @__PURE__ */ jsx12(LensProvider, { client: auth.lensClient, children: convex ? /* @__PURE__ */ jsx12(ConvexProviderWithAuth, { client: convex, useAuth: useConvexAuthHook, children: content }) : content }) });
1123
+ }
1124
+ function readSelectedAccount(auth, address) {
1125
+ try {
1126
+ const stored = JSON.parse(localStorage.getItem(`${auth.storagePrefix}account`) ?? "null");
1127
+ return stored?.address === address && typeof stored.username === "string" ? stored.username : null;
1128
+ } catch {
1129
+ return null;
1130
+ }
1131
+ }
1132
+ function writeSelectedAccount(auth, account) {
1133
+ try {
1134
+ const key = `${auth.storagePrefix}account`;
1135
+ if (account) localStorage.setItem(key, JSON.stringify(account));
1136
+ else localStorage.removeItem(key);
1137
+ } catch {
1138
+ }
1139
+ }
1140
+ function LensAuthState({ auth, onSignIn, withConvex, children }) {
1141
+ const wallet = useActiveAccount2();
1142
+ const activeWallet = useActiveWallet2();
1143
+ const { disconnect } = useDisconnect();
1144
+ const sessionKey = useSessionKey(auth);
1145
+ const [manualOpen, setManualOpen] = React4.useState(false);
1146
+ const [pendingSignIn, setPendingSignIn] = React4.useState(null);
1147
+ const account = React4.useMemo(() => {
1148
+ if (!sessionKey) return null;
1149
+ const address = sessionKey.slice(sessionKey.lastIndexOf(":") + 1);
1150
+ return { address, username: readSelectedAccount(auth, address) };
1151
+ }, [auth, sessionKey]);
1152
+ const status = sessionKey === void 0 ? "loading" : sessionKey ? "signed-in" : wallet ? "choosing-account" : "signed-out";
1153
+ useBackgroundRefresh(auth);
1154
+ React4.useEffect(() => {
1155
+ if (wallet) void auth.resumeSession().catch(() => {
1156
+ });
1157
+ }, [auth, wallet?.address]);
1158
+ const signOut = React4.useCallback(async () => {
1159
+ setManualOpen(false);
1160
+ writeSelectedAccount(auth, null);
1161
+ try {
1162
+ await auth.logout();
1163
+ } finally {
1164
+ if (activeWallet) disconnect(activeWallet);
1165
+ }
1166
+ }, [auth, activeWallet, disconnect]);
1167
+ const value = React4.useMemo(() => ({
1168
+ auth,
1169
+ status,
1170
+ isLoading: status === "loading",
1171
+ isSignedIn: status === "signed-in",
1172
+ wallet,
1173
+ account,
1174
+ signOut,
1175
+ openAccountDialog: () => setManualOpen(true)
1176
+ }), [auth, status, wallet, account, signOut]);
1177
+ const required = status === "choosing-account";
1178
+ const dialog = React4.useMemo(() => ({
1179
+ open: required || manualOpen && !!wallet,
1180
+ required,
1181
+ setOpen: (open) => {
1182
+ if (open) setManualOpen(true);
1183
+ else if (!required) setManualOpen(false);
1184
+ },
1185
+ completeSignIn: (signedIn) => {
1186
+ writeSelectedAccount(auth, signedIn);
1187
+ setManualOpen(false);
1188
+ if (onSignIn) setPendingSignIn(signedIn);
1189
+ }
1190
+ }), [auth, required, manualOpen, wallet, onSignIn]);
1191
+ return /* @__PURE__ */ jsx12(LensAuthContext.Provider, { value, children: /* @__PURE__ */ jsxs6(AccountDialogContext.Provider, { value: dialog, children: [
1192
+ children,
1193
+ pendingSignIn && onSignIn && (withConvex ? /* @__PURE__ */ jsx12(ConvexSignInCallback, { account: pendingSignIn, onSignIn, done: () => setPendingSignIn(null) }) : /* @__PURE__ */ jsx12(SignInCallback, { account: pendingSignIn, onSignIn, done: () => setPendingSignIn(null) }))
1194
+ ] }) });
1195
+ }
1196
+ function SignInCallback({ account, onSignIn, done }) {
1197
+ React4.useEffect(() => {
1198
+ Promise.resolve(onSignIn(account)).catch((err) => console.error("lens-convex-auth: onSignIn failed", err)).finally(done);
1199
+ }, [account]);
1200
+ return null;
1201
+ }
1202
+ function ConvexSignInCallback(props) {
1203
+ const { isAuthenticated, isLoading } = useConvexAuth();
1204
+ return isAuthenticated && !isLoading ? /* @__PURE__ */ jsx12(SignInCallback, { ...props }) : null;
1205
+ }
1206
+ function useBackgroundRefresh(auth) {
1207
+ React4.useEffect(() => {
1208
+ let timeout;
1209
+ let stopped = false;
1210
+ const schedule = () => {
1211
+ if (timeout) clearTimeout(timeout);
1212
+ timeout = void 0;
1213
+ const tokens = auth.tokens.getStoredTokens();
1214
+ if (stopped || !tokens?.expiresAt) return;
1215
+ const delay = Math.max(1e3, tokens.expiresAt - Date.now() - 60 * 1e3);
1216
+ timeout = setTimeout(() => void auth.tokens.refreshTokens(), delay);
1217
+ };
1218
+ const unsubscribe = subscribeToTokens(schedule);
1219
+ (async () => {
1220
+ try {
1221
+ await auth.resumeSession();
1222
+ } catch {
1223
+ }
1224
+ schedule();
1225
+ })();
1226
+ return () => {
1227
+ stopped = true;
1228
+ unsubscribe();
1229
+ if (timeout) clearTimeout(timeout);
1230
+ };
1231
+ }, [auth]);
1232
+ }
1233
+
1234
+ // src/react/SignInButton.tsx
1235
+ import * as React5 from "react";
1236
+ import { jsx as jsx13 } from "react/jsx-runtime";
1237
+ var ConnectButton = React5.lazy(() => import("thirdweb/react").then((m) => ({ default: m.ConnectButton })));
1238
+ function SignInButton({ label = "Sign in", signOutLabel = "Logout", theme, className, connectButtonProps }) {
1239
+ const { auth, wallet, status, signOut } = useLensAuth();
1240
+ const documentTheme = useDocumentTheme();
1241
+ const [mounted, setMounted] = React5.useState(false);
1242
+ React5.useEffect(() => setMounted(true), []);
1243
+ const placeholder = /* @__PURE__ */ jsx13(Button, { variant: "outline", size: "xl", className: cn("min-w-[165px] px-0", className), disabled: true, children: "Loading..." });
1244
+ if (!mounted) return placeholder;
1245
+ if (!wallet) {
1246
+ return /* @__PURE__ */ jsx13(React5.Suspense, { fallback: placeholder, children: /* @__PURE__ */ jsx13(
1247
+ ConnectButton,
1248
+ {
1249
+ client: auth.thirdwebClient,
1250
+ chain: auth.chain,
1251
+ theme: theme ?? documentTheme,
1252
+ ...connectButtonProps,
1253
+ connectButton: {
1254
+ label,
1255
+ ...connectButtonProps?.connectButton,
1256
+ style: { height: "50px", minWidth: "165px", ...connectButtonProps?.connectButton?.style }
1257
+ }
1258
+ }
1259
+ ) });
1260
+ }
1261
+ if (status !== "signed-in") return placeholder;
1262
+ return /* @__PURE__ */ jsx13(Button, { variant: "ghost", size: "xl", className: cn("min-w-[165px] px-0", className), onClick: signOut, children: signOutLabel });
1263
+ }
1264
+ export {
1265
+ AccountDialog,
1266
+ CreateAccountForm,
1267
+ LensAuthProvider,
1268
+ SignInButton,
1269
+ useLensAuth
1270
+ };
1271
+ //# sourceMappingURL=index.js.map