authfyio-react 0.3.9
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/README.md +83 -0
- package/dist/client.d.ts +128 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +227 -0
- package/dist/components.d.ts +79 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +111 -0
- package/dist/hooks-flow.d.ts +59 -0
- package/dist/hooks-flow.d.ts.map +1 -0
- package/dist/hooks-flow.js +133 -0
- package/dist/hooks.d.ts +113 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +212 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/jwt.d.ts +20 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +30 -0
- package/dist/provider.d.ts +50 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +91 -0
- package/dist/ui/CheckoutButton.d.ts +14 -0
- package/dist/ui/CheckoutButton.d.ts.map +1 -0
- package/dist/ui/CheckoutButton.js +28 -0
- package/dist/ui/Control.d.ts +17 -0
- package/dist/ui/Control.d.ts.map +1 -0
- package/dist/ui/Control.js +33 -0
- package/dist/ui/CreateOrganization.d.ts +19 -0
- package/dist/ui/CreateOrganization.d.ts.map +1 -0
- package/dist/ui/CreateOrganization.js +75 -0
- package/dist/ui/OrganizationList.d.ts +13 -0
- package/dist/ui/OrganizationList.d.ts.map +1 -0
- package/dist/ui/OrganizationList.js +47 -0
- package/dist/ui/OrganizationProfile.d.ts +12 -0
- package/dist/ui/OrganizationProfile.d.ts.map +1 -0
- package/dist/ui/OrganizationProfile.js +116 -0
- package/dist/ui/OrganizationSwitcher.d.ts +17 -0
- package/dist/ui/OrganizationSwitcher.d.ts.map +1 -0
- package/dist/ui/OrganizationSwitcher.js +59 -0
- package/dist/ui/PricingTable.d.ts +15 -0
- package/dist/ui/PricingTable.d.ts.map +1 -0
- package/dist/ui/PricingTable.js +74 -0
- package/dist/ui/SignIn.d.ts +23 -0
- package/dist/ui/SignIn.d.ts.map +1 -0
- package/dist/ui/SignIn.js +489 -0
- package/dist/ui/SignUp.d.ts +18 -0
- package/dist/ui/SignUp.d.ts.map +1 -0
- package/dist/ui/SignUp.js +153 -0
- package/dist/ui/UnstyledButtons.d.ts +24 -0
- package/dist/ui/UnstyledButtons.d.ts.map +1 -0
- package/dist/ui/UnstyledButtons.js +42 -0
- package/dist/ui/UserAvatar.d.ts +14 -0
- package/dist/ui/UserAvatar.d.ts.map +1 -0
- package/dist/ui/UserAvatar.js +52 -0
- package/dist/ui/UserButton.d.ts +15 -0
- package/dist/ui/UserButton.d.ts.map +1 -0
- package/dist/ui/UserButton.js +82 -0
- package/dist/ui/UserProfile.d.ts +19 -0
- package/dist/ui/UserProfile.d.ts.map +1 -0
- package/dist/ui/UserProfile.js +199 -0
- package/dist/ui/index.d.ts +14 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +13 -0
- package/dist/ui/styles.d.ts +10 -0
- package/dist/ui/styles.d.ts.map +1 -0
- package/dist/ui/styles.js +291 -0
- package/package.json +46 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useAuthfyio } from '../provider.js';
|
|
5
|
+
import { useCheckout } from '../hooks.js';
|
|
6
|
+
import { ensureStylesInjected } from './styles.js';
|
|
7
|
+
/**
|
|
8
|
+
* Renders every SubscriptionPlanEntity the instance exposes as a cards grid.
|
|
9
|
+
* Clicking a card triggers `useCheckout().startCheckout(planId, cycle)` which
|
|
10
|
+
* redirects the browser to Stripe Checkout.
|
|
11
|
+
*/
|
|
12
|
+
export function PricingTable({ includePlans, defaultBillingCycle = 'monthly', ctaLabel = 'Subscribe', }) {
|
|
13
|
+
useEffect(ensureStylesInjected, []);
|
|
14
|
+
const { client } = useAuthfyio();
|
|
15
|
+
const baseUrl = client.baseUrl;
|
|
16
|
+
const [plans, setPlans] = useState(null);
|
|
17
|
+
const [cycle, setCycle] = useState(defaultBillingCycle);
|
|
18
|
+
const { startCheckout, isLoading } = useCheckout();
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
fetch(`${baseUrl}/v1/billing/plans`, { credentials: 'include' })
|
|
22
|
+
.then((r) => (r.ok ? r.json() : null))
|
|
23
|
+
.then((body) => {
|
|
24
|
+
const rows = body?.plans ?? [];
|
|
25
|
+
if (includePlans?.length) {
|
|
26
|
+
const set = new Set(includePlans.map((p) => p.toLowerCase()));
|
|
27
|
+
setPlans(rows.filter((p) => set.has(p.name.toLowerCase())));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
setPlans(rows);
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
.catch(() => setPlans([]));
|
|
34
|
+
}, [baseUrl, includePlans?.join(',')]);
|
|
35
|
+
async function select(planId) {
|
|
36
|
+
setError(null);
|
|
37
|
+
try {
|
|
38
|
+
await startCheckout(planId, cycle);
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
setError(err?.message ?? 'Could not start checkout.');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (plans === null) {
|
|
45
|
+
return (_jsx("div", { className: "af-card", style: { maxWidth: 920 }, children: _jsx("div", { className: "af-subtitle", children: "Loading plans\u2026" }) }));
|
|
46
|
+
}
|
|
47
|
+
if (plans.length === 0) {
|
|
48
|
+
return (_jsx("div", { className: "af-card", style: { maxWidth: 920 }, children: _jsx("div", { className: "af-subtitle", children: "No plans configured for this instance." }) }));
|
|
49
|
+
}
|
|
50
|
+
return (_jsxs("div", { style: { fontFamily: 'var(--af-font, -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif)', maxWidth: 920, margin: '0 auto' }, children: [_jsx("div", { style: { display: 'inline-flex', padding: 3, background: 'rgba(17,24,39,0.04)', borderRadius: 999, marginBottom: 20 }, children: ['monthly', 'annual'].map((c) => (_jsx("button", { type: "button", onClick: () => setCycle(c), style: {
|
|
51
|
+
padding: '6px 14px',
|
|
52
|
+
fontSize: 12.5,
|
|
53
|
+
fontWeight: 500,
|
|
54
|
+
border: 0,
|
|
55
|
+
borderRadius: 999,
|
|
56
|
+
background: cycle === c ? '#fff' : 'transparent',
|
|
57
|
+
color: cycle === c ? '#111827' : '#6b7280',
|
|
58
|
+
cursor: 'pointer',
|
|
59
|
+
boxShadow: cycle === c ? '0 1px 2px rgba(17,24,39,0.06)' : 'none',
|
|
60
|
+
}, children: c === 'monthly' ? 'Monthly' : 'Annual (save 20%)' }, c))) }), _jsx("div", { style: { display: 'grid', gap: 16, gridTemplateColumns: `repeat(auto-fit, minmax(240px, 1fr))` }, children: plans.map((plan) => {
|
|
61
|
+
const cents = cycle === 'annual' && plan.annualPriceCents ? plan.annualPriceCents : plan.monthlyPriceCents;
|
|
62
|
+
const priceLabel = cents === 0 ? 'Free' : `$${(cents / 100).toFixed(cents % 100 === 0 ? 0 : 2)}`;
|
|
63
|
+
const perLabel = cents === 0 ? '' : cycle === 'annual' ? '/year' : '/month';
|
|
64
|
+
return (_jsxs("div", { style: {
|
|
65
|
+
padding: 22,
|
|
66
|
+
background: '#fff',
|
|
67
|
+
border: '1px solid rgba(17,24,39,0.08)',
|
|
68
|
+
borderRadius: 16,
|
|
69
|
+
boxShadow: '0 1px 3px rgba(17,24,39,0.04)',
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flexDirection: 'column',
|
|
72
|
+
}, children: [_jsx("div", { style: { fontSize: 14.5, fontWeight: 600, color: '#111827' }, children: plan.name }), plan.description && (_jsx("p", { style: { margin: '4px 0 16px', fontSize: 12.5, color: '#6b7280', minHeight: 32 }, children: plan.description })), _jsxs("div", { style: { fontSize: 28, fontWeight: 600, letterSpacing: '-0.02em', color: '#111827' }, children: [priceLabel, perLabel && _jsxs("span", { style: { fontSize: 13, color: '#6b7280', fontWeight: 400 }, children: [" ", perLabel] })] }), _jsx("button", { type: "button", className: "af-btn af-btn-primary", style: { marginTop: 20 }, disabled: isLoading, onClick: () => select(plan.id), children: isLoading ? 'Opening checkout…' : ctaLabel })] }, plan.id));
|
|
73
|
+
}) }), error && _jsx("div", { className: "af-error", style: { marginTop: 16 }, children: error })] }));
|
|
74
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
type Method = 'emailPassword' | 'usernamePassword' | 'magicLink' | 'emailCode' | 'phoneOtp' | 'passkey';
|
|
2
|
+
type SocialProvider = 'google' | 'github' | 'facebook' | 'apple' | 'microsoft' | 'linkedin' | 'discord' | 'slack' | 'twitter';
|
|
3
|
+
export type SignInProps = {
|
|
4
|
+
redirectUrl?: string;
|
|
5
|
+
signUpUrl?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
showBranding?: boolean;
|
|
9
|
+
brandText?: string;
|
|
10
|
+
/** Override social providers shown. Default: whatever the dashboard has enabled. */
|
|
11
|
+
socialProviders?: SocialProvider[];
|
|
12
|
+
/** Override methods shown. Default: whatever the dashboard has enabled. */
|
|
13
|
+
methods?: Method[];
|
|
14
|
+
/** Default selected tab. Default = first available method. */
|
|
15
|
+
defaultMethod?: Method;
|
|
16
|
+
onSignIn?: (result: {
|
|
17
|
+
userId: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
}) => void;
|
|
20
|
+
};
|
|
21
|
+
export declare function SignIn({ redirectUrl, signUpUrl, title, subtitle, showBranding, brandText, socialProviders: socialOverride, methods: methodsOverride, defaultMethod, onSignIn, }: SignInProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=SignIn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignIn.d.ts","sourceRoot":"","sources":["../../src/ui/SignIn.tsx"],"names":[],"mappings":"AAQA,KAAK,MAAM,GACP,eAAe,GACf,kBAAkB,GAClB,WAAW,GACX,WAAW,GACX,UAAU,GACV,SAAS,CAAC;AAEd,KAAK,cAAc,GACf,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,WAAW,GACX,UAAU,GACV,SAAS,GACT,OAAO,GACP,SAAS,CAAC;AA+Bd,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,8DAA8D;IAC9D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,wBAAgB,MAAM,CAAC,EACrB,WAAiB,EACjB,SAAsB,EACtB,KAAiC,EACjC,QAA2D,EAC3D,YAAmB,EACnB,SAAsB,EACtB,eAAe,EAAE,cAAc,EAC/B,OAAO,EAAE,eAAe,EACxB,aAAa,EACb,QAAQ,GACT,EAAE,WAAW,2CA0Mb"}
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useAuthfyio } from '../provider.js';
|
|
5
|
+
import { ensureStylesInjected } from './styles.js';
|
|
6
|
+
const METHOD_LABEL = {
|
|
7
|
+
emailPassword: 'Email',
|
|
8
|
+
usernamePassword: 'Username',
|
|
9
|
+
magicLink: 'Magic link',
|
|
10
|
+
emailCode: 'Email code',
|
|
11
|
+
phoneOtp: 'SMS code',
|
|
12
|
+
passkey: 'Passkey',
|
|
13
|
+
};
|
|
14
|
+
const SOCIAL_LABELS = {
|
|
15
|
+
google: 'Google',
|
|
16
|
+
github: 'GitHub',
|
|
17
|
+
facebook: 'Facebook',
|
|
18
|
+
apple: 'Apple',
|
|
19
|
+
microsoft: 'Microsoft',
|
|
20
|
+
linkedin: 'LinkedIn',
|
|
21
|
+
discord: 'Discord',
|
|
22
|
+
slack: 'Slack',
|
|
23
|
+
twitter: 'X / Twitter',
|
|
24
|
+
};
|
|
25
|
+
const WALLET_LABELS = {
|
|
26
|
+
metamask: 'MetaMask',
|
|
27
|
+
coinbase_wallet: 'Coinbase Wallet',
|
|
28
|
+
okx_wallet: 'OKX Wallet',
|
|
29
|
+
solana: 'Solana',
|
|
30
|
+
base: 'Base',
|
|
31
|
+
};
|
|
32
|
+
export function SignIn({ redirectUrl = '/', signUpUrl = '/sign-up', title = 'Sign in to your account', subtitle = 'Welcome back — choose how you want to sign in.', showBranding = true, brandText = 'Authfyio', socialProviders: socialOverride, methods: methodsOverride, defaultMethod, onSignIn, }) {
|
|
33
|
+
useEffect(ensureStylesInjected, []);
|
|
34
|
+
const { client } = useAuthfyio();
|
|
35
|
+
const baseUrl = client.baseUrl;
|
|
36
|
+
const [config, setConfig] = useState(null);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
let cancelled = false;
|
|
39
|
+
client.fetchAuthConfig().then((c) => {
|
|
40
|
+
if (cancelled)
|
|
41
|
+
return;
|
|
42
|
+
setConfig(c);
|
|
43
|
+
});
|
|
44
|
+
return () => {
|
|
45
|
+
cancelled = true;
|
|
46
|
+
};
|
|
47
|
+
}, [client]);
|
|
48
|
+
const enabledMethods = (() => {
|
|
49
|
+
if (methodsOverride)
|
|
50
|
+
return methodsOverride;
|
|
51
|
+
if (!config)
|
|
52
|
+
return [];
|
|
53
|
+
const m = [];
|
|
54
|
+
if (config.signIn.emailPassword)
|
|
55
|
+
m.push('emailPassword');
|
|
56
|
+
if (config.signIn.usernamePassword)
|
|
57
|
+
m.push('usernamePassword');
|
|
58
|
+
if (config.signIn.magicLink)
|
|
59
|
+
m.push('magicLink');
|
|
60
|
+
if (config.signIn.emailCode)
|
|
61
|
+
m.push('emailCode');
|
|
62
|
+
if (config.signIn.phoneOtp)
|
|
63
|
+
m.push('phoneOtp');
|
|
64
|
+
if (config.signIn.passkey)
|
|
65
|
+
m.push('passkey');
|
|
66
|
+
return m;
|
|
67
|
+
})();
|
|
68
|
+
const enabledSocial = (socialOverride ?? (config?.socialProviders ?? [])).filter((p) => SOCIAL_LABELS[p]);
|
|
69
|
+
const enabledWallets = (config?.web3Wallets ?? []).filter((w) => WALLET_LABELS[w]);
|
|
70
|
+
// OAuth /authorize URLs need three things attached to the anchor
|
|
71
|
+
// (none of which a custom header can carry through a top-level
|
|
72
|
+
// navigation):
|
|
73
|
+
// - publishable_key → tenant routing
|
|
74
|
+
// - return_url → customer-side bridge that lands cookies on
|
|
75
|
+
// the right origin and finishes the redirect
|
|
76
|
+
// After Google calls back, the API mints a one-time token and 302s
|
|
77
|
+
// the browser to ${return_url}?ott=…; the authfyio-nextjs/proxy
|
|
78
|
+
// 0.2.2+ catches `/api/af/oauth-finish` and exchanges the OTT.
|
|
79
|
+
const oauthQuery = (() => {
|
|
80
|
+
if (typeof window === 'undefined')
|
|
81
|
+
return '';
|
|
82
|
+
const params = new URLSearchParams();
|
|
83
|
+
if (client.publishableKey)
|
|
84
|
+
params.set('publishable_key', client.publishableKey);
|
|
85
|
+
const finish = new URL('/api/af/oauth-finish', window.location.origin);
|
|
86
|
+
finish.searchParams.set('to', redirectUrl);
|
|
87
|
+
params.set('return_url', finish.toString());
|
|
88
|
+
return '?' + params.toString();
|
|
89
|
+
})();
|
|
90
|
+
const showSignUpLink = methodsOverride ? true : (config?.signUp.enabled ?? true);
|
|
91
|
+
const legal = config?.legal;
|
|
92
|
+
const brand = config?.branding;
|
|
93
|
+
const cardStyle = brand?.primaryColor
|
|
94
|
+
? { ['--af-primary']: brand.primaryColor }
|
|
95
|
+
: {};
|
|
96
|
+
const effectiveBrandText = brand?.appName ?? brandText;
|
|
97
|
+
const showPoweredBy = brand && !brand.removeBranding;
|
|
98
|
+
const [method, setMethod] = useState(defaultMethod ?? null);
|
|
99
|
+
const [error, setError] = useState(null);
|
|
100
|
+
const [notice, setNotice] = useState(null);
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (enabledMethods.length === 0)
|
|
103
|
+
return;
|
|
104
|
+
if (!method || !enabledMethods.includes(method))
|
|
105
|
+
setMethod(enabledMethods[0]);
|
|
106
|
+
}, [enabledMethods.join(','), method]);
|
|
107
|
+
const activeMethod = method && enabledMethods.includes(method) ? method : enabledMethods[0] ?? null;
|
|
108
|
+
return (_jsxs("div", { className: "af-card", role: "form", "aria-label": "Sign in", style: cardStyle, children: [showBranding && (_jsxs("div", { className: "af-brand", children: [brand?.logoUrl ? (_jsx("img", { src: brand.logoUrl, alt: "", style: { width: 26, height: 26, borderRadius: 8, objectFit: 'cover' } })) : (_jsx("span", { className: "af-brand-mark", "aria-hidden": true })), _jsx("span", { className: "af-brand-text", children: effectiveBrandText })] })), _jsx("h1", { className: "af-title", children: title }), _jsx("p", { className: "af-subtitle", children: subtitle }), (enabledSocial.length > 0 || enabledWallets.length > 0) && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "af-social-grid", children: [enabledSocial.map((p) => (_jsxs("a", { className: "af-social-btn", href: `${baseUrl}/v1/auth/oauth/${p}/authorize${oauthQuery}`, "aria-label": `Continue with ${SOCIAL_LABELS[p]}`, children: [_jsx(SocialIcon, { provider: p }), "Continue with ", SOCIAL_LABELS[p]] }, p))), enabledWallets.map((w) => (_jsxs("a", { className: "af-social-btn", href: `${baseUrl}/v1/auth/web3/${w}/authorize${oauthQuery}`, "aria-label": `Continue with ${WALLET_LABELS[w]}`, children: [_jsx(WalletIcon, { wallet: w }), "Continue with ", WALLET_LABELS[w]] }, w)))] }), enabledMethods.length > 0 && _jsx("div", { className: "af-divider", children: "or" })] })), enabledMethods.length > 1 && (_jsx("div", { className: "af-tabs", role: "tablist", children: enabledMethods.map((m) => (_jsx("button", { type: "button", role: "tab", "aria-selected": activeMethod === m, className: `af-tab${activeMethod === m ? ' is-active' : ''}`, onClick: () => {
|
|
109
|
+
setError(null);
|
|
110
|
+
setNotice(null);
|
|
111
|
+
setMethod(m);
|
|
112
|
+
}, children: METHOD_LABEL[m] }, m))) })), activeMethod === 'emailPassword' && (_jsx(PasswordForm, { baseUrl: baseUrl, identifier: "email", redirectUrl: redirectUrl, onSignIn: onSignIn, onError: setError })), activeMethod === 'usernamePassword' && (_jsx(PasswordForm, { baseUrl: baseUrl, identifier: "username", redirectUrl: redirectUrl, onSignIn: onSignIn, onError: setError })), activeMethod === 'magicLink' && (_jsx(MagicLinkForm, { baseUrl: baseUrl, onError: setError, onNotice: setNotice })), activeMethod === 'emailCode' && (_jsx(EmailCodeForm, { baseUrl: baseUrl, redirectUrl: redirectUrl, onError: setError, onNotice: setNotice, onSignIn: onSignIn })), activeMethod === 'phoneOtp' && (_jsx(SmsOtpForm, { baseUrl: baseUrl, redirectUrl: redirectUrl, onError: setError, onNotice: setNotice, onSignIn: onSignIn })), activeMethod === 'passkey' && (_jsx(PasskeyForm, { baseUrl: baseUrl, redirectUrl: redirectUrl, onError: setError, onSignIn: onSignIn })), config && enabledMethods.length === 0 && enabledSocial.length === 0 && (_jsx("div", { className: "af-error", children: "No sign-in methods are enabled for this app. Enable at least one method (email/password, username/password, magic link, email code, SMS, passkey, or a social provider) in the Authfyio dashboard." })), error && _jsx("div", { className: "af-error", children: error }), notice && !error && _jsx("div", { className: "af-notice", children: notice }), (legal?.termsOfServiceUrl || legal?.privacyPolicyUrl) && (_jsxs("div", { className: "af-legal", children: ["By continuing you agree to our", ' ', legal?.termsOfServiceUrl && (_jsxs(_Fragment, { children: [_jsx("a", { href: legal.termsOfServiceUrl, target: "_blank", rel: "noreferrer", children: "Terms" }), legal?.privacyPolicyUrl ? ' and ' : '.'] })), legal?.privacyPolicyUrl && (_jsxs(_Fragment, { children: [_jsx("a", { href: legal.privacyPolicyUrl, target: "_blank", rel: "noreferrer", children: "Privacy Policy" }), "."] }))] })), showSignUpLink && (_jsxs("div", { className: "af-footer", children: ["Don't have an account? ", _jsx("a", { href: signUpUrl, children: "Sign up" })] })), showPoweredBy && (_jsxs("div", { className: "af-powered", children: ["Powered by ", _jsx("a", { href: "https://authfyio.com", target: "_blank", rel: "noreferrer", children: "Authfyio" })] }))] }));
|
|
113
|
+
}
|
|
114
|
+
/* ─────────────────── password (email or username) ─────────────────── */
|
|
115
|
+
function PasswordForm({ baseUrl, identifier, redirectUrl, onSignIn, onError, }) {
|
|
116
|
+
const [id, setId] = useState('');
|
|
117
|
+
const [password, setPassword] = useState('');
|
|
118
|
+
const [loading, setLoading] = useState(false);
|
|
119
|
+
// When the account has a verified second factor, the password step returns
|
|
120
|
+
// `needs_mfa` + a short-lived token instead of a session; we then show the
|
|
121
|
+
// code screen below.
|
|
122
|
+
const [mfaToken, setMfaToken] = useState(null);
|
|
123
|
+
const [mfaCode, setMfaCode] = useState('');
|
|
124
|
+
const [forgot, setForgot] = useState(false);
|
|
125
|
+
function complete(body) {
|
|
126
|
+
if (onSignIn)
|
|
127
|
+
onSignIn(body);
|
|
128
|
+
if (typeof window !== 'undefined')
|
|
129
|
+
window.location.href = redirectUrl;
|
|
130
|
+
}
|
|
131
|
+
async function submit(e) {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
setLoading(true);
|
|
134
|
+
onError(null);
|
|
135
|
+
try {
|
|
136
|
+
const path = identifier === 'username'
|
|
137
|
+
? '/v1/auth/sign-in/username-password'
|
|
138
|
+
: '/v1/auth/sign-in/email-password';
|
|
139
|
+
const body = identifier === 'username' ? { username: id, password } : { email: id, password };
|
|
140
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
credentials: 'include',
|
|
143
|
+
headers: { 'content-type': 'application/json' },
|
|
144
|
+
body: JSON.stringify(body),
|
|
145
|
+
});
|
|
146
|
+
if (!res.ok) {
|
|
147
|
+
const b = await res.json().catch(() => ({}));
|
|
148
|
+
throw new Error(b?.message ?? 'Invalid credentials.');
|
|
149
|
+
}
|
|
150
|
+
const data = (await res.json());
|
|
151
|
+
if (data?.status === 'needs_mfa') {
|
|
152
|
+
setMfaToken(data.mfaToken);
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
complete(data);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
onError(err?.message ?? 'Sign-in failed.');
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
setLoading(false);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function submitMfa(e) {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
setLoading(true);
|
|
167
|
+
onError(null);
|
|
168
|
+
try {
|
|
169
|
+
const res = await fetch(`${baseUrl}/v1/auth/sign-in/mfa/verify`, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
credentials: 'include',
|
|
172
|
+
headers: { 'content-type': 'application/json' },
|
|
173
|
+
body: JSON.stringify({ mfaToken, code: mfaCode.trim() }),
|
|
174
|
+
});
|
|
175
|
+
if (!res.ok) {
|
|
176
|
+
const b = await res.json().catch(() => ({}));
|
|
177
|
+
throw new Error(b?.message ?? 'Invalid code.');
|
|
178
|
+
}
|
|
179
|
+
complete((await res.json()));
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
onError(err?.message ?? 'Verification failed.');
|
|
183
|
+
}
|
|
184
|
+
finally {
|
|
185
|
+
setLoading(false);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (forgot && identifier === 'email') {
|
|
189
|
+
return (_jsx(ForgotPasswordForm, { baseUrl: baseUrl, redirectUrl: redirectUrl, initialEmail: id, onError: onError, onBack: () => {
|
|
190
|
+
onError(null);
|
|
191
|
+
setForgot(false);
|
|
192
|
+
} }));
|
|
193
|
+
}
|
|
194
|
+
if (mfaToken) {
|
|
195
|
+
return (_jsxs("form", { onSubmit: submitMfa, children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Authentication code" }), _jsx("input", { className: "af-input", inputMode: "numeric", autoComplete: "one-time-code", required: true, value: mfaCode, onChange: (e) => setMfaCode(e.target.value), placeholder: "123456" }), _jsx("p", { className: "af-sub", children: "Enter the code from your authenticator app, or a backup code." })] }), _jsx("div", { className: "af-primary-row", children: _jsx("button", { type: "submit", className: "af-btn af-btn-primary", disabled: loading, children: loading ? 'Verifying…' : 'Verify' }) })] }));
|
|
196
|
+
}
|
|
197
|
+
return (_jsxs("form", { onSubmit: submit, children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: identifier === 'username' ? 'Username' : 'Email' }), _jsx("input", { className: "af-input", type: identifier === 'username' ? 'text' : 'email', autoComplete: identifier === 'username' ? 'username' : 'email', required: true, value: id, onChange: (e) => setId(e.target.value), placeholder: identifier === 'username' ? 'yourname' : 'you@company.com' })] }), _jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Password" }), _jsx("input", { className: "af-input", type: "password", autoComplete: "current-password", required: true, value: password, onChange: (e) => setPassword(e.target.value), placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), identifier === 'email' && (_jsx("div", { className: "af-row", children: _jsx("button", { type: "button", className: "af-link", onClick: () => {
|
|
198
|
+
onError(null);
|
|
199
|
+
setForgot(true);
|
|
200
|
+
}, children: "Forgot password?" }) })), _jsx("div", { className: "af-primary-row", children: _jsx("button", { type: "submit", className: "af-btn af-btn-primary", disabled: loading, children: loading ? 'Signing in…' : 'Sign in' }) })] }));
|
|
201
|
+
}
|
|
202
|
+
/* ─────────────────── forgot password (code-based reset) ─────────────────── */
|
|
203
|
+
function ForgotPasswordForm({ baseUrl, redirectUrl, initialEmail, onError, onBack, }) {
|
|
204
|
+
const [email, setEmail] = useState(initialEmail);
|
|
205
|
+
const [code, setCode] = useState('');
|
|
206
|
+
const [newPassword, setNewPassword] = useState('');
|
|
207
|
+
const [step, setStep] = useState('request');
|
|
208
|
+
const [busy, setBusy] = useState(false);
|
|
209
|
+
const [notice, setNotice] = useState(null);
|
|
210
|
+
async function request(e) {
|
|
211
|
+
e.preventDefault();
|
|
212
|
+
setBusy(true);
|
|
213
|
+
onError(null);
|
|
214
|
+
try {
|
|
215
|
+
// Always succeeds server-side (no account enumeration); advance to the
|
|
216
|
+
// code step regardless so we don't leak whether the email is registered.
|
|
217
|
+
await fetch(`${baseUrl}/v1/auth/password/forgot`, {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
credentials: 'include',
|
|
220
|
+
headers: { 'content-type': 'application/json' },
|
|
221
|
+
body: JSON.stringify({ email }),
|
|
222
|
+
});
|
|
223
|
+
setStep('reset');
|
|
224
|
+
setNotice(`If an account exists for ${email}, we sent a 6-digit reset code.`);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
onError('Could not start password reset. Try again.');
|
|
228
|
+
}
|
|
229
|
+
finally {
|
|
230
|
+
setBusy(false);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function reset(e) {
|
|
234
|
+
e.preventDefault();
|
|
235
|
+
setBusy(true);
|
|
236
|
+
onError(null);
|
|
237
|
+
try {
|
|
238
|
+
const res = await fetch(`${baseUrl}/v1/auth/password/reset`, {
|
|
239
|
+
method: 'POST',
|
|
240
|
+
credentials: 'include',
|
|
241
|
+
headers: { 'content-type': 'application/json' },
|
|
242
|
+
body: JSON.stringify({ email, code: code.trim(), newPassword }),
|
|
243
|
+
});
|
|
244
|
+
if (!res.ok) {
|
|
245
|
+
const b = await res.json().catch(() => ({}));
|
|
246
|
+
throw new Error(b?.message ?? 'Invalid or expired code.');
|
|
247
|
+
}
|
|
248
|
+
// Reset signs the user in — go to the post-sign-in destination.
|
|
249
|
+
if (typeof window !== 'undefined')
|
|
250
|
+
window.location.href = redirectUrl;
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
onError(err?.message ?? 'Could not reset password.');
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
setBusy(false);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return (_jsxs("form", { onSubmit: step === 'request' ? request : reset, children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Email" }), _jsx("input", { className: "af-input", type: "email", autoComplete: "email", required: true, disabled: step === 'reset', value: email, onChange: (e) => setEmail(e.target.value), placeholder: "you@company.com" })] }), step === 'reset' && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Reset code" }), _jsx("input", { className: "af-input", inputMode: "numeric", autoComplete: "one-time-code", required: true, value: code, onChange: (e) => setCode(e.target.value), placeholder: "123456" })] }), _jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "New password" }), _jsx("input", { className: "af-input", type: "password", autoComplete: "new-password", required: true, minLength: 8, value: newPassword, onChange: (e) => setNewPassword(e.target.value), placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] })), notice && _jsx("p", { className: "af-sub", children: notice }), _jsx("div", { className: "af-primary-row", children: _jsx("button", { type: "submit", className: "af-btn af-btn-primary", disabled: busy, children: busy ? 'Please wait…' : step === 'request' ? 'Send reset code' : 'Reset password' }) }), _jsx("div", { className: "af-row", children: _jsx("button", { type: "button", className: "af-link", onClick: onBack, children: "Back to sign in" }) })] }));
|
|
260
|
+
}
|
|
261
|
+
/* ─────────────────── magic link ─────────────────── */
|
|
262
|
+
function MagicLinkForm({ baseUrl, onError, onNotice, }) {
|
|
263
|
+
const [email, setEmail] = useState('');
|
|
264
|
+
const [sending, setSending] = useState(false);
|
|
265
|
+
async function submit(e) {
|
|
266
|
+
e.preventDefault();
|
|
267
|
+
setSending(true);
|
|
268
|
+
onError(null);
|
|
269
|
+
onNotice(null);
|
|
270
|
+
try {
|
|
271
|
+
const res = await fetch(`${baseUrl}/v1/auth/sign-in/magic-link`, {
|
|
272
|
+
method: 'POST',
|
|
273
|
+
credentials: 'include',
|
|
274
|
+
headers: { 'content-type': 'application/json' },
|
|
275
|
+
body: JSON.stringify({ email }),
|
|
276
|
+
});
|
|
277
|
+
if (!res.ok) {
|
|
278
|
+
const body = await res.json().catch(() => ({}));
|
|
279
|
+
throw new Error(body?.message ?? 'Could not send link.');
|
|
280
|
+
}
|
|
281
|
+
onNotice(`We sent a sign-in link to ${email}. It expires in 15 minutes.`);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
onError(err?.message ?? 'Could not send link.');
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
setSending(false);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return (_jsxs("form", { onSubmit: submit, children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Email" }), _jsx("input", { className: "af-input", type: "email", autoComplete: "email", required: true, value: email, onChange: (e) => setEmail(e.target.value), placeholder: "you@company.com" })] }), _jsx("div", { className: "af-primary-row", children: _jsx("button", { type: "submit", className: "af-btn af-btn-primary", disabled: sending, children: sending ? 'Sending link…' : 'Email me a sign-in link' }) })] }));
|
|
291
|
+
}
|
|
292
|
+
/* ─────────────────── email code ─────────────────── */
|
|
293
|
+
function EmailCodeForm({ baseUrl, redirectUrl, onError, onNotice, onSignIn, }) {
|
|
294
|
+
const [email, setEmail] = useState('');
|
|
295
|
+
const [code, setCode] = useState('');
|
|
296
|
+
const [step, setStep] = useState('send');
|
|
297
|
+
const [busy, setBusy] = useState(false);
|
|
298
|
+
async function send(e) {
|
|
299
|
+
e.preventDefault();
|
|
300
|
+
onError(null);
|
|
301
|
+
onNotice(null);
|
|
302
|
+
setBusy(true);
|
|
303
|
+
try {
|
|
304
|
+
const res = await fetch(`${baseUrl}/v1/auth/sign-in/email-code`, {
|
|
305
|
+
method: 'POST',
|
|
306
|
+
credentials: 'include',
|
|
307
|
+
headers: { 'content-type': 'application/json' },
|
|
308
|
+
body: JSON.stringify({ email }),
|
|
309
|
+
});
|
|
310
|
+
if (!res.ok)
|
|
311
|
+
throw new Error('Could not send code.');
|
|
312
|
+
setStep('verify');
|
|
313
|
+
onNotice(`We sent a 6-digit code to ${email}.`);
|
|
314
|
+
}
|
|
315
|
+
catch (err) {
|
|
316
|
+
onError(err?.message ?? 'Could not send code.');
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
setBusy(false);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
async function verify(e) {
|
|
323
|
+
e.preventDefault();
|
|
324
|
+
onError(null);
|
|
325
|
+
onNotice(null);
|
|
326
|
+
setBusy(true);
|
|
327
|
+
try {
|
|
328
|
+
const res = await fetch(`${baseUrl}/v1/auth/email-code/verify`, {
|
|
329
|
+
method: 'POST',
|
|
330
|
+
credentials: 'include',
|
|
331
|
+
headers: { 'content-type': 'application/json' },
|
|
332
|
+
body: JSON.stringify({ email, code }),
|
|
333
|
+
});
|
|
334
|
+
if (!res.ok)
|
|
335
|
+
throw new Error('Invalid code.');
|
|
336
|
+
const body = (await res.json());
|
|
337
|
+
if (onSignIn)
|
|
338
|
+
onSignIn(body);
|
|
339
|
+
if (typeof window !== 'undefined')
|
|
340
|
+
window.location.href = redirectUrl;
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
onError(err?.message ?? 'Verification failed.');
|
|
344
|
+
}
|
|
345
|
+
finally {
|
|
346
|
+
setBusy(false);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return (_jsxs("form", { onSubmit: step === 'send' ? send : verify, children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Email" }), _jsx("input", { className: "af-input", type: "email", autoComplete: "email", required: true, disabled: step === 'verify', value: email, onChange: (e) => setEmail(e.target.value), placeholder: "you@company.com" })] }), step === 'verify' && (_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "6-digit code" }), _jsx("input", { className: "af-input", inputMode: "numeric", pattern: "[0-9]*", maxLength: 6, required: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\D/g, '')), placeholder: "000000" })] })), _jsx("div", { className: "af-primary-row", children: _jsx("button", { type: "submit", className: "af-btn af-btn-primary", disabled: busy, children: busy ? 'Working…' : step === 'send' ? 'Email me a code' : 'Verify & sign in' }) })] }));
|
|
350
|
+
}
|
|
351
|
+
/* ─────────────────── SMS OTP ─────────────────── */
|
|
352
|
+
function SmsOtpForm({ baseUrl, redirectUrl, onError, onNotice, onSignIn, }) {
|
|
353
|
+
const [phone, setPhone] = useState('');
|
|
354
|
+
const [code, setCode] = useState('');
|
|
355
|
+
const [step, setStep] = useState('send');
|
|
356
|
+
const [busy, setBusy] = useState(false);
|
|
357
|
+
async function send(e) {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
onError(null);
|
|
360
|
+
onNotice(null);
|
|
361
|
+
setBusy(true);
|
|
362
|
+
try {
|
|
363
|
+
const res = await fetch(`${baseUrl}/v1/auth/sign-in/sms-otp`, {
|
|
364
|
+
method: 'POST',
|
|
365
|
+
credentials: 'include',
|
|
366
|
+
headers: { 'content-type': 'application/json' },
|
|
367
|
+
body: JSON.stringify({ phone }),
|
|
368
|
+
});
|
|
369
|
+
if (!res.ok)
|
|
370
|
+
throw new Error('Could not send code.');
|
|
371
|
+
setStep('verify');
|
|
372
|
+
onNotice(`We sent a 6-digit code to ${phone}. It expires in 5 minutes.`);
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
onError(err?.message ?? 'Could not send code.');
|
|
376
|
+
}
|
|
377
|
+
finally {
|
|
378
|
+
setBusy(false);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async function verify(e) {
|
|
382
|
+
e.preventDefault();
|
|
383
|
+
onError(null);
|
|
384
|
+
onNotice(null);
|
|
385
|
+
setBusy(true);
|
|
386
|
+
try {
|
|
387
|
+
const res = await fetch(`${baseUrl}/v1/auth/sms/verify`, {
|
|
388
|
+
method: 'POST',
|
|
389
|
+
credentials: 'include',
|
|
390
|
+
headers: { 'content-type': 'application/json' },
|
|
391
|
+
body: JSON.stringify({ phone, code }),
|
|
392
|
+
});
|
|
393
|
+
if (!res.ok) {
|
|
394
|
+
const body = await res.json().catch(() => ({}));
|
|
395
|
+
throw new Error(body?.message ?? 'Invalid code.');
|
|
396
|
+
}
|
|
397
|
+
const body = (await res.json());
|
|
398
|
+
if (onSignIn)
|
|
399
|
+
onSignIn(body);
|
|
400
|
+
if (typeof window !== 'undefined')
|
|
401
|
+
window.location.href = redirectUrl;
|
|
402
|
+
}
|
|
403
|
+
catch (err) {
|
|
404
|
+
onError(err?.message ?? 'Verification failed.');
|
|
405
|
+
}
|
|
406
|
+
finally {
|
|
407
|
+
setBusy(false);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return (_jsxs("form", { onSubmit: step === 'send' ? send : verify, children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Phone number" }), _jsx("input", { className: "af-input", type: "tel", autoComplete: "tel", required: true, disabled: step === 'verify', value: phone, onChange: (e) => setPhone(e.target.value), placeholder: "+14155551234" })] }), step === 'verify' && (_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "6-digit code" }), _jsx("input", { className: "af-input", inputMode: "numeric", pattern: "[0-9]*", maxLength: 6, required: true, value: code, onChange: (e) => setCode(e.target.value.replace(/\D/g, '')), placeholder: "000000" })] })), _jsx("div", { className: "af-primary-row", children: _jsx("button", { type: "submit", className: "af-btn af-btn-primary", disabled: busy, children: busy ? 'Working…' : step === 'send' ? 'Send code' : 'Verify & sign in' }) })] }));
|
|
411
|
+
}
|
|
412
|
+
/* ─────────────────── Passkey ─────────────────── */
|
|
413
|
+
function PasskeyForm({ baseUrl, redirectUrl, onError, onSignIn, }) {
|
|
414
|
+
const [email, setEmail] = useState('');
|
|
415
|
+
const [busy, setBusy] = useState(false);
|
|
416
|
+
async function submit(e) {
|
|
417
|
+
e.preventDefault();
|
|
418
|
+
onError(null);
|
|
419
|
+
setBusy(true);
|
|
420
|
+
try {
|
|
421
|
+
const begin = await fetch(`${baseUrl}/v1/auth/passkeys/authenticate/begin`, {
|
|
422
|
+
method: 'POST',
|
|
423
|
+
credentials: 'include',
|
|
424
|
+
headers: { 'content-type': 'application/json' },
|
|
425
|
+
body: JSON.stringify({ email }),
|
|
426
|
+
});
|
|
427
|
+
if (!begin.ok)
|
|
428
|
+
throw new Error('Could not start passkey sign-in.');
|
|
429
|
+
const options = await begin.json();
|
|
430
|
+
const mod = await (new Function('return import("@simplewebauthn/browser")')()).catch(() => null);
|
|
431
|
+
const startAuthentication = mod?.startAuthentication;
|
|
432
|
+
if (!startAuthentication)
|
|
433
|
+
throw new Error('Install @simplewebauthn/browser to use passkey sign-in.');
|
|
434
|
+
const asserted = await startAuthentication(options);
|
|
435
|
+
const complete = await fetch(`${baseUrl}/v1/auth/passkeys/authenticate/complete`, {
|
|
436
|
+
method: 'POST',
|
|
437
|
+
credentials: 'include',
|
|
438
|
+
headers: { 'content-type': 'application/json' },
|
|
439
|
+
body: JSON.stringify({ response: asserted }),
|
|
440
|
+
});
|
|
441
|
+
if (!complete.ok)
|
|
442
|
+
throw new Error('Passkey sign-in failed.');
|
|
443
|
+
const body = (await complete.json());
|
|
444
|
+
if (onSignIn)
|
|
445
|
+
onSignIn(body);
|
|
446
|
+
if (typeof window !== 'undefined')
|
|
447
|
+
window.location.href = redirectUrl;
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
onError(err?.message ?? 'Passkey sign-in failed.');
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
setBusy(false);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return (_jsxs("form", { onSubmit: submit, children: [_jsxs("div", { className: "af-field", children: [_jsx("label", { className: "af-label", children: "Email" }), _jsx("input", { className: "af-input", type: "email", autoComplete: "email webauthn", required: true, value: email, onChange: (e) => setEmail(e.target.value), placeholder: "you@company.com" })] }), _jsx("div", { className: "af-primary-row", children: _jsx("button", { type: "submit", className: "af-btn af-btn-primary", disabled: busy, children: busy ? 'Checking passkey…' : 'Continue with passkey' }) })] }));
|
|
457
|
+
}
|
|
458
|
+
/* ─────────────────── helpers ─────────────────── */
|
|
459
|
+
function WalletIcon({ wallet }) {
|
|
460
|
+
// Tiny mark — purely decorative. Lets the buttons line up visually
|
|
461
|
+
// with the OAuth ones without bundling a wallet icon library.
|
|
462
|
+
const colors = {
|
|
463
|
+
metamask: '#F6851B',
|
|
464
|
+
coinbase_wallet: '#0052FF',
|
|
465
|
+
okx_wallet: '#000000',
|
|
466
|
+
solana: '#9945FF',
|
|
467
|
+
base: '#0052FF',
|
|
468
|
+
};
|
|
469
|
+
return (_jsx("span", { className: "af-social-icon", "aria-hidden": true, style: {
|
|
470
|
+
display: 'inline-flex',
|
|
471
|
+
alignItems: 'center',
|
|
472
|
+
justifyContent: 'center',
|
|
473
|
+
background: colors[wallet] ?? '#6b7280',
|
|
474
|
+
color: 'white',
|
|
475
|
+
borderRadius: 4,
|
|
476
|
+
fontSize: 9,
|
|
477
|
+
fontWeight: 700,
|
|
478
|
+
textTransform: 'uppercase',
|
|
479
|
+
}, children: (WALLET_LABELS[wallet] ?? wallet).slice(0, 1) }));
|
|
480
|
+
}
|
|
481
|
+
function SocialIcon({ provider }) {
|
|
482
|
+
if (provider === 'google') {
|
|
483
|
+
return (_jsxs("svg", { className: "af-social-icon", viewBox: "0 0 48 48", "aria-hidden": true, children: [_jsx("path", { fill: "#EA4335", d: "M24 9.5c3.5 0 6.6 1.2 9.1 3.6l6.8-6.8C35.6 2.4 30.2 0 24 0 14.7 0 6.7 5.4 2.9 13.2l7.9 6.2C12.5 13.4 17.8 9.5 24 9.5z" }), _jsx("path", { fill: "#4285F4", d: "M46.5 24.5c0-1.6-.2-3.1-.5-4.5H24v8.5h12.7c-.6 3-2.3 5.5-4.9 7.2l7.6 5.9c4.4-4 7.1-10 7.1-17.1z" }), _jsx("path", { fill: "#FBBC05", d: "M10.8 28.6c-.5-1.3-.8-2.7-.8-4.1s.3-2.8.8-4.1l-7.9-6.2C1.3 17.5 0 20.6 0 24s1.3 6.5 3 9.8l7.8-6.2z" }), _jsx("path", { fill: "#34A853", d: "M24 48c6.5 0 11.9-2.1 15.9-5.8l-7.6-5.9c-2.1 1.4-4.8 2.3-8.3 2.3-6.2 0-11.5-3.9-13.3-9.5l-7.9 6.2C6.7 42.6 14.7 48 24 48z" })] }));
|
|
484
|
+
}
|
|
485
|
+
if (provider === 'github') {
|
|
486
|
+
return (_jsx("svg", { className: "af-social-icon", viewBox: "0 0 24 24", "aria-hidden": true, children: _jsx("path", { fill: "#111", d: "M12 .5C5.65.5.5 5.66.5 12.02c0 5.1 3.29 9.42 7.86 10.95.57.1.79-.25.79-.55v-2c-3.2.7-3.87-1.37-3.87-1.37-.52-1.33-1.28-1.68-1.28-1.68-1.05-.72.08-.7.08-.7 1.16.08 1.77 1.19 1.77 1.19 1.03 1.76 2.7 1.25 3.35.96.1-.75.4-1.26.73-1.55-2.55-.29-5.24-1.27-5.24-5.66 0-1.25.45-2.27 1.18-3.07-.12-.29-.51-1.46.11-3.05 0 0 .96-.31 3.15 1.17.92-.26 1.9-.39 2.88-.4.98 0 1.96.13 2.88.4 2.19-1.48 3.14-1.17 3.14-1.17.63 1.59.23 2.76.12 3.05.73.8 1.18 1.82 1.18 3.07 0 4.4-2.69 5.37-5.25 5.65.41.35.78 1.04.78 2.1v3.12c0 .31.21.66.79.55A11.51 11.51 0 0 0 23.5 12.02C23.5 5.66 18.35.5 12 .5z" }) }));
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type SocialProvider = 'google' | 'github' | 'facebook' | 'apple' | 'microsoft' | 'linkedin' | 'discord' | 'slack' | 'twitter';
|
|
2
|
+
export type SignUpProps = {
|
|
3
|
+
redirectUrl?: string;
|
|
4
|
+
signInUrl?: string;
|
|
5
|
+
title?: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
showBranding?: boolean;
|
|
8
|
+
brandText?: string;
|
|
9
|
+
/** Override social providers shown. Default: whatever the dashboard has enabled. */
|
|
10
|
+
socialProviders?: SocialProvider[];
|
|
11
|
+
onSignUp?: (result: {
|
|
12
|
+
userId: string;
|
|
13
|
+
sessionId: string;
|
|
14
|
+
}) => void;
|
|
15
|
+
};
|
|
16
|
+
export declare function SignUp({ redirectUrl, signInUrl, title, subtitle, showBranding, brandText, socialProviders: socialOverride, onSignUp, }: SignUpProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=SignUp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SignUp.d.ts","sourceRoot":"","sources":["../../src/ui/SignUp.tsx"],"names":[],"mappings":"AAQA,KAAK,cAAc,GACf,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,OAAO,GACP,WAAW,GACX,UAAU,GACV,SAAS,GACT,OAAO,GACP,SAAS,CAAC;AA8Bd,MAAM,MAAM,WAAW,GAAG;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CACpE,CAAC;AAEF,wBAAgB,MAAM,CAAC,EACrB,WAAiB,EACjB,SAAsB,EACtB,KAA6B,EAC7B,QAAwC,EACxC,YAAmB,EACnB,SAAsB,EACtB,eAAe,EAAE,cAAc,EAC/B,QAAQ,GACT,EAAE,WAAW,2CAwUb"}
|