@authon/react 0.2.1 → 0.3.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
@@ -6,6 +6,7 @@ var AuthonContext = createContext(null);
6
6
  function AuthonProvider({ publishableKey, children, config }) {
7
7
  const [user, setUser] = useState(null);
8
8
  const [isLoading, setIsLoading] = useState(true);
9
+ const [activeOrganization, setActiveOrganization] = useState(null);
9
10
  const clientRef = useRef(null);
10
11
  useEffect(() => {
11
12
  const client = new Authon(publishableKey, config);
@@ -33,6 +34,7 @@ function AuthonProvider({ publishableKey, children, config }) {
33
34
  const signOut = useCallback(async () => {
34
35
  await clientRef.current?.signOut();
35
36
  setUser(null);
37
+ setActiveOrganization(null);
36
38
  }, []);
37
39
  const openSignIn = useCallback(async () => {
38
40
  await clientRef.current?.openSignIn();
@@ -48,13 +50,15 @@ function AuthonProvider({ publishableKey, children, config }) {
48
50
  isSignedIn: !!user,
49
51
  isLoading,
50
52
  user,
53
+ activeOrganization,
54
+ setActiveOrganization,
51
55
  signOut,
52
56
  openSignIn,
53
57
  openSignUp,
54
58
  getToken,
55
59
  client: clientRef.current
56
60
  }),
57
- [user, isLoading, signOut, openSignIn, openSignUp, getToken]
61
+ [user, isLoading, activeOrganization, signOut, openSignIn, openSignUp, getToken]
58
62
  );
59
63
  return /* @__PURE__ */ jsx(AuthonContext.Provider, { value, children });
60
64
  }
@@ -75,201 +79,1328 @@ function useUser() {
75
79
  return { user, isLoading };
76
80
  }
77
81
 
78
- // src/SignIn.tsx
79
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
80
- import { jsx as jsx2 } from "react/jsx-runtime";
81
- function SignIn({ mode = "popup" }) {
82
+ // src/components/SignIn.tsx
83
+ import { useState as useState5 } from "react";
84
+ import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES } from "@authon/shared";
85
+ import { getProviderButtonConfig } from "@authon/js";
86
+
87
+ // src/hooks/useBranding.ts
88
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
89
+ import { DEFAULT_BRANDING } from "@authon/shared";
90
+ var cache = /* @__PURE__ */ new Map();
91
+ function useBranding() {
82
92
  const { client } = useAuthon();
83
- const containerRef = useRef2(null);
93
+ const [state, setState] = useState2(() => {
94
+ const key = client?.publishableKey;
95
+ return cache.get(key ?? "") ?? { branding: DEFAULT_BRANDING, providers: [], isLoaded: false };
96
+ });
97
+ const fetchedRef = useRef2(false);
98
+ const fetchBranding = useCallback2(async () => {
99
+ if (!client || fetchedRef.current) return;
100
+ const key = client.publishableKey;
101
+ const cached = cache.get(key);
102
+ if (cached) {
103
+ setState(cached);
104
+ return;
105
+ }
106
+ fetchedRef.current = true;
107
+ try {
108
+ const providers = await client.getProviders();
109
+ const apiUrl = client.config?.apiUrl ?? "https://api.authon.dev";
110
+ const res = await fetch(`${apiUrl}/v1/auth/branding`, {
111
+ headers: { "x-api-key": key },
112
+ credentials: "include"
113
+ });
114
+ let branding = DEFAULT_BRANDING;
115
+ if (res.ok) {
116
+ const data = await res.json();
117
+ branding = { ...DEFAULT_BRANDING, ...data };
118
+ }
119
+ const next = { branding, providers, isLoaded: true };
120
+ cache.set(key, next);
121
+ setState(next);
122
+ } catch {
123
+ const fallback = { branding: DEFAULT_BRANDING, providers: [], isLoaded: true };
124
+ setState(fallback);
125
+ }
126
+ }, [client]);
84
127
  useEffect2(() => {
85
- if (mode === "popup") {
86
- client?.openSignIn();
128
+ fetchBranding();
129
+ }, [fetchBranding]);
130
+ return state;
131
+ }
132
+
133
+ // src/components/shared/ThemeProvider.tsx
134
+ import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
135
+ import { DEFAULT_BRANDING as DEFAULT_BRANDING2 } from "@authon/shared";
136
+ import { jsx as jsx2 } from "react/jsx-runtime";
137
+ var ThemeContext = createContext2(null);
138
+ function useTheme() {
139
+ const ctx = useContext2(ThemeContext);
140
+ if (!ctx) {
141
+ return resolveTheme(DEFAULT_BRANDING2, false);
142
+ }
143
+ return ctx;
144
+ }
145
+ function resolveTheme(branding, _dark) {
146
+ const radius = branding.borderRadius ?? DEFAULT_BRANDING2.borderRadius ?? 12;
147
+ return {
148
+ primaryStart: branding.primaryColorStart ?? DEFAULT_BRANDING2.primaryColorStart ?? "#7c3aed",
149
+ primaryEnd: branding.primaryColorEnd ?? DEFAULT_BRANDING2.primaryColorEnd ?? "#4f46e5",
150
+ bg: branding.lightBg ?? DEFAULT_BRANDING2.lightBg ?? "#ffffff",
151
+ text: branding.lightText ?? DEFAULT_BRANDING2.lightText ?? "#111827",
152
+ textMuted: "#6b7280",
153
+ border: "#e5e7eb",
154
+ borderRadius: `${radius}px`,
155
+ fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
156
+ inputStyle: "outline"
157
+ };
158
+ }
159
+ function ThemeProvider({ branding, children, overrides, style, className }) {
160
+ const merged = useMemo2(() => ({ ...branding, ...overrides }), [branding, overrides]);
161
+ const theme = useMemo2(() => resolveTheme(merged, false), [merged]);
162
+ const cssVars = {
163
+ "--authon-primary-start": theme.primaryStart,
164
+ "--authon-primary-end": theme.primaryEnd,
165
+ "--authon-bg": theme.bg,
166
+ "--authon-text": theme.text,
167
+ "--authon-text-muted": theme.textMuted,
168
+ "--authon-border": theme.border,
169
+ "--authon-radius": theme.borderRadius,
170
+ "--authon-font": theme.fontFamily
171
+ };
172
+ return /* @__PURE__ */ jsx2(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ jsx2(
173
+ "div",
174
+ {
175
+ className,
176
+ style: {
177
+ fontFamily: theme.fontFamily,
178
+ color: theme.text,
179
+ ...cssVars,
180
+ ...style
181
+ },
182
+ children
87
183
  }
88
- }, [client, mode]);
89
- if (mode === "embedded") {
90
- return /* @__PURE__ */ jsx2("div", { ref: containerRef, id: "authon-signin-container" });
184
+ ) });
185
+ }
186
+
187
+ // src/components/shared/Input.tsx
188
+ import { useState as useState3 } from "react";
189
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
190
+ function Input({
191
+ label,
192
+ error,
193
+ hint,
194
+ inputStyle,
195
+ onChange,
196
+ rightElement,
197
+ style: userStyle,
198
+ ...rest
199
+ }) {
200
+ const theme = useTheme();
201
+ const [focused, setFocused] = useState3(false);
202
+ const resolvedStyle = inputStyle ?? theme.inputStyle;
203
+ const wrapperStyle = {
204
+ display: "flex",
205
+ flexDirection: "column",
206
+ gap: 6,
207
+ width: "100%"
208
+ };
209
+ const labelStyle = {
210
+ fontSize: 13,
211
+ fontWeight: 500,
212
+ color: error ? "#ef4444" : theme.text
213
+ };
214
+ const inputContainerStyle = {
215
+ position: "relative",
216
+ display: "flex",
217
+ alignItems: "center"
218
+ };
219
+ const baseInputStyle = {
220
+ width: "100%",
221
+ height: 44,
222
+ paddingLeft: 14,
223
+ paddingRight: rightElement ? 44 : 14,
224
+ borderRadius: theme.borderRadius,
225
+ fontFamily: theme.fontFamily,
226
+ fontSize: 15,
227
+ color: theme.text,
228
+ outline: "none",
229
+ transition: "border-color 0.15s, box-shadow 0.15s, background 0.15s",
230
+ boxSizing: "border-box",
231
+ ...userStyle
232
+ };
233
+ let inputVariantStyle = {};
234
+ if (resolvedStyle === "filled") {
235
+ inputVariantStyle = {
236
+ background: focused ? `${theme.primaryStart}0d` : "#f3f4f6",
237
+ border: `1.5px solid ${error ? "#ef4444" : focused ? theme.primaryStart : "transparent"}`,
238
+ boxShadow: focused && !error ? `0 0 0 3px ${theme.primaryStart}22` : "none"
239
+ };
240
+ } else {
241
+ inputVariantStyle = {
242
+ background: theme.bg,
243
+ border: `1.5px solid ${error ? "#ef4444" : focused ? theme.primaryStart : theme.border}`,
244
+ boxShadow: focused && !error ? `0 0 0 3px ${theme.primaryStart}22` : "none"
245
+ };
91
246
  }
92
- return null;
247
+ const rightStyle = {
248
+ position: "absolute",
249
+ right: 12,
250
+ display: "flex",
251
+ alignItems: "center",
252
+ color: theme.textMuted
253
+ };
254
+ const hintStyle = {
255
+ fontSize: 12,
256
+ color: error ? "#ef4444" : theme.textMuted
257
+ };
258
+ return /* @__PURE__ */ jsxs("div", { style: wrapperStyle, children: [
259
+ label && /* @__PURE__ */ jsx3("label", { style: labelStyle, children: label }),
260
+ /* @__PURE__ */ jsxs("div", { style: inputContainerStyle, children: [
261
+ /* @__PURE__ */ jsx3(
262
+ "input",
263
+ {
264
+ ...rest,
265
+ style: { ...baseInputStyle, ...inputVariantStyle },
266
+ onFocus: (e) => {
267
+ setFocused(true);
268
+ rest.onFocus?.(e);
269
+ },
270
+ onBlur: (e) => {
271
+ setFocused(false);
272
+ rest.onBlur?.(e);
273
+ },
274
+ onChange: (e) => onChange?.(e.target.value)
275
+ }
276
+ ),
277
+ rightElement && /* @__PURE__ */ jsx3("div", { style: rightStyle, children: rightElement })
278
+ ] }),
279
+ (error || hint) && /* @__PURE__ */ jsx3("span", { style: hintStyle, children: error ?? hint })
280
+ ] });
93
281
  }
94
282
 
95
- // src/SignUp.tsx
96
- import { useEffect as useEffect3 } from "react";
97
- import { jsx as jsx3 } from "react/jsx-runtime";
98
- function SignUp({ mode = "popup" }) {
283
+ // src/components/shared/Button.tsx
284
+ import { useState as useState4 } from "react";
285
+ import { Fragment, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
286
+ var SPINNER_STYLE = {
287
+ display: "inline-block",
288
+ width: 16,
289
+ height: 16,
290
+ border: "2px solid currentColor",
291
+ borderTopColor: "transparent",
292
+ borderRadius: "50%",
293
+ animation: "authon-spin 0.6s linear infinite",
294
+ flexShrink: 0
295
+ };
296
+ function Button({
297
+ variant = "primary",
298
+ size = "md",
299
+ fullWidth = false,
300
+ loading = false,
301
+ disabled = false,
302
+ children,
303
+ onClick,
304
+ type = "button",
305
+ style: userStyle
306
+ }) {
307
+ const theme = useTheme();
308
+ const [hovered, setHovered] = useState4(false);
309
+ const sizeMap = {
310
+ sm: { height: 36, paddingLeft: 12, paddingRight: 12, fontSize: 13 },
311
+ md: { height: 44, paddingLeft: 16, paddingRight: 16, fontSize: 15 },
312
+ lg: { height: 52, paddingLeft: 20, paddingRight: 20, fontSize: 16 }
313
+ };
314
+ const base = {
315
+ display: "inline-flex",
316
+ alignItems: "center",
317
+ justifyContent: "center",
318
+ gap: 8,
319
+ borderRadius: theme.borderRadius,
320
+ fontFamily: theme.fontFamily,
321
+ fontWeight: 600,
322
+ border: "none",
323
+ cursor: disabled || loading ? "not-allowed" : "pointer",
324
+ transition: "opacity 0.15s, transform 0.1s",
325
+ width: fullWidth ? "100%" : void 0,
326
+ opacity: disabled ? 0.55 : hovered && !disabled && !loading ? 0.88 : 1,
327
+ transform: hovered && !disabled && !loading ? "translateY(-1px)" : void 0,
328
+ userSelect: "none",
329
+ ...sizeMap[size]
330
+ };
331
+ let variantStyle = {};
332
+ switch (variant) {
333
+ case "primary":
334
+ variantStyle = {
335
+ background: `linear-gradient(135deg, ${theme.primaryStart}, ${theme.primaryEnd})`,
336
+ color: "#ffffff",
337
+ boxShadow: hovered ? `0 4px 16px ${theme.primaryStart}55` : "0 2px 8px rgba(0,0,0,0.1)"
338
+ };
339
+ break;
340
+ case "secondary":
341
+ variantStyle = {
342
+ background: `${theme.primaryStart}18`,
343
+ color: theme.primaryStart
344
+ };
345
+ break;
346
+ case "outline":
347
+ variantStyle = {
348
+ background: "transparent",
349
+ color: theme.text,
350
+ border: `1.5px solid ${theme.border}`
351
+ };
352
+ break;
353
+ case "ghost":
354
+ variantStyle = {
355
+ background: "transparent",
356
+ color: theme.textMuted
357
+ };
358
+ break;
359
+ case "social":
360
+ variantStyle = {
361
+ background: theme.bg,
362
+ color: theme.text,
363
+ border: `1.5px solid ${theme.border}`
364
+ };
365
+ break;
366
+ }
367
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
368
+ /* @__PURE__ */ jsx4("style", { children: `@keyframes authon-spin { to { transform: rotate(360deg); } }` }),
369
+ /* @__PURE__ */ jsx4(
370
+ "button",
371
+ {
372
+ type,
373
+ disabled: disabled || loading,
374
+ onClick,
375
+ onMouseEnter: () => setHovered(true),
376
+ onMouseLeave: () => setHovered(false),
377
+ style: { ...base, ...variantStyle, ...userStyle },
378
+ children: loading ? /* @__PURE__ */ jsx4("span", { style: SPINNER_STYLE }) : children
379
+ }
380
+ )
381
+ ] });
382
+ }
383
+
384
+ // src/components/shared/Divider.tsx
385
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
386
+ function Divider({ label = "Or continue with" }) {
387
+ const theme = useTheme();
388
+ const containerStyle = {
389
+ display: "flex",
390
+ alignItems: "center",
391
+ gap: 12,
392
+ margin: "4px 0"
393
+ };
394
+ const lineStyle = {
395
+ flex: 1,
396
+ height: 1,
397
+ background: theme.border
398
+ };
399
+ const textStyle = {
400
+ fontSize: 13,
401
+ color: theme.textMuted,
402
+ whiteSpace: "nowrap",
403
+ fontWeight: 400
404
+ };
405
+ return /* @__PURE__ */ jsxs3("div", { style: containerStyle, children: [
406
+ /* @__PURE__ */ jsx5("div", { style: lineStyle }),
407
+ /* @__PURE__ */ jsx5("span", { style: textStyle, children: label }),
408
+ /* @__PURE__ */ jsx5("div", { style: lineStyle })
409
+ ] });
410
+ }
411
+
412
+ // src/components/SignIn.tsx
413
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
414
+ function SignInCard({ afterSignInUrl, onSignIn, onNavigateSignUp }) {
415
+ const theme = useTheme();
99
416
  const { client } = useAuthon();
100
- useEffect3(() => {
101
- if (mode === "popup") {
102
- client?.openSignUp();
417
+ const { branding, providers, isLoaded } = useBranding();
418
+ const [email, setEmail] = useState5("");
419
+ const [password, setPassword] = useState5("");
420
+ const [showPassword, setShowPassword] = useState5(false);
421
+ const [loading, setLoading] = useState5(false);
422
+ const [oauthLoading, setOauthLoading] = useState5(null);
423
+ const [error, setError] = useState5("");
424
+ const [fieldErrors, setFieldErrors] = useState5({});
425
+ const validate = () => {
426
+ const errs = {};
427
+ if (!email.trim()) errs.email = "Email is required";
428
+ else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errs.email = "Invalid email address";
429
+ if (!password) errs.password = "Password is required";
430
+ setFieldErrors(errs);
431
+ return Object.keys(errs).length === 0;
432
+ };
433
+ const handleSubmit = async () => {
434
+ if (!validate() || !client) return;
435
+ setLoading(true);
436
+ setError("");
437
+ try {
438
+ await client.signInWithEmail(email, password);
439
+ if (afterSignInUrl) window.location.assign(afterSignInUrl);
440
+ onSignIn?.();
441
+ } catch (e) {
442
+ setError(e?.message ?? "Sign in failed");
443
+ } finally {
444
+ setLoading(false);
103
445
  }
104
- }, [client, mode]);
105
- if (mode === "embedded") {
106
- return /* @__PURE__ */ jsx3("div", { id: "authon-signup-container" });
446
+ };
447
+ const handleOAuth = async (provider) => {
448
+ if (!client) return;
449
+ setOauthLoading(provider);
450
+ setError("");
451
+ try {
452
+ await client.signInWithOAuth(provider);
453
+ } catch (e) {
454
+ setError(e?.message ?? "OAuth sign in failed");
455
+ } finally {
456
+ setOauthLoading(null);
457
+ }
458
+ };
459
+ const cardStyle = {
460
+ width: "100%",
461
+ maxWidth: 440,
462
+ background: theme.bg,
463
+ borderRadius: `calc(${theme.borderRadius} + 4px)`,
464
+ boxShadow: "0 4px 32px rgba(0,0,0,0.10)",
465
+ padding: "40px 36px 32px",
466
+ boxSizing: "border-box",
467
+ display: "flex",
468
+ flexDirection: "column",
469
+ gap: 20,
470
+ fontFamily: theme.fontFamily
471
+ };
472
+ const logoStyle = {
473
+ display: "flex",
474
+ flexDirection: "column",
475
+ alignItems: "center",
476
+ gap: 10
477
+ };
478
+ const titleStyle = {
479
+ fontSize: 24,
480
+ fontWeight: 700,
481
+ color: theme.text,
482
+ textAlign: "center",
483
+ letterSpacing: "-0.3px"
484
+ };
485
+ const subtitleStyle = {
486
+ fontSize: 14,
487
+ color: theme.textMuted,
488
+ textAlign: "center",
489
+ marginTop: -12
490
+ };
491
+ const errorBoxStyle = {
492
+ padding: "10px 14px",
493
+ borderRadius: theme.borderRadius,
494
+ background: "#fef2f2",
495
+ border: "1px solid #fecaca",
496
+ color: "#dc2626",
497
+ fontSize: 13
498
+ };
499
+ const forgotStyle = {
500
+ textAlign: "right",
501
+ marginTop: -12
502
+ };
503
+ const linkStyle = {
504
+ fontSize: 13,
505
+ color: theme.primaryStart,
506
+ background: "none",
507
+ border: "none",
508
+ cursor: "pointer",
509
+ padding: 0,
510
+ fontFamily: theme.fontFamily,
511
+ textDecoration: "none"
512
+ };
513
+ const footerStyle = {
514
+ textAlign: "center",
515
+ fontSize: 14,
516
+ color: theme.textMuted,
517
+ paddingTop: 4
518
+ };
519
+ const eyeIconPath = showPassword ? "M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24 M1 1l22 22" : "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z M12 9a3 3 0 100 6 3 3 0 000-6z";
520
+ const showEmailPw = branding.showEmailPassword !== false;
521
+ const showDivider = branding.showDivider !== false && providers.length > 0 && showEmailPw;
522
+ const providersToShow = providers.filter(
523
+ (p) => !(branding.hiddenProviders ?? []).includes(p)
524
+ );
525
+ if (!isLoaded) {
526
+ return /* @__PURE__ */ jsx6("div", { style: { ...cardStyle, alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx6(
527
+ "span",
528
+ {
529
+ style: {
530
+ width: 28,
531
+ height: 28,
532
+ border: `3px solid ${theme.primaryStart}33`,
533
+ borderTopColor: theme.primaryStart,
534
+ borderRadius: "50%",
535
+ display: "inline-block",
536
+ animation: "authon-spin 0.7s linear infinite"
537
+ }
538
+ }
539
+ ) });
107
540
  }
108
- return null;
541
+ return /* @__PURE__ */ jsxs4("div", { style: cardStyle, children: [
542
+ /* @__PURE__ */ jsxs4("div", { style: logoStyle, children: [
543
+ branding.logoDataUrl && /* @__PURE__ */ jsx6("img", { src: branding.logoDataUrl, alt: branding.brandName ?? "Logo", style: { height: 40, objectFit: "contain" } }),
544
+ /* @__PURE__ */ jsx6("h1", { style: titleStyle, children: "Sign in" }),
545
+ branding.brandName && /* @__PURE__ */ jsxs4("p", { style: subtitleStyle, children: [
546
+ "to ",
547
+ branding.brandName
548
+ ] })
549
+ ] }),
550
+ error && /* @__PURE__ */ jsx6("div", { style: errorBoxStyle, children: error }),
551
+ providersToShow.length > 0 && /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: providersToShow.length <= 3 ? providersToShow.map((provider) => /* @__PURE__ */ jsx6(
552
+ OAuthButton,
553
+ {
554
+ provider,
555
+ loading: oauthLoading === provider,
556
+ disabled: !!oauthLoading || loading,
557
+ onClick: () => handleOAuth(provider),
558
+ borderRadius: theme.borderRadius
559
+ },
560
+ provider
561
+ )) : /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexWrap: "wrap", gap: 10, justifyContent: "center" }, children: providersToShow.map((provider) => /* @__PURE__ */ jsx6(
562
+ CompactOAuthButton,
563
+ {
564
+ provider,
565
+ loading: oauthLoading === provider,
566
+ disabled: !!oauthLoading || loading,
567
+ onClick: () => handleOAuth(provider),
568
+ borderRadius: theme.borderRadius
569
+ },
570
+ provider
571
+ )) }) }),
572
+ showDivider && /* @__PURE__ */ jsx6(Divider, {}),
573
+ showEmailPw && /* @__PURE__ */ jsxs4(Fragment2, { children: [
574
+ /* @__PURE__ */ jsx6(
575
+ Input,
576
+ {
577
+ label: "Email",
578
+ type: "email",
579
+ placeholder: "you@example.com",
580
+ value: email,
581
+ onChange: setEmail,
582
+ error: fieldErrors.email,
583
+ autoComplete: "email"
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
587
+ /* @__PURE__ */ jsx6(
588
+ Input,
589
+ {
590
+ label: "Password",
591
+ type: showPassword ? "text" : "password",
592
+ placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022",
593
+ value: password,
594
+ onChange: setPassword,
595
+ error: fieldErrors.password,
596
+ autoComplete: "current-password",
597
+ rightElement: /* @__PURE__ */ jsx6(
598
+ "button",
599
+ {
600
+ type: "button",
601
+ onClick: () => setShowPassword((v) => !v),
602
+ style: { background: "none", border: "none", cursor: "pointer", padding: 0, display: "flex", color: theme.textMuted },
603
+ "aria-label": showPassword ? "Hide password" : "Show password",
604
+ children: /* @__PURE__ */ jsx6("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx6("path", { d: eyeIconPath }) })
605
+ }
606
+ )
607
+ }
608
+ ),
609
+ /* @__PURE__ */ jsx6("div", { style: forgotStyle, children: /* @__PURE__ */ jsx6("button", { type: "button", style: linkStyle, children: "Forgot password?" }) })
610
+ ] }),
611
+ /* @__PURE__ */ jsx6(
612
+ Button,
613
+ {
614
+ variant: "primary",
615
+ fullWidth: true,
616
+ loading,
617
+ disabled: !!oauthLoading,
618
+ onClick: handleSubmit,
619
+ children: "Sign in"
620
+ }
621
+ )
622
+ ] }),
623
+ /* @__PURE__ */ jsxs4("div", { style: footerStyle, children: [
624
+ "Don't have an account?",
625
+ " ",
626
+ /* @__PURE__ */ jsx6(
627
+ "button",
628
+ {
629
+ type: "button",
630
+ style: linkStyle,
631
+ onClick: onNavigateSignUp,
632
+ children: "Sign up"
633
+ }
634
+ )
635
+ ] }),
636
+ branding.showSecuredBy !== false && /* @__PURE__ */ jsx6(SecuredByAuthon, { primaryStart: theme.primaryStart, textMuted: theme.textMuted }),
637
+ branding.termsUrl || branding.privacyUrl ? /* @__PURE__ */ jsxs4("div", { style: { textAlign: "center", fontSize: 11, color: theme.textMuted, marginTop: -8 }, children: [
638
+ branding.termsUrl && /* @__PURE__ */ jsx6("a", { href: branding.termsUrl, target: "_blank", rel: "noopener noreferrer", style: { color: theme.textMuted }, children: "Terms" }),
639
+ branding.termsUrl && branding.privacyUrl && " \xB7 ",
640
+ branding.privacyUrl && /* @__PURE__ */ jsx6("a", { href: branding.privacyUrl, target: "_blank", rel: "noopener noreferrer", style: { color: theme.textMuted }, children: "Privacy" })
641
+ ] }) : null
642
+ ] });
643
+ }
644
+ function OAuthButton({ provider, loading, disabled, onClick, borderRadius }) {
645
+ const [hovered, setHovered] = useState5(false);
646
+ const colors = PROVIDER_COLORS[provider] ?? { bg: "#333", text: "#fff" };
647
+ const name = PROVIDER_DISPLAY_NAMES[provider] ?? provider;
648
+ const config = getProviderButtonConfig(provider);
649
+ const needsBorder = colors.bg.toLowerCase() === "#ffffff";
650
+ const style = {
651
+ display: "flex",
652
+ alignItems: "center",
653
+ gap: 10,
654
+ width: "100%",
655
+ height: 44,
656
+ paddingLeft: 16,
657
+ paddingRight: 16,
658
+ borderRadius,
659
+ background: colors.bg,
660
+ color: colors.text,
661
+ border: needsBorder ? "1.5px solid #dadce0" : "none",
662
+ cursor: disabled ? "not-allowed" : "pointer",
663
+ fontSize: 15,
664
+ fontWeight: 600,
665
+ fontFamily: "system-ui, -apple-system, sans-serif",
666
+ justifyContent: "center",
667
+ opacity: disabled ? 0.6 : hovered ? 0.88 : 1,
668
+ transition: "opacity 0.15s",
669
+ boxSizing: "border-box"
670
+ };
671
+ return /* @__PURE__ */ jsx6(
672
+ "button",
673
+ {
674
+ type: "button",
675
+ style,
676
+ onClick,
677
+ disabled,
678
+ onMouseEnter: () => setHovered(true),
679
+ onMouseLeave: () => setHovered(false),
680
+ "aria-label": `Continue with ${name}`,
681
+ children: loading ? /* @__PURE__ */ jsx6("span", { style: { width: 18, height: 18, border: `2px solid ${colors.text}`, borderTopColor: "transparent", borderRadius: "50%", display: "inline-block", animation: "authon-spin 0.6s linear infinite" } }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
682
+ /* @__PURE__ */ jsx6("span", { style: { display: "flex", alignItems: "center" }, dangerouslySetInnerHTML: { __html: config.iconSvg } }),
683
+ /* @__PURE__ */ jsxs4("span", { children: [
684
+ "Continue with ",
685
+ name
686
+ ] })
687
+ ] })
688
+ }
689
+ );
690
+ }
691
+ function CompactOAuthButton({ provider, loading, disabled, onClick, borderRadius }) {
692
+ const [hovered, setHovered] = useState5(false);
693
+ const colors = PROVIDER_COLORS[provider] ?? { bg: "#333", text: "#fff" };
694
+ const name = PROVIDER_DISPLAY_NAMES[provider] ?? provider;
695
+ const config = getProviderButtonConfig(provider);
696
+ const needsBorder = colors.bg.toLowerCase() === "#ffffff";
697
+ const style = {
698
+ display: "flex",
699
+ alignItems: "center",
700
+ justifyContent: "center",
701
+ width: 48,
702
+ height: 48,
703
+ borderRadius,
704
+ background: colors.bg,
705
+ border: needsBorder ? "1.5px solid #dadce0" : "none",
706
+ cursor: disabled ? "not-allowed" : "pointer",
707
+ opacity: disabled ? 0.6 : hovered ? 0.85 : 1,
708
+ transition: "opacity 0.15s",
709
+ padding: 0
710
+ };
711
+ return /* @__PURE__ */ jsx6(
712
+ "button",
713
+ {
714
+ type: "button",
715
+ style,
716
+ onClick,
717
+ disabled,
718
+ onMouseEnter: () => setHovered(true),
719
+ onMouseLeave: () => setHovered(false),
720
+ "aria-label": `Continue with ${name}`,
721
+ children: loading ? /* @__PURE__ */ jsx6("span", { style: { width: 18, height: 18, border: `2px solid ${colors.text}`, borderTopColor: "transparent", borderRadius: "50%", display: "inline-block", animation: "authon-spin 0.6s linear infinite" } }) : /* @__PURE__ */ jsx6("span", { style: { display: "flex" }, dangerouslySetInnerHTML: { __html: config.iconSvg } })
722
+ }
723
+ );
724
+ }
725
+ function SecuredByAuthon({ primaryStart, textMuted }) {
726
+ return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 5, marginTop: -8 }, children: [
727
+ /* @__PURE__ */ jsxs4("svg", { width: "12", height: "14", viewBox: "0 0 12 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
728
+ /* @__PURE__ */ jsx6("path", { d: "M6 0L0.5 2.5V6.5C0.5 9.7 2.9 12.7 6 13.5C9.1 12.7 11.5 9.7 11.5 6.5V2.5L6 0Z", fill: primaryStart, opacity: "0.85" }),
729
+ /* @__PURE__ */ jsx6("path", { d: "M4 7L5.5 8.5L8.5 5.5", stroke: "white", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
730
+ ] }),
731
+ /* @__PURE__ */ jsxs4("span", { style: { fontSize: 11, color: textMuted }, children: [
732
+ "Secured by",
733
+ " ",
734
+ /* @__PURE__ */ jsx6("a", { href: "https://authon.dev", target: "_blank", rel: "noopener noreferrer", style: { color: primaryStart, textDecoration: "none", fontWeight: 600 }, children: "Authon" })
735
+ ] })
736
+ ] });
737
+ }
738
+ function SignIn({ appearance, afterSignInUrl, onSignIn, onNavigateSignUp }) {
739
+ const { branding, isLoaded } = useBranding();
740
+ const effectiveBranding = isLoaded ? { ...branding, ...appearance?.variables ?? {} } : branding;
741
+ return /* @__PURE__ */ jsxs4(ThemeProvider, { branding: effectiveBranding, overrides: appearance?.variables, style: { display: "flex", justifyContent: "center" }, children: [
742
+ /* @__PURE__ */ jsx6("style", { children: `@keyframes authon-spin { to { transform: rotate(360deg); } }` }),
743
+ /* @__PURE__ */ jsx6(
744
+ SignInCard,
745
+ {
746
+ afterSignInUrl,
747
+ onSignIn,
748
+ onNavigateSignUp
749
+ }
750
+ )
751
+ ] });
752
+ }
753
+
754
+ // src/components/SignUp.tsx
755
+ import { useState as useState6 } from "react";
756
+ import { PROVIDER_COLORS as PROVIDER_COLORS2, PROVIDER_DISPLAY_NAMES as PROVIDER_DISPLAY_NAMES2 } from "@authon/shared";
757
+ import { getProviderButtonConfig as getProviderButtonConfig2 } from "@authon/js";
758
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
759
+ function SignUpCard({ afterSignUpUrl, onSignUp, onNavigateSignIn }) {
760
+ const theme = useTheme();
761
+ const { client } = useAuthon();
762
+ const { branding, providers, isLoaded } = useBranding();
763
+ const [displayName, setDisplayName] = useState6("");
764
+ const [email, setEmail] = useState6("");
765
+ const [password, setPassword] = useState6("");
766
+ const [confirmPassword, setConfirmPassword] = useState6("");
767
+ const [showPassword, setShowPassword] = useState6(false);
768
+ const [loading, setLoading] = useState6(false);
769
+ const [oauthLoading, setOauthLoading] = useState6(null);
770
+ const [error, setError] = useState6("");
771
+ const [fieldErrors, setFieldErrors] = useState6({});
772
+ const validate = () => {
773
+ const errs = {};
774
+ if (!email.trim()) errs.email = "Email is required";
775
+ else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) errs.email = "Invalid email address";
776
+ if (!password) errs.password = "Password is required";
777
+ else if (password.length < 8) errs.password = "Password must be at least 8 characters";
778
+ if (confirmPassword !== password) errs.confirmPassword = "Passwords do not match";
779
+ setFieldErrors(errs);
780
+ return Object.keys(errs).length === 0;
781
+ };
782
+ const handleSubmit = async () => {
783
+ if (!validate() || !client) return;
784
+ setLoading(true);
785
+ setError("");
786
+ try {
787
+ await client.signUpWithEmail(email, password, displayName ? { displayName } : void 0);
788
+ if (afterSignUpUrl) window.location.assign(afterSignUpUrl);
789
+ onSignUp?.();
790
+ } catch (e) {
791
+ setError(e?.message ?? "Sign up failed");
792
+ } finally {
793
+ setLoading(false);
794
+ }
795
+ };
796
+ const handleOAuth = async (provider) => {
797
+ if (!client) return;
798
+ setOauthLoading(provider);
799
+ setError("");
800
+ try {
801
+ await client.signInWithOAuth(provider);
802
+ } catch (e) {
803
+ setError(e?.message ?? "OAuth sign in failed");
804
+ } finally {
805
+ setOauthLoading(null);
806
+ }
807
+ };
808
+ const cardStyle = {
809
+ width: "100%",
810
+ maxWidth: 440,
811
+ background: theme.bg,
812
+ borderRadius: `calc(${theme.borderRadius} + 4px)`,
813
+ boxShadow: "0 4px 32px rgba(0,0,0,0.10)",
814
+ padding: "40px 36px 32px",
815
+ boxSizing: "border-box",
816
+ display: "flex",
817
+ flexDirection: "column",
818
+ gap: 20,
819
+ fontFamily: theme.fontFamily
820
+ };
821
+ const logoStyle = {
822
+ display: "flex",
823
+ flexDirection: "column",
824
+ alignItems: "center",
825
+ gap: 10
826
+ };
827
+ const titleStyle = {
828
+ fontSize: 24,
829
+ fontWeight: 700,
830
+ color: theme.text,
831
+ textAlign: "center",
832
+ letterSpacing: "-0.3px"
833
+ };
834
+ const subtitleStyle = {
835
+ fontSize: 14,
836
+ color: theme.textMuted,
837
+ textAlign: "center",
838
+ marginTop: -12
839
+ };
840
+ const errorBoxStyle = {
841
+ padding: "10px 14px",
842
+ borderRadius: theme.borderRadius,
843
+ background: "#fef2f2",
844
+ border: "1px solid #fecaca",
845
+ color: "#dc2626",
846
+ fontSize: 13
847
+ };
848
+ const linkStyle = {
849
+ fontSize: 13,
850
+ color: theme.primaryStart,
851
+ background: "none",
852
+ border: "none",
853
+ cursor: "pointer",
854
+ padding: 0,
855
+ fontFamily: theme.fontFamily,
856
+ textDecoration: "none"
857
+ };
858
+ const footerStyle = {
859
+ textAlign: "center",
860
+ fontSize: 14,
861
+ color: theme.textMuted,
862
+ paddingTop: 4
863
+ };
864
+ const showEmailPw = branding.showEmailPassword !== false;
865
+ const showDivider = branding.showDivider !== false && providers.length > 0 && showEmailPw;
866
+ const providersToShow = providers.filter(
867
+ (p) => !(branding.hiddenProviders ?? []).includes(p)
868
+ );
869
+ if (!isLoaded) {
870
+ return /* @__PURE__ */ jsx7("div", { style: { ...cardStyle, alignItems: "center", justifyContent: "center", minHeight: 200 }, children: /* @__PURE__ */ jsx7(
871
+ "span",
872
+ {
873
+ style: {
874
+ width: 28,
875
+ height: 28,
876
+ border: `3px solid ${theme.primaryStart}33`,
877
+ borderTopColor: theme.primaryStart,
878
+ borderRadius: "50%",
879
+ display: "inline-block",
880
+ animation: "authon-spin 0.7s linear infinite"
881
+ }
882
+ }
883
+ ) });
884
+ }
885
+ const eyeIconPath = showPassword ? "M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24 M1 1l22 22" : "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z M12 9a3 3 0 100 6 3 3 0 000-6z";
886
+ return /* @__PURE__ */ jsxs5("div", { style: cardStyle, children: [
887
+ /* @__PURE__ */ jsxs5("div", { style: logoStyle, children: [
888
+ branding.logoDataUrl && /* @__PURE__ */ jsx7("img", { src: branding.logoDataUrl, alt: branding.brandName ?? "Logo", style: { height: 40, objectFit: "contain" } }),
889
+ /* @__PURE__ */ jsx7("h1", { style: titleStyle, children: "Create account" }),
890
+ branding.brandName && /* @__PURE__ */ jsxs5("p", { style: subtitleStyle, children: [
891
+ "Join ",
892
+ branding.brandName
893
+ ] })
894
+ ] }),
895
+ error && /* @__PURE__ */ jsx7("div", { style: errorBoxStyle, children: error }),
896
+ providersToShow.length > 0 && /* @__PURE__ */ jsx7("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: providersToShow.length <= 3 ? providersToShow.map((provider) => /* @__PURE__ */ jsx7(
897
+ OAuthButtonFull,
898
+ {
899
+ provider,
900
+ loading: oauthLoading === provider,
901
+ disabled: !!oauthLoading || loading,
902
+ onClick: () => handleOAuth(provider),
903
+ borderRadius: theme.borderRadius
904
+ },
905
+ provider
906
+ )) : /* @__PURE__ */ jsx7("div", { style: { display: "flex", flexWrap: "wrap", gap: 10, justifyContent: "center" }, children: providersToShow.map((provider) => /* @__PURE__ */ jsx7(
907
+ CompactOAuthBtn,
908
+ {
909
+ provider,
910
+ loading: oauthLoading === provider,
911
+ disabled: !!oauthLoading || loading,
912
+ onClick: () => handleOAuth(provider),
913
+ borderRadius: theme.borderRadius
914
+ },
915
+ provider
916
+ )) }) }),
917
+ showDivider && /* @__PURE__ */ jsx7(Divider, {}),
918
+ showEmailPw && /* @__PURE__ */ jsxs5(Fragment3, { children: [
919
+ /* @__PURE__ */ jsx7(
920
+ Input,
921
+ {
922
+ label: "Display name",
923
+ type: "text",
924
+ placeholder: "Your name",
925
+ value: displayName,
926
+ onChange: setDisplayName,
927
+ error: fieldErrors.displayName,
928
+ autoComplete: "name"
929
+ }
930
+ ),
931
+ /* @__PURE__ */ jsx7(
932
+ Input,
933
+ {
934
+ label: "Email",
935
+ type: "email",
936
+ placeholder: "you@example.com",
937
+ value: email,
938
+ onChange: setEmail,
939
+ error: fieldErrors.email,
940
+ autoComplete: "email"
941
+ }
942
+ ),
943
+ /* @__PURE__ */ jsx7(
944
+ Input,
945
+ {
946
+ label: "Password",
947
+ type: showPassword ? "text" : "password",
948
+ placeholder: "Minimum 8 characters",
949
+ value: password,
950
+ onChange: setPassword,
951
+ error: fieldErrors.password,
952
+ autoComplete: "new-password",
953
+ rightElement: /* @__PURE__ */ jsx7(
954
+ "button",
955
+ {
956
+ type: "button",
957
+ onClick: () => setShowPassword((v) => !v),
958
+ style: { background: "none", border: "none", cursor: "pointer", padding: 0, display: "flex", color: theme.textMuted },
959
+ "aria-label": showPassword ? "Hide password" : "Show password",
960
+ children: /* @__PURE__ */ jsx7("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx7("path", { d: eyeIconPath }) })
961
+ }
962
+ )
963
+ }
964
+ ),
965
+ /* @__PURE__ */ jsx7(
966
+ Input,
967
+ {
968
+ label: "Confirm password",
969
+ type: showPassword ? "text" : "password",
970
+ placeholder: "Repeat password",
971
+ value: confirmPassword,
972
+ onChange: setConfirmPassword,
973
+ error: fieldErrors.confirmPassword,
974
+ autoComplete: "new-password"
975
+ }
976
+ ),
977
+ /* @__PURE__ */ jsx7(
978
+ Button,
979
+ {
980
+ variant: "primary",
981
+ fullWidth: true,
982
+ loading,
983
+ disabled: !!oauthLoading,
984
+ onClick: handleSubmit,
985
+ children: "Create account"
986
+ }
987
+ )
988
+ ] }),
989
+ /* @__PURE__ */ jsxs5("div", { style: footerStyle, children: [
990
+ "Already have an account?",
991
+ " ",
992
+ /* @__PURE__ */ jsx7("button", { type: "button", style: linkStyle, onClick: onNavigateSignIn, children: "Sign in" })
993
+ ] }),
994
+ branding.showSecuredBy !== false && /* @__PURE__ */ jsx7(SecuredByAuthon2, { primaryStart: theme.primaryStart, textMuted: theme.textMuted }),
995
+ branding.termsUrl || branding.privacyUrl ? /* @__PURE__ */ jsxs5("div", { style: { textAlign: "center", fontSize: 11, color: theme.textMuted, marginTop: -8 }, children: [
996
+ "By creating an account you agree to our",
997
+ " ",
998
+ branding.termsUrl && /* @__PURE__ */ jsx7("a", { href: branding.termsUrl, target: "_blank", rel: "noopener noreferrer", style: { color: theme.primaryStart }, children: "Terms" }),
999
+ branding.termsUrl && branding.privacyUrl && " and ",
1000
+ branding.privacyUrl && /* @__PURE__ */ jsx7("a", { href: branding.privacyUrl, target: "_blank", rel: "noopener noreferrer", style: { color: theme.primaryStart }, children: "Privacy Policy" })
1001
+ ] }) : null
1002
+ ] });
1003
+ }
1004
+ function OAuthButtonFull({ provider, loading, disabled, onClick, borderRadius }) {
1005
+ const [hovered, setHovered] = useState6(false);
1006
+ const colors = PROVIDER_COLORS2[provider] ?? { bg: "#333", text: "#fff" };
1007
+ const name = PROVIDER_DISPLAY_NAMES2[provider] ?? provider;
1008
+ const config = getProviderButtonConfig2(provider);
1009
+ const needsBorder = colors.bg.toLowerCase() === "#ffffff";
1010
+ const style = {
1011
+ display: "flex",
1012
+ alignItems: "center",
1013
+ gap: 10,
1014
+ width: "100%",
1015
+ height: 44,
1016
+ paddingLeft: 16,
1017
+ paddingRight: 16,
1018
+ borderRadius,
1019
+ background: colors.bg,
1020
+ color: colors.text,
1021
+ border: needsBorder ? "1.5px solid #dadce0" : "none",
1022
+ cursor: disabled ? "not-allowed" : "pointer",
1023
+ fontSize: 15,
1024
+ fontWeight: 600,
1025
+ fontFamily: "system-ui, -apple-system, sans-serif",
1026
+ justifyContent: "center",
1027
+ opacity: disabled ? 0.6 : hovered ? 0.88 : 1,
1028
+ transition: "opacity 0.15s",
1029
+ boxSizing: "border-box"
1030
+ };
1031
+ return /* @__PURE__ */ jsx7(
1032
+ "button",
1033
+ {
1034
+ type: "button",
1035
+ style,
1036
+ onClick,
1037
+ disabled,
1038
+ onMouseEnter: () => setHovered(true),
1039
+ onMouseLeave: () => setHovered(false),
1040
+ "aria-label": `Continue with ${name}`,
1041
+ children: loading ? /* @__PURE__ */ jsx7("span", { style: { width: 18, height: 18, border: `2px solid ${colors.text}`, borderTopColor: "transparent", borderRadius: "50%", display: "inline-block", animation: "authon-spin 0.6s linear infinite" } }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
1042
+ /* @__PURE__ */ jsx7("span", { style: { display: "flex", alignItems: "center" }, dangerouslySetInnerHTML: { __html: config.iconSvg } }),
1043
+ /* @__PURE__ */ jsxs5("span", { children: [
1044
+ "Continue with ",
1045
+ name
1046
+ ] })
1047
+ ] })
1048
+ }
1049
+ );
1050
+ }
1051
+ function CompactOAuthBtn({ provider, loading, disabled, onClick, borderRadius }) {
1052
+ const [hovered, setHovered] = useState6(false);
1053
+ const colors = PROVIDER_COLORS2[provider] ?? { bg: "#333", text: "#fff" };
1054
+ const name = PROVIDER_DISPLAY_NAMES2[provider] ?? provider;
1055
+ const config = getProviderButtonConfig2(provider);
1056
+ const needsBorder = colors.bg.toLowerCase() === "#ffffff";
1057
+ const style = {
1058
+ display: "flex",
1059
+ alignItems: "center",
1060
+ justifyContent: "center",
1061
+ width: 48,
1062
+ height: 48,
1063
+ borderRadius,
1064
+ background: colors.bg,
1065
+ border: needsBorder ? "1.5px solid #dadce0" : "none",
1066
+ cursor: disabled ? "not-allowed" : "pointer",
1067
+ opacity: disabled ? 0.6 : hovered ? 0.85 : 1,
1068
+ transition: "opacity 0.15s",
1069
+ padding: 0
1070
+ };
1071
+ return /* @__PURE__ */ jsx7(
1072
+ "button",
1073
+ {
1074
+ type: "button",
1075
+ style,
1076
+ onClick,
1077
+ disabled,
1078
+ onMouseEnter: () => setHovered(true),
1079
+ onMouseLeave: () => setHovered(false),
1080
+ "aria-label": `Continue with ${name}`,
1081
+ children: loading ? /* @__PURE__ */ jsx7("span", { style: { width: 18, height: 18, border: `2px solid ${colors.text}`, borderTopColor: "transparent", borderRadius: "50%", display: "inline-block", animation: "authon-spin 0.6s linear infinite" } }) : /* @__PURE__ */ jsx7("span", { style: { display: "flex" }, dangerouslySetInnerHTML: { __html: config.iconSvg } })
1082
+ }
1083
+ );
1084
+ }
1085
+ function SecuredByAuthon2({ primaryStart, textMuted }) {
1086
+ return /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 5, marginTop: -8 }, children: [
1087
+ /* @__PURE__ */ jsxs5("svg", { width: "12", height: "14", viewBox: "0 0 12 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
1088
+ /* @__PURE__ */ jsx7("path", { d: "M6 0L0.5 2.5V6.5C0.5 9.7 2.9 12.7 6 13.5C9.1 12.7 11.5 9.7 11.5 6.5V2.5L6 0Z", fill: primaryStart, opacity: "0.85" }),
1089
+ /* @__PURE__ */ jsx7("path", { d: "M4 7L5.5 8.5L8.5 5.5", stroke: "white", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" })
1090
+ ] }),
1091
+ /* @__PURE__ */ jsxs5("span", { style: { fontSize: 11, color: textMuted }, children: [
1092
+ "Secured by",
1093
+ " ",
1094
+ /* @__PURE__ */ jsx7("a", { href: "https://authon.dev", target: "_blank", rel: "noopener noreferrer", style: { color: primaryStart, textDecoration: "none", fontWeight: 600 }, children: "Authon" })
1095
+ ] })
1096
+ ] });
1097
+ }
1098
+ function SignUp({ appearance, afterSignUpUrl, onSignUp, onNavigateSignIn }) {
1099
+ const { branding, isLoaded } = useBranding();
1100
+ const effectiveBranding = isLoaded ? { ...branding, ...appearance?.variables ?? {} } : branding;
1101
+ return /* @__PURE__ */ jsxs5(ThemeProvider, { branding: effectiveBranding, overrides: appearance?.variables, style: { display: "flex", justifyContent: "center" }, children: [
1102
+ /* @__PURE__ */ jsx7("style", { children: `@keyframes authon-spin { to { transform: rotate(360deg); } }` }),
1103
+ /* @__PURE__ */ jsx7(
1104
+ SignUpCard,
1105
+ {
1106
+ afterSignUpUrl,
1107
+ onSignUp,
1108
+ onNavigateSignIn
1109
+ }
1110
+ )
1111
+ ] });
109
1112
  }
110
1113
 
111
- // src/UserButton.tsx
112
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef3, useState as useState2 } from "react";
113
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
114
- function UserButton() {
115
- const { user, signOut, openSignIn, isSignedIn } = useAuthon();
116
- const [open, setOpen] = useState2(false);
1114
+ // src/components/UserButton.tsx
1115
+ import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
1116
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1117
+ function UserAvatar({
1118
+ avatarUrl,
1119
+ displayName,
1120
+ email,
1121
+ size,
1122
+ primaryStart,
1123
+ primaryEnd
1124
+ }) {
1125
+ const initials = displayName ? displayName.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2) : (email?.[0] ?? "?").toUpperCase();
1126
+ const avatarStyle = {
1127
+ width: size,
1128
+ height: size,
1129
+ borderRadius: "50%",
1130
+ overflow: "hidden",
1131
+ display: "flex",
1132
+ alignItems: "center",
1133
+ justifyContent: "center",
1134
+ background: avatarUrl ? "transparent" : `linear-gradient(135deg, ${primaryStart}, ${primaryEnd})`,
1135
+ color: "#fff",
1136
+ fontSize: size * 0.38,
1137
+ fontWeight: 700,
1138
+ userSelect: "none"
1139
+ };
1140
+ return /* @__PURE__ */ jsx8("div", { style: avatarStyle, children: avatarUrl ? /* @__PURE__ */ jsx8("img", { src: avatarUrl, alt: displayName ?? "avatar", style: { width: "100%", height: "100%", objectFit: "cover" } }) : initials });
1141
+ }
1142
+ function UserButtonInner({ afterSignOutUrl, userProfileUrl }) {
1143
+ const theme = useTheme();
1144
+ const { user, signOut, openSignIn, isSignedIn, activeOrganization, client } = useAuthon();
1145
+ const [open, setOpen] = useState7(false);
117
1146
  const dropdownRef = useRef3(null);
118
- const handleClickOutside = useCallback2((e) => {
1147
+ const handleClickOutside = useCallback3((e) => {
119
1148
  if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
120
1149
  setOpen(false);
121
1150
  }
122
1151
  }, []);
123
- useEffect4(() => {
124
- if (open) {
125
- document.addEventListener("mousedown", handleClickOutside);
126
- }
1152
+ useEffect3(() => {
1153
+ if (open) document.addEventListener("mousedown", handleClickOutside);
127
1154
  return () => document.removeEventListener("mousedown", handleClickOutside);
128
1155
  }, [open, handleClickOutside]);
129
1156
  if (!isSignedIn) {
130
- return /* @__PURE__ */ jsx4(
1157
+ return /* @__PURE__ */ jsx8(
131
1158
  "button",
132
1159
  {
1160
+ type: "button",
133
1161
  onClick: () => openSignIn(),
134
1162
  style: {
135
- padding: "8px 16px",
136
- borderRadius: "8px",
1163
+ padding: "8px 18px",
1164
+ borderRadius: theme.borderRadius,
137
1165
  border: "none",
138
- background: "linear-gradient(135deg, #7c3aed, #4f46e5)",
1166
+ background: `linear-gradient(135deg, ${theme.primaryStart}, ${theme.primaryEnd})`,
139
1167
  color: "#fff",
140
1168
  cursor: "pointer",
141
- fontSize: "14px",
142
- fontWeight: 600
1169
+ fontSize: 14,
1170
+ fontWeight: 600,
1171
+ fontFamily: theme.fontFamily
143
1172
  },
144
1173
  children: "Sign In"
145
1174
  }
146
1175
  );
147
1176
  }
148
- const initials = user?.displayName ? user.displayName.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2) : (user?.email?.[0] ?? "?").toUpperCase();
149
- return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, style: { position: "relative", display: "inline-block" }, children: [
150
- /* @__PURE__ */ jsx4(
151
- "button",
1177
+ const triggerStyle = {
1178
+ width: 38,
1179
+ height: 38,
1180
+ borderRadius: "50%",
1181
+ border: `2px solid ${theme.primaryStart}`,
1182
+ background: "none",
1183
+ cursor: "pointer",
1184
+ padding: 1,
1185
+ overflow: "hidden",
1186
+ display: "flex",
1187
+ alignItems: "center",
1188
+ justifyContent: "center"
1189
+ };
1190
+ const dropdownStyle = {
1191
+ position: "absolute",
1192
+ right: 0,
1193
+ top: 46,
1194
+ minWidth: 240,
1195
+ background: theme.bg,
1196
+ border: `1px solid ${theme.border}`,
1197
+ borderRadius: theme.borderRadius,
1198
+ boxShadow: "0 8px 32px rgba(0,0,0,0.13)",
1199
+ zIndex: 9999,
1200
+ overflow: "hidden",
1201
+ fontFamily: theme.fontFamily
1202
+ };
1203
+ const headerStyle = {
1204
+ padding: "14px 16px",
1205
+ borderBottom: `1px solid ${theme.border}`,
1206
+ display: "flex",
1207
+ alignItems: "center",
1208
+ gap: 12
1209
+ };
1210
+ const menuItemStyle = (danger) => ({
1211
+ display: "flex",
1212
+ alignItems: "center",
1213
+ gap: 10,
1214
+ width: "100%",
1215
+ padding: "10px 16px",
1216
+ textAlign: "left",
1217
+ background: "none",
1218
+ border: "none",
1219
+ cursor: "pointer",
1220
+ fontSize: 14,
1221
+ color: danger ? "#ef4444" : theme.text,
1222
+ fontWeight: 500,
1223
+ fontFamily: theme.fontFamily,
1224
+ boxSizing: "border-box"
1225
+ });
1226
+ return /* @__PURE__ */ jsxs6("div", { ref: dropdownRef, style: { position: "relative", display: "inline-block" }, children: [
1227
+ /* @__PURE__ */ jsx8("button", { type: "button", style: triggerStyle, onClick: () => setOpen((v) => !v), "aria-label": "User menu", children: /* @__PURE__ */ jsx8(
1228
+ UserAvatar,
152
1229
  {
153
- onClick: () => setOpen((v) => !v),
154
- style: {
155
- width: "36px",
156
- height: "36px",
157
- borderRadius: "50%",
158
- border: "2px solid #7c3aed",
159
- background: user?.avatarUrl ? "transparent" : "linear-gradient(135deg, #7c3aed, #4f46e5)",
160
- cursor: "pointer",
161
- padding: 0,
162
- overflow: "hidden",
163
- display: "flex",
164
- alignItems: "center",
165
- justifyContent: "center",
166
- color: "#fff",
167
- fontSize: "13px",
168
- fontWeight: 700
169
- },
170
- children: user?.avatarUrl ? /* @__PURE__ */ jsx4(
171
- "img",
1230
+ avatarUrl: user?.avatarUrl,
1231
+ displayName: user?.displayName,
1232
+ email: user?.email,
1233
+ size: 32,
1234
+ primaryStart: theme.primaryStart,
1235
+ primaryEnd: theme.primaryEnd
1236
+ }
1237
+ ) }),
1238
+ open && /* @__PURE__ */ jsxs6("div", { style: dropdownStyle, children: [
1239
+ /* @__PURE__ */ jsxs6("div", { style: headerStyle, children: [
1240
+ /* @__PURE__ */ jsx8(
1241
+ UserAvatar,
172
1242
  {
173
- src: user.avatarUrl,
174
- alt: user.displayName ?? "avatar",
175
- style: { width: "100%", height: "100%", objectFit: "cover" }
1243
+ avatarUrl: user?.avatarUrl,
1244
+ displayName: user?.displayName,
1245
+ email: user?.email,
1246
+ size: 40,
1247
+ primaryStart: theme.primaryStart,
1248
+ primaryEnd: theme.primaryEnd
176
1249
  }
177
- ) : initials
178
- }
179
- ),
180
- open && /* @__PURE__ */ jsxs(
181
- "div",
182
- {
183
- style: {
184
- position: "absolute",
185
- right: 0,
186
- top: "44px",
187
- minWidth: "200px",
188
- background: "#fff",
189
- border: "1px solid #e5e7eb",
190
- borderRadius: "12px",
191
- boxShadow: "0 8px 24px rgba(0,0,0,0.12)",
192
- zIndex: 9999,
193
- overflow: "hidden"
194
- },
195
- children: [
196
- /* @__PURE__ */ jsxs(
197
- "div",
198
- {
199
- style: {
200
- padding: "12px 16px",
201
- borderBottom: "1px solid #f3f4f6"
202
- },
203
- children: [
204
- user?.displayName && /* @__PURE__ */ jsx4("div", { style: { fontSize: "14px", fontWeight: 600, color: "#111827" }, children: user.displayName }),
205
- user?.email && /* @__PURE__ */ jsx4("div", { style: { fontSize: "12px", color: "#6b7280", marginTop: "2px" }, children: user.email })
206
- ]
207
- }
208
- ),
209
- /* @__PURE__ */ jsx4(
210
- "button",
211
- {
212
- onClick: async () => {
213
- setOpen(false);
214
- await signOut();
215
- },
216
- style: {
217
- display: "block",
218
- width: "100%",
219
- padding: "10px 16px",
220
- textAlign: "left",
221
- background: "none",
222
- border: "none",
223
- cursor: "pointer",
224
- fontSize: "14px",
225
- color: "#ef4444",
226
- fontWeight: 500
227
- },
228
- onMouseEnter: (e) => {
229
- e.currentTarget.style.background = "#fef2f2";
230
- },
231
- onMouseLeave: (e) => {
232
- e.currentTarget.style.background = "none";
233
- },
234
- children: "Sign out"
235
- }
236
- )
237
- ]
238
- }
239
- )
1250
+ ),
1251
+ /* @__PURE__ */ jsxs6("div", { style: { flex: 1, minWidth: 0 }, children: [
1252
+ user?.displayName && /* @__PURE__ */ jsx8("div", { style: { fontSize: 14, fontWeight: 600, color: theme.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: user.displayName }),
1253
+ user?.email && /* @__PURE__ */ jsx8("div", { style: { fontSize: 12, color: theme.textMuted, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", marginTop: 1 }, children: user.email })
1254
+ ] })
1255
+ ] }),
1256
+ activeOrganization && /* @__PURE__ */ jsx8(
1257
+ OrgSwitcherRow,
1258
+ {
1259
+ org: activeOrganization,
1260
+ primaryStart: theme.primaryStart,
1261
+ theme,
1262
+ client
1263
+ }
1264
+ ),
1265
+ userProfileUrl && /* @__PURE__ */ jsx8(
1266
+ MenuButton,
1267
+ {
1268
+ style: menuItemStyle(),
1269
+ onClick: () => {
1270
+ setOpen(false);
1271
+ window.location.assign(userProfileUrl);
1272
+ },
1273
+ icon: /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1274
+ /* @__PURE__ */ jsx8("path", { d: "M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" }),
1275
+ /* @__PURE__ */ jsx8("circle", { cx: "12", cy: "7", r: "4" })
1276
+ ] }),
1277
+ children: "Manage account"
1278
+ }
1279
+ ),
1280
+ /* @__PURE__ */ jsx8("div", { style: { borderTop: `1px solid ${theme.border}` }, children: /* @__PURE__ */ jsx8(
1281
+ MenuButton,
1282
+ {
1283
+ style: menuItemStyle(true),
1284
+ onClick: async () => {
1285
+ setOpen(false);
1286
+ await signOut();
1287
+ if (afterSignOutUrl) window.location.assign(afterSignOutUrl);
1288
+ },
1289
+ icon: /* @__PURE__ */ jsxs6("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1290
+ /* @__PURE__ */ jsx8("path", { d: "M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4" }),
1291
+ /* @__PURE__ */ jsx8("polyline", { points: "16 17 21 12 16 7" }),
1292
+ /* @__PURE__ */ jsx8("line", { x1: "21", y1: "12", x2: "9", y2: "12" })
1293
+ ] }),
1294
+ children: "Sign out"
1295
+ }
1296
+ ) })
1297
+ ] })
240
1298
  ] });
241
1299
  }
1300
+ function MenuButton({
1301
+ style: baseStyle2,
1302
+ onClick,
1303
+ icon,
1304
+ children
1305
+ }) {
1306
+ const [hovered, setHovered] = useState7(false);
1307
+ return /* @__PURE__ */ jsxs6(
1308
+ "button",
1309
+ {
1310
+ type: "button",
1311
+ style: {
1312
+ ...baseStyle2,
1313
+ background: hovered ? baseStyle2.color === "#ef4444" ? "#fef2f2" : "#f9fafb" : "none"
1314
+ },
1315
+ onClick,
1316
+ onMouseEnter: () => setHovered(true),
1317
+ onMouseLeave: () => setHovered(false),
1318
+ children: [
1319
+ icon,
1320
+ children
1321
+ ]
1322
+ }
1323
+ );
1324
+ }
1325
+ function OrgSwitcherRow({
1326
+ org,
1327
+ primaryStart,
1328
+ theme,
1329
+ client
1330
+ }) {
1331
+ return /* @__PURE__ */ jsxs6(
1332
+ "div",
1333
+ {
1334
+ style: {
1335
+ padding: "8px 16px",
1336
+ borderBottom: `1px solid ${theme.border}`,
1337
+ display: "flex",
1338
+ alignItems: "center",
1339
+ gap: 10
1340
+ },
1341
+ children: [
1342
+ org.logoUrl ? /* @__PURE__ */ jsx8("img", { src: org.logoUrl, alt: org.name, style: { width: 24, height: 24, borderRadius: 6, objectFit: "cover" } }) : /* @__PURE__ */ jsx8(
1343
+ "div",
1344
+ {
1345
+ style: {
1346
+ width: 24,
1347
+ height: 24,
1348
+ borderRadius: 6,
1349
+ background: `${primaryStart}22`,
1350
+ color: primaryStart,
1351
+ display: "flex",
1352
+ alignItems: "center",
1353
+ justifyContent: "center",
1354
+ fontSize: 11,
1355
+ fontWeight: 700
1356
+ },
1357
+ children: org.name[0]?.toUpperCase()
1358
+ }
1359
+ ),
1360
+ /* @__PURE__ */ jsxs6("div", { style: { flex: 1, minWidth: 0 }, children: [
1361
+ /* @__PURE__ */ jsx8("div", { style: { fontSize: 12, color: theme.textMuted }, children: "Organization" }),
1362
+ /* @__PURE__ */ jsx8("div", { style: { fontSize: 13, fontWeight: 600, color: theme.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: org.name })
1363
+ ] })
1364
+ ]
1365
+ }
1366
+ );
1367
+ }
1368
+ function UserButton({ appearance, afterSignOutUrl, userProfileUrl }) {
1369
+ const { branding } = useBranding();
1370
+ const effectiveBranding = { ...branding, ...appearance?.variables ?? {} };
1371
+ return /* @__PURE__ */ jsx8(ThemeProvider, { branding: effectiveBranding, style: { display: "inline-block" }, children: /* @__PURE__ */ jsx8(UserButtonInner, { afterSignOutUrl, userProfileUrl }) });
1372
+ }
242
1373
 
243
1374
  // src/SignedIn.tsx
244
- import { Fragment, jsx as jsx5 } from "react/jsx-runtime";
1375
+ import { Fragment as Fragment4, jsx as jsx9 } from "react/jsx-runtime";
245
1376
  function SignedIn({ children }) {
246
1377
  const { isSignedIn, isLoading } = useAuthon();
247
1378
  if (isLoading || !isSignedIn) return null;
248
- return /* @__PURE__ */ jsx5(Fragment, { children });
1379
+ return /* @__PURE__ */ jsx9(Fragment4, { children });
249
1380
  }
250
1381
 
251
1382
  // src/SignedOut.tsx
252
- import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
1383
+ import { Fragment as Fragment5, jsx as jsx10 } from "react/jsx-runtime";
253
1384
  function SignedOut({ children }) {
254
1385
  const { isSignedIn, isLoading } = useAuthon();
255
1386
  if (isLoading || isSignedIn) return null;
256
- return /* @__PURE__ */ jsx6(Fragment2, { children });
1387
+ return /* @__PURE__ */ jsx10(Fragment5, { children });
257
1388
  }
258
1389
 
259
1390
  // src/Protect.tsx
260
- import { Fragment as Fragment3, jsx as jsx7 } from "react/jsx-runtime";
1391
+ import { Fragment as Fragment6, jsx as jsx11 } from "react/jsx-runtime";
261
1392
  function Protect({ children, fallback = null, condition }) {
262
1393
  const { isSignedIn, isLoading, user } = useAuthon();
263
1394
  if (isLoading) return null;
264
- if (!isSignedIn || !user) return /* @__PURE__ */ jsx7(Fragment3, { children: fallback });
265
- if (condition && !condition(user)) return /* @__PURE__ */ jsx7(Fragment3, { children: fallback });
266
- return /* @__PURE__ */ jsx7(Fragment3, { children });
1395
+ if (!isSignedIn || !user) return /* @__PURE__ */ jsx11(Fragment6, { children: fallback });
1396
+ if (condition && !condition(user)) return /* @__PURE__ */ jsx11(Fragment6, { children: fallback });
1397
+ return /* @__PURE__ */ jsx11(Fragment6, { children });
267
1398
  }
268
1399
 
269
1400
  // src/SocialButton.tsx
270
- import { PROVIDER_COLORS, PROVIDER_DISPLAY_NAMES } from "@authon/shared";
271
- import { getProviderButtonConfig } from "@authon/js";
272
- import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs2 } from "react/jsx-runtime";
1401
+ import { PROVIDER_COLORS as PROVIDER_COLORS3, PROVIDER_DISPLAY_NAMES as PROVIDER_DISPLAY_NAMES3 } from "@authon/shared";
1402
+ import { getProviderButtonConfig as getProviderButtonConfig3 } from "@authon/js";
1403
+ import { Fragment as Fragment7, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
273
1404
  var baseStyle = {
274
1405
  display: "flex",
275
1406
  alignItems: "center",
@@ -306,16 +1437,16 @@ function SocialButton({
306
1437
  height = 48,
307
1438
  size = 48
308
1439
  }) {
309
- const colors = PROVIDER_COLORS[provider] || { bg: "#333", text: "#fff" };
310
- const displayName = PROVIDER_DISPLAY_NAMES[provider] || provider;
1440
+ const colors = PROVIDER_COLORS3[provider] || { bg: "#333", text: "#fff" };
1441
+ const displayName = PROVIDER_DISPLAY_NAMES3[provider] || provider;
311
1442
  const buttonLabel = label ?? `Continue with ${displayName}`;
312
1443
  const needsBorder = colors.bg.toLowerCase() === "#ffffff";
313
1444
  const resolvedIconSize = iconSize ?? (compact ? 24 : 20);
314
- const config = getProviderButtonConfig(provider);
1445
+ const config = getProviderButtonConfig3(provider);
315
1446
  const iconSvg = config.iconSvg.replace(/width="\d+"/, `width="${resolvedIconSize}"`).replace(/height="\d+"/, `height="${resolvedIconSize}"`);
316
1447
  const borderProps = needsBorder ? { border: "1px solid #dadce0" } : {};
317
1448
  if (compact) {
318
- return /* @__PURE__ */ jsx8(
1449
+ return /* @__PURE__ */ jsx12(
319
1450
  "button",
320
1451
  {
321
1452
  className,
@@ -331,7 +1462,7 @@ function SocialButton({
331
1462
  onClick: () => onClick(provider),
332
1463
  disabled: disabled || loading,
333
1464
  "aria-label": `Sign in with ${displayName}`,
334
- children: loading ? /* @__PURE__ */ jsx8(
1465
+ children: loading ? /* @__PURE__ */ jsx12(
335
1466
  "span",
336
1467
  {
337
1468
  style: {
@@ -344,7 +1475,7 @@ function SocialButton({
344
1475
  animation: "authon-spin 0.6s linear infinite"
345
1476
  }
346
1477
  }
347
- ) : /* @__PURE__ */ jsx8(
1478
+ ) : /* @__PURE__ */ jsx12(
348
1479
  "span",
349
1480
  {
350
1481
  style: { display: "flex", alignItems: "center", flexShrink: 0 },
@@ -354,7 +1485,7 @@ function SocialButton({
354
1485
  }
355
1486
  );
356
1487
  }
357
- return /* @__PURE__ */ jsx8(
1488
+ return /* @__PURE__ */ jsx12(
358
1489
  "button",
359
1490
  {
360
1491
  className,
@@ -370,7 +1501,7 @@ function SocialButton({
370
1501
  onClick: () => onClick(provider),
371
1502
  disabled: disabled || loading,
372
1503
  "aria-label": `Sign in with ${displayName}`,
373
- children: loading ? /* @__PURE__ */ jsx8(
1504
+ children: loading ? /* @__PURE__ */ jsx12(
374
1505
  "span",
375
1506
  {
376
1507
  style: {
@@ -383,23 +1514,23 @@ function SocialButton({
383
1514
  animation: "authon-spin 0.6s linear infinite"
384
1515
  }
385
1516
  }
386
- ) : /* @__PURE__ */ jsxs2(Fragment4, { children: [
387
- /* @__PURE__ */ jsx8(
1517
+ ) : /* @__PURE__ */ jsxs7(Fragment7, { children: [
1518
+ /* @__PURE__ */ jsx12(
388
1519
  "span",
389
1520
  {
390
1521
  style: { display: "flex", alignItems: "center", flexShrink: 0 },
391
1522
  dangerouslySetInnerHTML: { __html: iconSvg }
392
1523
  }
393
1524
  ),
394
- /* @__PURE__ */ jsx8("span", { style: { fontSize: 15, fontWeight: 600, whiteSpace: "nowrap" }, children: buttonLabel })
1525
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: 15, fontWeight: 600, whiteSpace: "nowrap" }, children: buttonLabel })
395
1526
  ] })
396
1527
  }
397
1528
  );
398
1529
  }
399
1530
 
400
1531
  // src/SocialButtons.tsx
401
- import { useState as useState3, useEffect as useEffect5 } from "react";
402
- import { jsx as jsx9 } from "react/jsx-runtime";
1532
+ import { useState as useState8, useEffect as useEffect4 } from "react";
1533
+ import { jsx as jsx13 } from "react/jsx-runtime";
403
1534
  function SocialButtons({
404
1535
  onSuccess,
405
1536
  onError,
@@ -411,9 +1542,9 @@ function SocialButtons({
411
1542
  buttonProps
412
1543
  }) {
413
1544
  const { client } = useAuthon();
414
- const [providers, setProviders] = useState3([]);
415
- const [loadingProvider, setLoadingProvider] = useState3(null);
416
- useEffect5(() => {
1545
+ const [providers, setProviders] = useState8([]);
1546
+ const [loadingProvider, setLoadingProvider] = useState8(null);
1547
+ useEffect4(() => {
417
1548
  if (!client) return;
418
1549
  client.getProviders().then((p) => setProviders(p));
419
1550
  }, [client]);
@@ -433,7 +1564,7 @@ function SocialButtons({
433
1564
  }
434
1565
  };
435
1566
  const containerStyle = compact ? { display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: resolvedGap, ...userStyle } : { display: "flex", flexDirection: "column", gap: resolvedGap, ...userStyle };
436
- return /* @__PURE__ */ jsx9("div", { className, style: containerStyle, children: providers.map((provider) => /* @__PURE__ */ jsx9(
1567
+ return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: providers.map((provider) => /* @__PURE__ */ jsx13(
437
1568
  SocialButton,
438
1569
  {
439
1570
  provider,
@@ -447,17 +1578,723 @@ function SocialButtons({
447
1578
  provider
448
1579
  )) });
449
1580
  }
1581
+
1582
+ // src/useAuthonMfa.ts
1583
+ import { useCallback as useCallback4, useContext as useContext3, useState as useState9 } from "react";
1584
+ function useAuthonMfa() {
1585
+ const ctx = useContext3(AuthonContext);
1586
+ if (!ctx) throw new Error("useAuthonMfa must be used within <AuthonProvider>");
1587
+ const [isLoading, setIsLoading] = useState9(false);
1588
+ const [error, setError] = useState9(null);
1589
+ const wrap = useCallback4(
1590
+ async (fn) => {
1591
+ setIsLoading(true);
1592
+ setError(null);
1593
+ try {
1594
+ const result = await fn();
1595
+ return result;
1596
+ } catch (err) {
1597
+ setError(err instanceof Error ? err : new Error(String(err)));
1598
+ return null;
1599
+ } finally {
1600
+ setIsLoading(false);
1601
+ }
1602
+ },
1603
+ []
1604
+ );
1605
+ const setupMfa = useCallback4(async () => {
1606
+ return wrap(() => ctx.client.setupMfa());
1607
+ }, [ctx.client, wrap]);
1608
+ const verifyMfaSetup = useCallback4(
1609
+ async (code) => {
1610
+ const result = await wrap(() => ctx.client.verifyMfaSetup(code));
1611
+ return result !== null;
1612
+ },
1613
+ [ctx.client, wrap]
1614
+ );
1615
+ const verifyMfa = useCallback4(
1616
+ async (mfaToken, code) => {
1617
+ const result = await wrap(() => ctx.client.verifyMfa(mfaToken, code));
1618
+ return result !== null;
1619
+ },
1620
+ [ctx.client, wrap]
1621
+ );
1622
+ const disableMfa = useCallback4(
1623
+ async (code) => {
1624
+ const result = await wrap(() => ctx.client.disableMfa(code));
1625
+ return result !== null;
1626
+ },
1627
+ [ctx.client, wrap]
1628
+ );
1629
+ const getMfaStatus = useCallback4(async () => {
1630
+ return wrap(() => ctx.client.getMfaStatus());
1631
+ }, [ctx.client, wrap]);
1632
+ const regenerateBackupCodes = useCallback4(
1633
+ async (code) => {
1634
+ return wrap(() => ctx.client.regenerateBackupCodes(code));
1635
+ },
1636
+ [ctx.client, wrap]
1637
+ );
1638
+ return {
1639
+ setupMfa,
1640
+ verifyMfaSetup,
1641
+ verifyMfa,
1642
+ disableMfa,
1643
+ getMfaStatus,
1644
+ regenerateBackupCodes,
1645
+ isLoading,
1646
+ error
1647
+ };
1648
+ }
1649
+
1650
+ // src/useAuthonPasskeys.ts
1651
+ import { useCallback as useCallback5, useContext as useContext4, useState as useState10 } from "react";
1652
+ function useAuthonPasskeys() {
1653
+ const ctx = useContext4(AuthonContext);
1654
+ if (!ctx) throw new Error("useAuthonPasskeys must be used within <AuthonProvider>");
1655
+ const [isLoading, setIsLoading] = useState10(false);
1656
+ const [error, setError] = useState10(null);
1657
+ const wrap = useCallback5(
1658
+ async (fn) => {
1659
+ setIsLoading(true);
1660
+ setError(null);
1661
+ try {
1662
+ return await fn();
1663
+ } catch (err) {
1664
+ setError(err instanceof Error ? err : new Error(String(err)));
1665
+ return null;
1666
+ } finally {
1667
+ setIsLoading(false);
1668
+ }
1669
+ },
1670
+ []
1671
+ );
1672
+ const registerPasskey = useCallback5(
1673
+ (name) => wrap(() => ctx.client.registerPasskey(name)),
1674
+ [ctx.client, wrap]
1675
+ );
1676
+ const authenticateWithPasskey = useCallback5(
1677
+ async (email) => {
1678
+ const result = await wrap(() => ctx.client.authenticateWithPasskey(email));
1679
+ return result !== null;
1680
+ },
1681
+ [ctx.client, wrap]
1682
+ );
1683
+ const listPasskeys = useCallback5(
1684
+ () => wrap(() => ctx.client.listPasskeys()),
1685
+ [ctx.client, wrap]
1686
+ );
1687
+ const renamePasskey = useCallback5(
1688
+ (id, name) => wrap(() => ctx.client.renamePasskey(id, name)),
1689
+ [ctx.client, wrap]
1690
+ );
1691
+ const revokePasskey = useCallback5(
1692
+ async (id) => {
1693
+ const result = await wrap(() => ctx.client.revokePasskey(id));
1694
+ return result !== null;
1695
+ },
1696
+ [ctx.client, wrap]
1697
+ );
1698
+ return {
1699
+ registerPasskey,
1700
+ authenticateWithPasskey,
1701
+ listPasskeys,
1702
+ renamePasskey,
1703
+ revokePasskey,
1704
+ isLoading,
1705
+ error
1706
+ };
1707
+ }
1708
+
1709
+ // src/useAuthonPasswordless.ts
1710
+ import { useCallback as useCallback6, useContext as useContext5, useState as useState11 } from "react";
1711
+ function useAuthonPasswordless() {
1712
+ const ctx = useContext5(AuthonContext);
1713
+ if (!ctx) throw new Error("useAuthonPasswordless must be used within <AuthonProvider>");
1714
+ const [isLoading, setIsLoading] = useState11(false);
1715
+ const [error, setError] = useState11(null);
1716
+ const wrap = useCallback6(
1717
+ async (fn) => {
1718
+ setIsLoading(true);
1719
+ setError(null);
1720
+ try {
1721
+ return await fn();
1722
+ } catch (err) {
1723
+ setError(err instanceof Error ? err : new Error(String(err)));
1724
+ return null;
1725
+ } finally {
1726
+ setIsLoading(false);
1727
+ }
1728
+ },
1729
+ []
1730
+ );
1731
+ const sendMagicLink = useCallback6(
1732
+ async (email) => {
1733
+ const result = await wrap(() => ctx.client.sendMagicLink(email));
1734
+ return result !== null;
1735
+ },
1736
+ [ctx.client, wrap]
1737
+ );
1738
+ const sendEmailOtp = useCallback6(
1739
+ async (email) => {
1740
+ const result = await wrap(() => ctx.client.sendEmailOtp(email));
1741
+ return result !== null;
1742
+ },
1743
+ [ctx.client, wrap]
1744
+ );
1745
+ const verifyPasswordless = useCallback6(
1746
+ async (opts) => {
1747
+ const result = await wrap(() => ctx.client.verifyPasswordless(opts));
1748
+ return result !== null;
1749
+ },
1750
+ [ctx.client, wrap]
1751
+ );
1752
+ return {
1753
+ sendMagicLink,
1754
+ sendEmailOtp,
1755
+ verifyPasswordless,
1756
+ isLoading,
1757
+ error
1758
+ };
1759
+ }
1760
+
1761
+ // src/useAuthonWeb3.ts
1762
+ import { useCallback as useCallback7, useContext as useContext6, useState as useState12 } from "react";
1763
+ function useAuthonWeb3() {
1764
+ const ctx = useContext6(AuthonContext);
1765
+ if (!ctx) throw new Error("useAuthonWeb3 must be used within <AuthonProvider>");
1766
+ const [isLoading, setIsLoading] = useState12(false);
1767
+ const [error, setError] = useState12(null);
1768
+ const wrap = useCallback7(
1769
+ async (fn) => {
1770
+ setIsLoading(true);
1771
+ setError(null);
1772
+ try {
1773
+ return await fn();
1774
+ } catch (err) {
1775
+ setError(err instanceof Error ? err : new Error(String(err)));
1776
+ return null;
1777
+ } finally {
1778
+ setIsLoading(false);
1779
+ }
1780
+ },
1781
+ []
1782
+ );
1783
+ const getNonce = useCallback7(
1784
+ (address, chain, walletType, chainId) => wrap(() => ctx.client.web3GetNonce(address, chain, walletType, chainId)),
1785
+ [ctx.client, wrap]
1786
+ );
1787
+ const verify = useCallback7(
1788
+ async (message, signature, address, chain, walletType) => {
1789
+ const result = await wrap(
1790
+ () => ctx.client.web3Verify(message, signature, address, chain, walletType)
1791
+ );
1792
+ return result !== null;
1793
+ },
1794
+ [ctx.client, wrap]
1795
+ );
1796
+ const listWallets = useCallback7(
1797
+ () => wrap(() => ctx.client.listWallets()),
1798
+ [ctx.client, wrap]
1799
+ );
1800
+ const linkWallet = useCallback7(
1801
+ (params) => wrap(() => ctx.client.linkWallet(params)),
1802
+ [ctx.client, wrap]
1803
+ );
1804
+ const unlinkWallet = useCallback7(
1805
+ async (walletId) => {
1806
+ const result = await wrap(() => ctx.client.unlinkWallet(walletId));
1807
+ return result !== null;
1808
+ },
1809
+ [ctx.client, wrap]
1810
+ );
1811
+ return {
1812
+ getNonce,
1813
+ verify,
1814
+ listWallets,
1815
+ linkWallet,
1816
+ unlinkWallet,
1817
+ isLoading,
1818
+ error
1819
+ };
1820
+ }
1821
+
1822
+ // src/useAuthonSessions.ts
1823
+ import { useCallback as useCallback8, useContext as useContext7, useState as useState13 } from "react";
1824
+ function useAuthonSessions() {
1825
+ const ctx = useContext7(AuthonContext);
1826
+ if (!ctx) throw new Error("useAuthonSessions must be used within <AuthonProvider>");
1827
+ const [isLoading, setIsLoading] = useState13(false);
1828
+ const [error, setError] = useState13(null);
1829
+ const wrap = useCallback8(
1830
+ async (fn) => {
1831
+ setIsLoading(true);
1832
+ setError(null);
1833
+ try {
1834
+ return await fn();
1835
+ } catch (err) {
1836
+ setError(err instanceof Error ? err : new Error(String(err)));
1837
+ return null;
1838
+ } finally {
1839
+ setIsLoading(false);
1840
+ }
1841
+ },
1842
+ []
1843
+ );
1844
+ const listSessions = useCallback8(
1845
+ () => wrap(() => ctx.client.listSessions()),
1846
+ [ctx.client, wrap]
1847
+ );
1848
+ const revokeSession = useCallback8(
1849
+ async (sessionId) => {
1850
+ const result = await wrap(() => ctx.client.revokeSession(sessionId));
1851
+ return result !== null;
1852
+ },
1853
+ [ctx.client, wrap]
1854
+ );
1855
+ return {
1856
+ listSessions,
1857
+ revokeSession,
1858
+ isLoading,
1859
+ error
1860
+ };
1861
+ }
1862
+
1863
+ // src/useOrganization.ts
1864
+ import { useContext as useContext8, useEffect as useEffect5, useState as useState14 } from "react";
1865
+ function useOrganization() {
1866
+ const ctx = useContext8(AuthonContext);
1867
+ if (!ctx) throw new Error("useOrganization must be used within <AuthonProvider>");
1868
+ const [members, setMembers] = useState14([]);
1869
+ const [isLoaded, setIsLoaded] = useState14(false);
1870
+ const organization = ctx.activeOrganization;
1871
+ useEffect5(() => {
1872
+ if (!organization || !ctx.client) {
1873
+ setMembers([]);
1874
+ setIsLoaded(!organization);
1875
+ return;
1876
+ }
1877
+ setIsLoaded(false);
1878
+ ctx.client.organizations.getMembers(organization.id).then((m) => {
1879
+ setMembers(m);
1880
+ setIsLoaded(true);
1881
+ }).catch(() => {
1882
+ setMembers([]);
1883
+ setIsLoaded(true);
1884
+ });
1885
+ }, [organization?.id, ctx.client]);
1886
+ return {
1887
+ organization,
1888
+ members,
1889
+ isLoaded
1890
+ };
1891
+ }
1892
+
1893
+ // src/useOrganizationList.ts
1894
+ import { useCallback as useCallback10, useContext as useContext9, useEffect as useEffect6, useState as useState15 } from "react";
1895
+ function useOrganizationList() {
1896
+ const ctx = useContext9(AuthonContext);
1897
+ if (!ctx) throw new Error("useOrganizationList must be used within <AuthonProvider>");
1898
+ const [organizations, setOrganizations] = useState15([]);
1899
+ const [isLoaded, setIsLoaded] = useState15(false);
1900
+ useEffect6(() => {
1901
+ if (!ctx.client || !ctx.isSignedIn) {
1902
+ setOrganizations([]);
1903
+ setIsLoaded(!ctx.isSignedIn);
1904
+ return;
1905
+ }
1906
+ setIsLoaded(false);
1907
+ ctx.client.organizations.list().then((res) => {
1908
+ setOrganizations(res.data);
1909
+ setIsLoaded(true);
1910
+ }).catch(() => {
1911
+ setOrganizations([]);
1912
+ setIsLoaded(true);
1913
+ });
1914
+ }, [ctx.client, ctx.isSignedIn]);
1915
+ const createOrganization = useCallback10(
1916
+ async (params) => {
1917
+ if (!ctx.client) return null;
1918
+ try {
1919
+ const org = await ctx.client.organizations.create(params);
1920
+ setOrganizations((prev) => [...prev, org]);
1921
+ return org;
1922
+ } catch {
1923
+ return null;
1924
+ }
1925
+ },
1926
+ [ctx.client]
1927
+ );
1928
+ const setActive = useCallback10(
1929
+ (org) => {
1930
+ ctx.setActiveOrganization(org);
1931
+ },
1932
+ [ctx.setActiveOrganization]
1933
+ );
1934
+ return {
1935
+ organizations,
1936
+ isLoaded,
1937
+ createOrganization,
1938
+ setActive
1939
+ };
1940
+ }
1941
+
1942
+ // src/components/UserProfile.tsx
1943
+ import { useCallback as useCallback11, useEffect as useEffect7, useState as useState16 } from "react";
1944
+ import { Fragment as Fragment8, jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
1945
+ function ProfileTab() {
1946
+ const theme = useTheme();
1947
+ const { user, client } = useAuthon();
1948
+ const [displayName, setDisplayName] = useState16(user?.displayName ?? "");
1949
+ const [phone, setPhone] = useState16(user?.phone ?? "");
1950
+ const [loading, setLoading] = useState16(false);
1951
+ const [success, setSuccess] = useState16("");
1952
+ const [error, setError] = useState16("");
1953
+ const handleSave = async () => {
1954
+ if (!client) return;
1955
+ setLoading(true);
1956
+ setError("");
1957
+ setSuccess("");
1958
+ try {
1959
+ await client.updateProfile({ displayName: displayName || void 0, phone: phone || void 0 });
1960
+ setSuccess("Profile updated");
1961
+ } catch (e) {
1962
+ setError(e?.message ?? "Failed to update profile");
1963
+ } finally {
1964
+ setLoading(false);
1965
+ }
1966
+ };
1967
+ const initials = user?.displayName ? user.displayName.split(" ").map((n) => n[0]).join("").toUpperCase().slice(0, 2) : (user?.email?.[0] ?? "?").toUpperCase();
1968
+ return /* @__PURE__ */ jsxs8("div", { style: { display: "flex", flexDirection: "column", gap: 24 }, children: [
1969
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", gap: 16 }, children: [
1970
+ /* @__PURE__ */ jsx14(
1971
+ "div",
1972
+ {
1973
+ style: {
1974
+ width: 64,
1975
+ height: 64,
1976
+ borderRadius: "50%",
1977
+ background: user?.avatarUrl ? "transparent" : `linear-gradient(135deg, ${theme.primaryStart}, ${theme.primaryEnd})`,
1978
+ color: "#fff",
1979
+ display: "flex",
1980
+ alignItems: "center",
1981
+ justifyContent: "center",
1982
+ fontSize: 22,
1983
+ fontWeight: 700,
1984
+ overflow: "hidden",
1985
+ flexShrink: 0
1986
+ },
1987
+ children: user?.avatarUrl ? /* @__PURE__ */ jsx14("img", { src: user.avatarUrl, alt: "avatar", style: { width: "100%", height: "100%", objectFit: "cover" } }) : initials
1988
+ }
1989
+ ),
1990
+ /* @__PURE__ */ jsxs8("div", { children: [
1991
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 16, fontWeight: 600, color: theme.text }, children: user?.displayName ?? "User" }),
1992
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 13, color: theme.textMuted }, children: user?.email }),
1993
+ !user?.emailVerified && /* @__PURE__ */ jsx14("span", { style: { fontSize: 11, color: "#d97706", background: "#fef3c7", padding: "2px 8px", borderRadius: 99, fontWeight: 500, marginTop: 4, display: "inline-block" }, children: "Email not verified" })
1994
+ ] })
1995
+ ] }),
1996
+ success && /* @__PURE__ */ jsx14("div", { style: { padding: "10px 14px", borderRadius: theme.borderRadius, background: "#f0fdf4", border: "1px solid #bbf7d0", color: "#166534", fontSize: 13 }, children: success }),
1997
+ error && /* @__PURE__ */ jsx14("div", { style: { padding: "10px 14px", borderRadius: theme.borderRadius, background: "#fef2f2", border: "1px solid #fecaca", color: "#dc2626", fontSize: 13 }, children: error }),
1998
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
1999
+ /* @__PURE__ */ jsx14(Input, { label: "Display name", value: displayName, onChange: setDisplayName, placeholder: "Your name" }),
2000
+ /* @__PURE__ */ jsx14(Input, { label: "Email", type: "email", value: user?.email ?? "", disabled: true, placeholder: "Email" }),
2001
+ /* @__PURE__ */ jsx14(Input, { label: "Phone", type: "tel", value: phone, onChange: setPhone, placeholder: "+1 (555) 000-0000" })
2002
+ ] }),
2003
+ /* @__PURE__ */ jsx14(Button, { variant: "primary", onClick: handleSave, loading, style: { alignSelf: "flex-start", minWidth: 120 }, children: "Save changes" })
2004
+ ] });
2005
+ }
2006
+ function SecurityTab() {
2007
+ const theme = useTheme();
2008
+ const { client } = useAuthon();
2009
+ const [currentPw, setCurrentPw] = useState16("");
2010
+ const [newPw, setNewPw] = useState16("");
2011
+ const [confirmPw, setConfirmPw] = useState16("");
2012
+ const [pwLoading, setPwLoading] = useState16(false);
2013
+ const [pwError, setPwError] = useState16("");
2014
+ const [pwSuccess, setPwSuccess] = useState16("");
2015
+ const [mfaStatus, setMfaStatus] = useState16(null);
2016
+ const [mfaLoading, setMfaLoading] = useState16(false);
2017
+ useEffect7(() => {
2018
+ if (!client) return;
2019
+ client.getMfaStatus().then(setMfaStatus).catch(() => null);
2020
+ }, [client]);
2021
+ const handlePasswordChange = async () => {
2022
+ if (!client) return;
2023
+ if (newPw !== confirmPw) {
2024
+ setPwError("Passwords do not match");
2025
+ return;
2026
+ }
2027
+ if (newPw.length < 8) {
2028
+ setPwError("Password must be at least 8 characters");
2029
+ return;
2030
+ }
2031
+ setPwLoading(true);
2032
+ setPwError("");
2033
+ setPwSuccess("");
2034
+ try {
2035
+ await client.updateProfile({ displayName: void 0 });
2036
+ setPwSuccess("Password updated");
2037
+ setCurrentPw("");
2038
+ setNewPw("");
2039
+ setConfirmPw("");
2040
+ } catch (e) {
2041
+ setPwError(e?.message ?? "Failed to update password");
2042
+ } finally {
2043
+ setPwLoading(false);
2044
+ }
2045
+ };
2046
+ const sectionTitle = {
2047
+ fontSize: 15,
2048
+ fontWeight: 600,
2049
+ color: theme.text,
2050
+ marginBottom: 16,
2051
+ paddingBottom: 10,
2052
+ borderBottom: `1px solid ${theme.border}`
2053
+ };
2054
+ return /* @__PURE__ */ jsxs8("div", { style: { display: "flex", flexDirection: "column", gap: 32 }, children: [
2055
+ /* @__PURE__ */ jsxs8("div", { children: [
2056
+ /* @__PURE__ */ jsx14("div", { style: sectionTitle, children: "Change password" }),
2057
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: [
2058
+ /* @__PURE__ */ jsx14(Input, { label: "Current password", type: "password", value: currentPw, onChange: setCurrentPw, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", autoComplete: "current-password" }),
2059
+ /* @__PURE__ */ jsx14(Input, { label: "New password", type: "password", value: newPw, onChange: setNewPw, placeholder: "Minimum 8 characters", autoComplete: "new-password" }),
2060
+ /* @__PURE__ */ jsx14(Input, { label: "Confirm new password", type: "password", value: confirmPw, onChange: setConfirmPw, placeholder: "Repeat new password", autoComplete: "new-password" }),
2061
+ pwError && /* @__PURE__ */ jsx14("div", { style: { color: "#dc2626", fontSize: 13 }, children: pwError }),
2062
+ pwSuccess && /* @__PURE__ */ jsx14("div", { style: { color: "#166534", fontSize: 13 }, children: pwSuccess }),
2063
+ /* @__PURE__ */ jsx14(Button, { variant: "primary", onClick: handlePasswordChange, loading: pwLoading, style: { alignSelf: "flex-start", minWidth: 160 }, children: "Update password" })
2064
+ ] })
2065
+ ] }),
2066
+ /* @__PURE__ */ jsxs8("div", { children: [
2067
+ /* @__PURE__ */ jsx14("div", { style: sectionTitle, children: "Two-factor authentication" }),
2068
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", padding: "14px 0" }, children: [
2069
+ /* @__PURE__ */ jsxs8("div", { children: [
2070
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 14, fontWeight: 500, color: theme.text }, children: "Authenticator app" }),
2071
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 13, color: theme.textMuted, marginTop: 2 }, children: mfaStatus?.enabled ? `Enabled \xB7 ${mfaStatus.backupCodesRemaining} backup codes remaining` : "Add extra security to your account" })
2072
+ ] }),
2073
+ /* @__PURE__ */ jsx14("div", { style: { display: "flex", alignItems: "center", gap: 8 }, children: /* @__PURE__ */ jsxs8(
2074
+ "span",
2075
+ {
2076
+ style: {
2077
+ display: "inline-flex",
2078
+ alignItems: "center",
2079
+ gap: 4,
2080
+ padding: "3px 10px",
2081
+ borderRadius: 99,
2082
+ fontSize: 12,
2083
+ fontWeight: 600,
2084
+ background: mfaStatus?.enabled ? "#f0fdf4" : "#f3f4f6",
2085
+ color: mfaStatus?.enabled ? "#166534" : theme.textMuted
2086
+ },
2087
+ children: [
2088
+ /* @__PURE__ */ jsx14("span", { style: { width: 6, height: 6, borderRadius: "50%", background: mfaStatus?.enabled ? "#22c55e" : "#9ca3af", display: "inline-block" } }),
2089
+ mfaStatus?.enabled ? "Enabled" : "Disabled"
2090
+ ]
2091
+ }
2092
+ ) })
2093
+ ] })
2094
+ ] })
2095
+ ] });
2096
+ }
2097
+ function SessionsTab() {
2098
+ const theme = useTheme();
2099
+ const { client } = useAuthon();
2100
+ const [sessions, setSessions] = useState16([]);
2101
+ const [loading, setLoading] = useState16(true);
2102
+ const [revoking, setRevoking] = useState16(null);
2103
+ const loadSessions = useCallback11(async () => {
2104
+ if (!client) return;
2105
+ setLoading(true);
2106
+ try {
2107
+ const data = await client.listSessions();
2108
+ setSessions(data);
2109
+ } catch {
2110
+ } finally {
2111
+ setLoading(false);
2112
+ }
2113
+ }, [client]);
2114
+ useEffect7(() => {
2115
+ loadSessions();
2116
+ }, [loadSessions]);
2117
+ const handleRevoke = async (sessionId) => {
2118
+ if (!client) return;
2119
+ setRevoking(sessionId);
2120
+ try {
2121
+ await client.revokeSession(sessionId);
2122
+ setSessions((prev) => prev.filter((s) => s.id !== sessionId));
2123
+ } catch {
2124
+ } finally {
2125
+ setRevoking(null);
2126
+ }
2127
+ };
2128
+ const sectionTitle = {
2129
+ fontSize: 15,
2130
+ fontWeight: 600,
2131
+ color: theme.text,
2132
+ marginBottom: 16,
2133
+ paddingBottom: 10,
2134
+ borderBottom: `1px solid ${theme.border}`
2135
+ };
2136
+ if (loading) {
2137
+ return /* @__PURE__ */ jsx14("div", { style: { display: "flex", justifyContent: "center", padding: 40 }, children: /* @__PURE__ */ jsx14("span", { style: { width: 24, height: 24, border: `3px solid ${theme.primaryStart}33`, borderTopColor: theme.primaryStart, borderRadius: "50%", display: "inline-block", animation: "authon-spin 0.7s linear infinite" } }) });
2138
+ }
2139
+ return /* @__PURE__ */ jsxs8("div", { children: [
2140
+ /* @__PURE__ */ jsx14("div", { style: sectionTitle, children: "Active sessions" }),
2141
+ /* @__PURE__ */ jsx14("div", { style: { display: "flex", flexDirection: "column", gap: 10 }, children: sessions.length === 0 ? /* @__PURE__ */ jsx14("div", { style: { color: theme.textMuted, fontSize: 14, textAlign: "center", padding: "24px 0" }, children: "No active sessions" }) : sessions.map((session) => /* @__PURE__ */ jsxs8(
2142
+ "div",
2143
+ {
2144
+ style: {
2145
+ display: "flex",
2146
+ alignItems: "center",
2147
+ justifyContent: "space-between",
2148
+ padding: "12px 14px",
2149
+ borderRadius: theme.borderRadius,
2150
+ border: `1px solid ${theme.border}`,
2151
+ gap: 12
2152
+ },
2153
+ children: [
2154
+ /* @__PURE__ */ jsxs8("div", { style: { display: "flex", alignItems: "center", gap: 12, flex: 1, minWidth: 0 }, children: [
2155
+ /* @__PURE__ */ jsx14("div", { style: { width: 36, height: 36, borderRadius: "50%", background: `${theme.primaryStart}18`, color: theme.primaryStart, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsxs8("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2156
+ /* @__PURE__ */ jsx14("rect", { x: "2", y: "3", width: "20", height: "14", rx: "2", ry: "2" }),
2157
+ /* @__PURE__ */ jsx14("line", { x1: "8", y1: "21", x2: "16", y2: "21" }),
2158
+ /* @__PURE__ */ jsx14("line", { x1: "12", y1: "17", x2: "12", y2: "21" })
2159
+ ] }) }),
2160
+ /* @__PURE__ */ jsxs8("div", { style: { minWidth: 0 }, children: [
2161
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 13, fontWeight: 500, color: theme.text, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: session.userAgent ? session.userAgent.length > 50 ? session.userAgent.slice(0, 50) + "\u2026" : session.userAgent : "Unknown device" }),
2162
+ /* @__PURE__ */ jsxs8("div", { style: { fontSize: 12, color: theme.textMuted, marginTop: 2 }, children: [
2163
+ session.ipAddress ?? "Unknown IP",
2164
+ session.lastActiveAt && /* @__PURE__ */ jsxs8(Fragment8, { children: [
2165
+ " \xB7 ",
2166
+ formatRelative(session.lastActiveAt)
2167
+ ] })
2168
+ ] })
2169
+ ] })
2170
+ ] }),
2171
+ /* @__PURE__ */ jsx14(
2172
+ Button,
2173
+ {
2174
+ variant: "outline",
2175
+ size: "sm",
2176
+ loading: revoking === session.id,
2177
+ onClick: () => handleRevoke(session.id),
2178
+ children: "Revoke"
2179
+ }
2180
+ )
2181
+ ]
2182
+ },
2183
+ session.id
2184
+ )) })
2185
+ ] });
2186
+ }
2187
+ function formatRelative(dateStr) {
2188
+ const diff = Date.now() - new Date(dateStr).getTime();
2189
+ const mins = Math.floor(diff / 6e4);
2190
+ if (mins < 1) return "Just now";
2191
+ if (mins < 60) return `${mins}m ago`;
2192
+ const hours = Math.floor(mins / 60);
2193
+ if (hours < 24) return `${hours}h ago`;
2194
+ const days = Math.floor(hours / 24);
2195
+ return `${days}d ago`;
2196
+ }
2197
+ function UserProfilePanel() {
2198
+ const theme = useTheme();
2199
+ const [tab, setTab] = useState16("profile");
2200
+ const panelStyle = {
2201
+ width: "100%",
2202
+ maxWidth: 640,
2203
+ background: theme.bg,
2204
+ borderRadius: `calc(${theme.borderRadius} + 4px)`,
2205
+ boxShadow: "0 4px 32px rgba(0,0,0,0.10)",
2206
+ overflow: "hidden",
2207
+ fontFamily: theme.fontFamily
2208
+ };
2209
+ const tabBarStyle = {
2210
+ display: "flex",
2211
+ borderBottom: `1px solid ${theme.border}`,
2212
+ padding: "0 24px"
2213
+ };
2214
+ const tabBtnStyle = (active) => ({
2215
+ padding: "14px 16px",
2216
+ background: "none",
2217
+ border: "none",
2218
+ borderBottom: active ? `2px solid ${theme.primaryStart}` : "2px solid transparent",
2219
+ color: active ? theme.primaryStart : theme.textMuted,
2220
+ fontWeight: active ? 600 : 400,
2221
+ fontSize: 14,
2222
+ cursor: "pointer",
2223
+ fontFamily: theme.fontFamily,
2224
+ marginBottom: -1
2225
+ });
2226
+ const contentStyle = {
2227
+ padding: "28px 28px"
2228
+ };
2229
+ const tabs = [
2230
+ { key: "profile", label: "Profile" },
2231
+ { key: "security", label: "Security" },
2232
+ { key: "sessions", label: "Sessions" }
2233
+ ];
2234
+ return /* @__PURE__ */ jsxs8("div", { style: panelStyle, children: [
2235
+ /* @__PURE__ */ jsx14("style", { children: `@keyframes authon-spin { to { transform: rotate(360deg); } }` }),
2236
+ /* @__PURE__ */ jsx14("div", { style: tabBarStyle, children: tabs.map(({ key, label }) => /* @__PURE__ */ jsx14(
2237
+ "button",
2238
+ {
2239
+ type: "button",
2240
+ style: tabBtnStyle(tab === key),
2241
+ onClick: () => setTab(key),
2242
+ children: label
2243
+ },
2244
+ key
2245
+ )) }),
2246
+ /* @__PURE__ */ jsxs8("div", { style: contentStyle, children: [
2247
+ tab === "profile" && /* @__PURE__ */ jsx14(ProfileTab, {}),
2248
+ tab === "security" && /* @__PURE__ */ jsx14(SecurityTab, {}),
2249
+ tab === "sessions" && /* @__PURE__ */ jsx14(SessionsTab, {})
2250
+ ] })
2251
+ ] });
2252
+ }
2253
+ function UserProfile({ appearance }) {
2254
+ const { branding } = useBranding();
2255
+ const effectiveBranding = { ...branding, ...appearance?.variables ?? {} };
2256
+ return /* @__PURE__ */ jsx14(ThemeProvider, { branding: effectiveBranding, style: { display: "flex", justifyContent: "center" }, children: /* @__PURE__ */ jsx14(UserProfilePanel, {}) });
2257
+ }
2258
+
2259
+ // src/components/shared/ProviderIcon.tsx
2260
+ import { getProviderButtonConfig as getProviderButtonConfig4 } from "@authon/js";
2261
+ import { jsx as jsx15 } from "react/jsx-runtime";
2262
+ function ProviderIcon({ provider, size = 20 }) {
2263
+ const config = getProviderButtonConfig4(provider);
2264
+ const svg = config.iconSvg.replace(/width="\d+"/, `width="${size}"`).replace(/height="\d+"/, `height="${size}"`);
2265
+ return /* @__PURE__ */ jsx15(
2266
+ "span",
2267
+ {
2268
+ style: { display: "flex", alignItems: "center", flexShrink: 0 },
2269
+ dangerouslySetInnerHTML: { __html: svg }
2270
+ }
2271
+ );
2272
+ }
450
2273
  export {
451
2274
  AuthonProvider,
2275
+ Button,
2276
+ Divider,
2277
+ Input,
452
2278
  Protect,
2279
+ ProviderIcon,
453
2280
  SignIn,
454
2281
  SignUp,
455
2282
  SignedIn,
456
2283
  SignedOut,
457
2284
  SocialButton,
458
2285
  SocialButtons,
2286
+ ThemeProvider,
459
2287
  UserButton,
2288
+ UserProfile,
460
2289
  useAuthon,
2290
+ useAuthonMfa,
2291
+ useAuthonPasskeys,
2292
+ useAuthonPasswordless,
2293
+ useAuthonSessions,
2294
+ useAuthonWeb3,
2295
+ useBranding,
2296
+ useOrganization,
2297
+ useOrganizationList,
461
2298
  useUser
462
2299
  };
463
2300
  //# sourceMappingURL=index.js.map