@authon/react 0.2.0 → 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,205 +79,2222 @@ 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
+ };
246
+ }
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
+ ] });
281
+ }
282
+
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;
91
366
  }
92
- return null;
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
+ ] });
93
410
  }
94
411
 
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" }) {
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);
445
+ }
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);
103
457
  }
104
- }, [client, mode]);
105
- if (mode === "embedded") {
106
- return /* @__PURE__ */ jsx3("div", { id: "authon-signup-container" });
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
+ ] });
109
752
  }
110
753
 
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);
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
+ ] });
1112
+ }
1113
+
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 });
1398
+ }
1399
+
1400
+ // src/SocialButton.tsx
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";
1404
+ var baseStyle = {
1405
+ display: "flex",
1406
+ alignItems: "center",
1407
+ justifyContent: "center",
1408
+ gap: 10,
1409
+ paddingLeft: 16,
1410
+ paddingRight: 16,
1411
+ border: "none",
1412
+ cursor: "pointer",
1413
+ fontFamily: "inherit",
1414
+ transition: "opacity 0.15s",
1415
+ width: "100%"
1416
+ };
1417
+ var compactBaseStyle = {
1418
+ display: "flex",
1419
+ alignItems: "center",
1420
+ justifyContent: "center",
1421
+ border: "none",
1422
+ cursor: "pointer",
1423
+ transition: "opacity 0.15s",
1424
+ padding: 0
1425
+ };
1426
+ function SocialButton({
1427
+ provider,
1428
+ onClick,
1429
+ loading = false,
1430
+ disabled = false,
1431
+ label,
1432
+ compact = false,
1433
+ className,
1434
+ style: userStyle,
1435
+ iconSize,
1436
+ borderRadius = 10,
1437
+ height = 48,
1438
+ size = 48
1439
+ }) {
1440
+ const colors = PROVIDER_COLORS3[provider] || { bg: "#333", text: "#fff" };
1441
+ const displayName = PROVIDER_DISPLAY_NAMES3[provider] || provider;
1442
+ const buttonLabel = label ?? `Continue with ${displayName}`;
1443
+ const needsBorder = colors.bg.toLowerCase() === "#ffffff";
1444
+ const resolvedIconSize = iconSize ?? (compact ? 24 : 20);
1445
+ const config = getProviderButtonConfig3(provider);
1446
+ const iconSvg = config.iconSvg.replace(/width="\d+"/, `width="${resolvedIconSize}"`).replace(/height="\d+"/, `height="${resolvedIconSize}"`);
1447
+ const borderProps = needsBorder ? { border: "1px solid #dadce0" } : {};
1448
+ if (compact) {
1449
+ return /* @__PURE__ */ jsx12(
1450
+ "button",
1451
+ {
1452
+ className,
1453
+ style: {
1454
+ ...compactBaseStyle,
1455
+ backgroundColor: colors.bg,
1456
+ borderRadius,
1457
+ width: size,
1458
+ height: size,
1459
+ ...borderProps,
1460
+ ...userStyle
1461
+ },
1462
+ onClick: () => onClick(provider),
1463
+ disabled: disabled || loading,
1464
+ "aria-label": `Sign in with ${displayName}`,
1465
+ children: loading ? /* @__PURE__ */ jsx12(
1466
+ "span",
1467
+ {
1468
+ style: {
1469
+ display: "inline-block",
1470
+ width: 16,
1471
+ height: 16,
1472
+ border: `2px solid ${colors.text}`,
1473
+ borderTopColor: "transparent",
1474
+ borderRadius: "50%",
1475
+ animation: "authon-spin 0.6s linear infinite"
1476
+ }
1477
+ }
1478
+ ) : /* @__PURE__ */ jsx12(
1479
+ "span",
1480
+ {
1481
+ style: { display: "flex", alignItems: "center", flexShrink: 0 },
1482
+ dangerouslySetInnerHTML: { __html: iconSvg }
1483
+ }
1484
+ )
1485
+ }
1486
+ );
1487
+ }
1488
+ return /* @__PURE__ */ jsx12(
1489
+ "button",
1490
+ {
1491
+ className,
1492
+ style: {
1493
+ ...baseStyle,
1494
+ backgroundColor: colors.bg,
1495
+ color: colors.text,
1496
+ borderRadius,
1497
+ height,
1498
+ ...borderProps,
1499
+ ...userStyle
1500
+ },
1501
+ onClick: () => onClick(provider),
1502
+ disabled: disabled || loading,
1503
+ "aria-label": `Sign in with ${displayName}`,
1504
+ children: loading ? /* @__PURE__ */ jsx12(
1505
+ "span",
1506
+ {
1507
+ style: {
1508
+ display: "inline-block",
1509
+ width: 16,
1510
+ height: 16,
1511
+ border: `2px solid ${colors.text}`,
1512
+ borderTopColor: "transparent",
1513
+ borderRadius: "50%",
1514
+ animation: "authon-spin 0.6s linear infinite"
1515
+ }
1516
+ }
1517
+ ) : /* @__PURE__ */ jsxs7(Fragment7, { children: [
1518
+ /* @__PURE__ */ jsx12(
1519
+ "span",
1520
+ {
1521
+ style: { display: "flex", alignItems: "center", flexShrink: 0 },
1522
+ dangerouslySetInnerHTML: { __html: iconSvg }
1523
+ }
1524
+ ),
1525
+ /* @__PURE__ */ jsx12("span", { style: { fontSize: 15, fontWeight: 600, whiteSpace: "nowrap" }, children: buttonLabel })
1526
+ ] })
1527
+ }
1528
+ );
1529
+ }
1530
+
1531
+ // src/SocialButtons.tsx
1532
+ import { useState as useState8, useEffect as useEffect4 } from "react";
1533
+ import { jsx as jsx13 } from "react/jsx-runtime";
1534
+ function SocialButtons({
1535
+ onSuccess,
1536
+ onError,
1537
+ className,
1538
+ style: userStyle,
1539
+ gap,
1540
+ compact = false,
1541
+ labels,
1542
+ buttonProps
1543
+ }) {
1544
+ const { client } = useAuthon();
1545
+ const [providers, setProviders] = useState8([]);
1546
+ const [loadingProvider, setLoadingProvider] = useState8(null);
1547
+ useEffect4(() => {
1548
+ if (!client) return;
1549
+ client.getProviders().then((p) => setProviders(p));
1550
+ }, [client]);
1551
+ if (providers.length === 0) return null;
1552
+ const resolvedGap = gap ?? (compact ? 12 : 10);
1553
+ const handleClick = async (provider) => {
1554
+ if (!client) return;
1555
+ setLoadingProvider(provider);
1556
+ try {
1557
+ await client.signInWithOAuth(provider);
1558
+ onSuccess?.();
1559
+ } catch (e) {
1560
+ const error = e instanceof Error ? e : new Error(String(e));
1561
+ onError?.(error);
1562
+ } finally {
1563
+ setLoadingProvider(null);
1564
+ }
1565
+ };
1566
+ const containerStyle = compact ? { display: "flex", flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: resolvedGap, ...userStyle } : { display: "flex", flexDirection: "column", gap: resolvedGap, ...userStyle };
1567
+ return /* @__PURE__ */ jsx13("div", { className, style: containerStyle, children: providers.map((provider) => /* @__PURE__ */ jsx13(
1568
+ SocialButton,
1569
+ {
1570
+ provider,
1571
+ onClick: handleClick,
1572
+ loading: loadingProvider === provider,
1573
+ disabled: !!loadingProvider,
1574
+ compact,
1575
+ label: labels?.[provider],
1576
+ ...buttonProps
1577
+ },
1578
+ provider
1579
+ )) });
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
+ );
267
2272
  }
268
2273
  export {
269
2274
  AuthonProvider,
2275
+ Button,
2276
+ Divider,
2277
+ Input,
270
2278
  Protect,
2279
+ ProviderIcon,
271
2280
  SignIn,
272
2281
  SignUp,
273
2282
  SignedIn,
274
2283
  SignedOut,
2284
+ SocialButton,
2285
+ SocialButtons,
2286
+ ThemeProvider,
275
2287
  UserButton,
2288
+ UserProfile,
276
2289
  useAuthon,
2290
+ useAuthonMfa,
2291
+ useAuthonPasskeys,
2292
+ useAuthonPasswordless,
2293
+ useAuthonSessions,
2294
+ useAuthonWeb3,
2295
+ useBranding,
2296
+ useOrganization,
2297
+ useOrganizationList,
277
2298
  useUser
278
2299
  };
279
2300
  //# sourceMappingURL=index.js.map