@atlasauth/react 0.1.17 → 0.1.19
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/CHANGELOG.md +18 -0
- package/dist/AtlasProvider.d.ts +9 -0
- package/dist/AtlasProvider.js +2 -0
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/account.d.ts +66 -0
- package/dist/account.js +223 -0
- package/dist/account.js.map +1 -0
- package/dist/auth-buttons.d.ts +29 -0
- package/dist/auth-buttons.js +43 -0
- package/dist/auth-buttons.js.map +1 -0
- package/dist/billing.d.ts +127 -0
- package/dist/billing.js +235 -0
- package/dist/billing.js.map +1 -0
- package/dist/components-shared.d.ts +22 -0
- package/dist/components-shared.js +70 -0
- package/dist/components-shared.js.map +1 -0
- package/dist/components.d.ts +29 -427
- package/dist/components.js +68 -1223
- package/dist/components.js.map +1 -1
- package/dist/control-components.d.ts +46 -0
- package/dist/control-components.js +69 -0
- package/dist/control-components.js.map +1 -0
- package/dist/flow.d.ts +81 -0
- package/dist/flow.js +319 -0
- package/dist/flow.js.map +1 -0
- package/dist/organization.d.ts +37 -0
- package/dist/organization.js +111 -0
- package/dist/organization.js.map +1 -0
- package/dist/reverification.d.ts +21 -0
- package/dist/reverification.js +74 -0
- package/dist/reverification.js.map +1 -0
- package/dist/sign-in.d.ts +42 -0
- package/dist/sign-in.js +91 -0
- package/dist/sign-in.js.map +1 -0
- package/dist/sign-up.d.ts +18 -0
- package/dist/sign-up.js +37 -0
- package/dist/sign-up.js.map +1 -0
- package/dist/user-button.d.ts +54 -0
- package/dist/user-button.js +75 -0
- package/dist/user-button.js.map +1 -0
- package/dist/waitlist.d.ts +15 -0
- package/dist/waitlist.js +52 -0
- package/dist/waitlist.js.map +1 -0
- package/package.json +4 -3
package/dist/components.js
CHANGED
|
@@ -1,1229 +1,74 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SignedIn = SignedIn;
|
|
4
|
-
exports.SignedOut = SignedOut;
|
|
5
|
-
exports.AtlasLoading = AtlasLoading;
|
|
6
|
-
exports.AtlasLoaded = AtlasLoaded;
|
|
7
|
-
exports.Protect = Protect;
|
|
8
|
-
exports.SignIn = SignIn;
|
|
9
|
-
exports.useSignIn = useSignIn;
|
|
10
|
-
exports.SignUp = SignUp;
|
|
11
|
-
exports.UserButton = UserButton;
|
|
12
|
-
exports.SignInButton = SignInButton;
|
|
13
|
-
exports.SignUpButton = SignUpButton;
|
|
14
|
-
exports.SignOutButton = SignOutButton;
|
|
15
|
-
exports.RedirectToSignIn = RedirectToSignIn;
|
|
16
|
-
exports.RedirectToSignUp = RedirectToSignUp;
|
|
17
|
-
exports.MultisessionAppSupport = MultisessionAppSupport;
|
|
18
|
-
exports.UserAvatar = UserAvatar;
|
|
19
|
-
exports.useSignUp = useSignUp;
|
|
20
|
-
exports.OrganizationSwitcher = OrganizationSwitcher;
|
|
21
|
-
exports.useOrganizationList = useOrganizationList;
|
|
22
|
-
exports.CreateOrganization = CreateOrganization;
|
|
23
|
-
exports.OrganizationList = OrganizationList;
|
|
24
|
-
exports.ActiveSessions = ActiveSessions;
|
|
25
|
-
exports.ProfileCard = ProfileCard;
|
|
26
|
-
exports.ManageMfa = ManageMfa;
|
|
27
|
-
exports.ManagePasskeys = ManagePasskeys;
|
|
28
|
-
exports.UserProfile = UserProfile;
|
|
29
|
-
exports.OrganizationProfile = OrganizationProfile;
|
|
30
|
-
exports.useWaitlist = useWaitlist;
|
|
31
|
-
exports.Waitlist = Waitlist;
|
|
32
|
-
exports.useReverification = useReverification;
|
|
33
|
-
exports.useBilling = useBilling;
|
|
34
|
-
exports.useCheckout = useCheckout;
|
|
35
|
-
exports.useSubscription = useSubscription;
|
|
36
|
-
exports.useInvoices = useInvoices;
|
|
37
|
-
exports.usePaymentMethods = usePaymentMethods;
|
|
38
|
-
exports.PricingTable = PricingTable;
|
|
39
|
-
exports.Invoices = Invoices;
|
|
40
|
-
exports.EmbeddedCheckout = EmbeddedCheckout;
|
|
41
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
42
|
-
const react_1 = require("react");
|
|
43
|
-
const js_1 = require("@atlasauth/js");
|
|
44
|
-
const AtlasProvider_1 = require("./AtlasProvider");
|
|
45
|
-
const fapi_1 = require("./fapi");
|
|
46
|
-
const hooks_1 = require("./hooks");
|
|
47
|
-
const appearance_1 = require("./appearance");
|
|
48
|
-
const i18n_1 = require("./i18n");
|
|
49
|
-
const protect_1 = require("./protect");
|
|
50
|
-
/**
|
|
51
|
-
* §10.2 components.
|
|
52
|
-
*
|
|
53
|
-
* `<SignIn/>` and `<SignUp/>` are "full multi-step flows driven entirely by the
|
|
54
|
-
* server-side attempt status". That phrase is the design: this file contains no
|
|
55
|
-
* decision about what comes next. It renders whatever `status` the server
|
|
56
|
-
* returned, via the exhaustive mapping in @atlasauth/js — so adding a step to the
|
|
57
|
-
* flow is a server change, and a client that has not shipped yet degrades to a
|
|
58
|
-
* visible "needs an update" rather than a blank screen.
|
|
59
|
-
*/
|
|
60
|
-
function useClass(appearanceKey, base) {
|
|
61
|
-
const { appearance } = (0, AtlasProvider_1.useAtlas)();
|
|
62
|
-
return (0, appearance_1.classFor)(appearanceKey, appearance, base);
|
|
63
|
-
}
|
|
64
|
-
function useText() {
|
|
65
|
-
const { catalog } = (0, AtlasProvider_1.useAtlas)();
|
|
66
|
-
return (key, vars) => (0, i18n_1.translate)(catalog, key, vars);
|
|
67
|
-
}
|
|
68
|
-
function SignedIn({ children }) {
|
|
69
|
-
const { isLoaded, isSignedIn } = (0, hooks_1.useUser)();
|
|
70
|
-
// Renders nothing while loading rather than flashing the signed-out branch.
|
|
71
|
-
if (!isLoaded || !isSignedIn)
|
|
72
|
-
return null;
|
|
73
|
-
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
74
|
-
}
|
|
75
|
-
function SignedOut({ children }) {
|
|
76
|
-
const { isLoaded, isSignedIn } = (0, hooks_1.useUser)();
|
|
77
|
-
if (!isLoaded || isSignedIn)
|
|
78
|
-
return null;
|
|
79
|
-
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
80
|
-
}
|
|
81
|
-
/** §10.2: render children only while the SDK is still loading. */
|
|
82
|
-
function AtlasLoading({ children }) {
|
|
83
|
-
const { isLoaded } = (0, hooks_1.useUser)();
|
|
84
|
-
return isLoaded ? null : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
|
|
85
|
-
}
|
|
86
|
-
/** §10.2: render children only once the SDK has finished loading — the
|
|
87
|
-
* symmetric partner of {@link AtlasLoading}, so a consumer can show a spinner
|
|
88
|
-
* and its resolved content without hand-rolling the inverse condition. */
|
|
89
|
-
function AtlasLoaded({ children }) {
|
|
90
|
-
const { isLoaded } = (0, hooks_1.useUser)();
|
|
91
|
-
return isLoaded ? (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }) : null;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* §10.2 `<Protect permission="…">`.
|
|
95
|
-
*
|
|
96
|
-
* A rendering helper, never an authorization boundary — anyone can flip the
|
|
97
|
-
* condition in devtools. The server checking the same permission is what
|
|
98
|
-
* actually stops them, and this component's job is only to avoid showing a
|
|
99
|
-
* button that would fail.
|
|
100
|
-
*/
|
|
101
|
-
function Protect({ children, fallback = null, ...condition }) {
|
|
102
|
-
const { claims, status } = (0, AtlasProvider_1.useAtlas)();
|
|
103
|
-
if (status === 'loading')
|
|
104
|
-
return null;
|
|
105
|
-
return (0, protect_1.evaluate)(claims, condition).allowed ? (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }) : (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback });
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* The server-driven flow shell.
|
|
109
|
-
*
|
|
110
|
-
* Every branch comes from `nextStep`, which is exhaustive — including the
|
|
111
|
-
* `unknown` case, which renders an honest message instead of nothing.
|
|
112
|
-
*/
|
|
113
|
-
function AttemptFlow({ attempt, onSubmit, busy, errors, titleKey, }) {
|
|
114
|
-
const t = useText();
|
|
115
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
116
|
-
const titleClass = useClass('headerTitle', 'atlas-title');
|
|
117
|
-
const inputClass = useClass('formFieldInput', 'atlas-input');
|
|
118
|
-
const buttonClass = useClass('formButtonPrimary', 'atlas-button');
|
|
119
|
-
const errorClass = useClass('formFieldError', 'atlas-error');
|
|
120
|
-
const [values, setValues] = (0, react_1.useState)({});
|
|
121
|
-
const step = attempt ? (0, js_1.nextStep)(attempt) : { kind: 'collect_identifier' };
|
|
122
|
-
const field = (name, labelKey, type = 'text') => ((0, jsx_runtime_1.jsxs)("label", { className: "atlas-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t(labelKey) }), (0, jsx_runtime_1.jsx)("input", { className: inputClass, type: type, value: values[name] ?? '', onChange: (event) => setValues((v) => ({ ...v, [name]: event.target.value })) }), errors
|
|
123
|
-
.filter((error) => error.param === name)
|
|
124
|
-
.map((error) => ((0, jsx_runtime_1.jsx)("span", { className: errorClass, children: error.message }, error.code)))] }, name));
|
|
125
|
-
const fields = () => {
|
|
126
|
-
switch (step.kind) {
|
|
127
|
-
case 'collect_identifier':
|
|
128
|
-
return [field('identifier', 'signIn.identifierLabel', 'email')];
|
|
129
|
-
case 'collect_first_factor':
|
|
130
|
-
return [field('password', 'signIn.passwordLabel', 'password')];
|
|
131
|
-
case 'collect_second_factor':
|
|
132
|
-
return [field('code', 'mfa.codeLabel')];
|
|
133
|
-
case 'enroll_second_factor':
|
|
134
|
-
/**
|
|
135
|
-
* Two codes, not one. §5.6 wants consecutive codes so a phone with a
|
|
136
|
-
* fast clock fails here rather than on every sign-in afterwards, and
|
|
137
|
-
* asking for one would hide that failure until it was expensive.
|
|
138
|
-
*/
|
|
139
|
-
return [
|
|
140
|
-
field('code', 'mfa.enrollFirstCodeLabel'),
|
|
141
|
-
field('secondCode', 'mfa.enrollSecondCodeLabel'),
|
|
142
|
-
];
|
|
143
|
-
case 'collect_email_code':
|
|
144
|
-
return [field('code', 'signIn.emailCodeLabel')];
|
|
145
|
-
case 'collect_new_password':
|
|
146
|
-
return [field('password', 'reset.newPasswordLabel', 'password')];
|
|
147
|
-
case 'await_oauth':
|
|
148
|
-
return [(0, jsx_runtime_1.jsx)("p", { children: t('signIn.magicLinkSent') }, "oauth")];
|
|
149
|
-
case 'done':
|
|
150
|
-
return [(0, jsx_runtime_1.jsx)("p", { children: t('signIn.checkOtherTab') }, "done")];
|
|
151
|
-
case 'restart':
|
|
152
|
-
return [(0, jsx_runtime_1.jsx)("p", { children: t('error.generic') }, "restart")];
|
|
153
|
-
default:
|
|
154
|
-
/**
|
|
155
|
-
* A status this SDK version does not know. Saying so beats rendering
|
|
156
|
-
* nothing, which is a blank login box the user cannot act on.
|
|
157
|
-
*/
|
|
158
|
-
return [
|
|
159
|
-
(0, jsx_runtime_1.jsx)("p", { className: errorClass, children: t('error.generic') }, "unknown"),
|
|
160
|
-
];
|
|
161
|
-
}
|
|
162
|
-
};
|
|
163
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, children: [(0, jsx_runtime_1.jsx)("h1", { className: titleClass, children: t(titleKey) }), errors
|
|
164
|
-
.filter((error) => !error.param)
|
|
165
|
-
.map((error) => ((0, jsx_runtime_1.jsx)("p", { className: errorClass, role: "alert", children: error.message }, error.code))), (0, jsx_runtime_1.jsxs)("form", { onSubmit: (event) => {
|
|
166
|
-
event.preventDefault();
|
|
167
|
-
onSubmit(values);
|
|
168
|
-
}, children: [fields(), step.kind !== 'done' && step.kind !== 'await_oauth' ? ((0, jsx_runtime_1.jsx)("button", { className: buttonClass, disabled: busy, type: "submit", children: t('signIn.submit') })) : null] })] }));
|
|
169
|
-
}
|
|
170
|
-
/**
|
|
171
|
-
* Drives the flow through `advance`, which owns the step-to-endpoint mapping.
|
|
172
|
-
* This component never decides what comes next — it renders the status the
|
|
173
|
-
* server returned and posts whatever the driver says the current step needs.
|
|
174
|
-
*/
|
|
175
|
-
function useFlow() {
|
|
176
|
-
const { publishableKey, frontendApi, reload, pendingAttempt } = (0, AtlasProvider_1.useAtlas)();
|
|
177
|
-
// Seeded from a redirect that came back mid-flow (e.g. OAuth needing a second
|
|
178
|
-
// factor), so the user resumes at the demanded step instead of starting over.
|
|
179
|
-
const [state, setState] = (0, react_1.useState)(() => (0, fapi_1.flowFromPending)(pendingAttempt));
|
|
180
|
-
const client = (0, react_1.useMemo)(() => new fapi_1.FapiClient({ publishableKey, baseUrl: frontendApi }), [frontendApi, publishableKey]);
|
|
181
|
-
// Apply a driver result to the UI, and — on completion — exchange the one-time
|
|
182
|
-
// ticket (§7.1) for cookies over a same-origin request (FAPI is cross-origin
|
|
183
|
-
// to the app and cannot set the cookie on the completion response itself),
|
|
184
|
-
// THEN re-boot so the rest of the app sees a signed-in user. Shared by the
|
|
185
|
-
// password/flow `submit` and the native (Google) button, so both complete a
|
|
186
|
-
// sign-in identically.
|
|
187
|
-
const finish = (0, react_1.useCallback)(async (next) => {
|
|
188
|
-
setState(next);
|
|
189
|
-
if (next.attempt?.status === 'complete') {
|
|
190
|
-
if (next.ticket) {
|
|
191
|
-
await client.post('/v1/client/tickets/exchange', {
|
|
192
|
-
attempt_id: next.attempt.id,
|
|
193
|
-
ticket: next.ticket,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
await reload();
|
|
197
|
-
}
|
|
198
|
-
}, [client, reload]);
|
|
199
|
-
const submit = (0, react_1.useCallback)((values) => {
|
|
200
|
-
void (async () => {
|
|
201
|
-
setState((current) => ({ ...current, busy: true, errors: [] }));
|
|
202
|
-
const next = await (0, fapi_1.advance)(client, state, values);
|
|
203
|
-
await finish(next);
|
|
204
|
-
})();
|
|
205
|
-
}, [client, finish, state]);
|
|
206
|
-
/** §5.3: collect a sign-in completed by a link opened on another device. */
|
|
207
|
-
(0, react_1.useEffect)(() => {
|
|
208
|
-
if (!(0, fapi_1.shouldKeepPolling)(state))
|
|
209
|
-
return;
|
|
210
|
-
const timer = setInterval(() => {
|
|
211
|
-
void (async () => {
|
|
212
|
-
const { state: next, ticket } = await (0, fapi_1.pollAttempt)(client, state);
|
|
213
|
-
setState(next);
|
|
214
|
-
if (!ticket || !next.attempt)
|
|
215
|
-
return;
|
|
216
|
-
// The ticket is exchanged by a normal request, so no session token has
|
|
217
|
-
// ever appeared in a URL.
|
|
218
|
-
await client.post('/v1/client/tickets/exchange', {
|
|
219
|
-
attempt_id: next.attempt.id,
|
|
220
|
-
ticket,
|
|
221
|
-
});
|
|
222
|
-
await reload();
|
|
223
|
-
})();
|
|
224
|
-
}, 2_000);
|
|
225
|
-
return () => clearInterval(timer);
|
|
226
|
-
}, [client, reload, state]);
|
|
227
|
-
return { state, submit, client, finish };
|
|
228
|
-
}
|
|
229
|
-
/** Google's multi-color "G" mark — trusted, static, first-party SVG. */
|
|
230
|
-
function GoogleMark() {
|
|
231
|
-
return ((0, jsx_runtime_1.jsxs)("svg", { className: "atlas-social-icon", width: "18", height: "18", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false", children: [(0, jsx_runtime_1.jsx)("path", { fill: "#4285F4", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" }), (0, jsx_runtime_1.jsx)("path", { fill: "#34A853", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" }), (0, jsx_runtime_1.jsx)("path", { fill: "#FBBC05", d: "M5.84 14.1c-.22-.66-.35-1.36-.35-2.1s.13-1.44.35-2.1V7.06H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.94l3.66-2.84z" }), (0, jsx_runtime_1.jsx)("path", { fill: "#EA4335", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.06l3.66 2.84C6.71 7.31 9.14 5.38 12 5.38z" })] }));
|
|
232
|
-
}
|
|
233
|
-
/**
|
|
234
|
-
* §6 the native "Sign in with Google" button (GSI id_token flow — no secret, no
|
|
235
|
-
* redirect). Discovers whether Google native sign-in is enabled for the instance
|
|
236
|
-
* via `/v1/client/environment`; if so it mints a single-use replay-binding nonce
|
|
237
|
-
* and renders OUR OWN themed button, backed by Google's real (but transparent)
|
|
238
|
-
* GSI button overlaid on top. The user sees a button that inherits the app's
|
|
239
|
-
* appearance (the `socialButton` element key / `atlas-social-button` class, so
|
|
240
|
-
* `appearance.elements.socialButton` and the design tokens theme it like every
|
|
241
|
-
* other control) — NOT Google's fixed, unstyleable button. On a returned
|
|
242
|
-
* credential the id_token is posted to the native endpoint and completes through
|
|
243
|
-
* the shared `finish`. Renders nothing when Google native sign-in isn't enabled.
|
|
244
|
-
*
|
|
245
|
-
* One-Tap stays off unless `oneTap` is set (see {@link FlowProps.googleOneTap}).
|
|
246
|
-
*/
|
|
247
|
-
function GoogleSignInButton({ client, onComplete, oneTap, }) {
|
|
248
|
-
const ref = (0, react_1.useRef)(null);
|
|
249
|
-
const socialButtonClass = useClass('socialButton', 'atlas-social-button');
|
|
250
|
-
const [clientId, setClientId] = (0, react_1.useState)(null);
|
|
251
|
-
// Discover the Google client_id (exposed only for native-enabled providers).
|
|
252
|
-
(0, react_1.useEffect)(() => {
|
|
253
|
-
let alive = true;
|
|
254
|
-
void (async () => {
|
|
255
|
-
const res = await client.get('/v1/client/environment');
|
|
256
|
-
const google = res.data?.social_providers?.find((p) => p.provider === 'google' && p.native && p.client_id && p.allow_sign_in !== false);
|
|
257
|
-
if (alive)
|
|
258
|
-
setClientId(google?.client_id ?? null);
|
|
259
|
-
})();
|
|
260
|
-
return () => {
|
|
261
|
-
alive = false;
|
|
262
|
-
};
|
|
263
|
-
}, [client]);
|
|
264
|
-
// Overlay Google's invisible button on our themed one once client_id is known.
|
|
265
|
-
(0, react_1.useEffect)(() => {
|
|
266
|
-
const wrapper = ref.current;
|
|
267
|
-
if (!clientId || !wrapper)
|
|
268
|
-
return;
|
|
269
|
-
let cancelled = false;
|
|
270
|
-
let handle;
|
|
271
|
-
void (async () => {
|
|
272
|
-
const nonce = await (0, js_1.mintNativeNonce)(client, 'google');
|
|
273
|
-
if (cancelled || !nonce || !ref.current)
|
|
274
|
-
return;
|
|
275
|
-
handle = await (0, js_1.renderGoogleOverlayButton)({
|
|
276
|
-
wrapper,
|
|
277
|
-
clientId,
|
|
278
|
-
nonce,
|
|
279
|
-
oneTap: oneTap ?? false,
|
|
280
|
-
onCredential: (idToken) => {
|
|
281
|
-
void (async () => {
|
|
282
|
-
const next = await (0, js_1.nativeSignIn)(client, { provider: 'google', idToken, nonce });
|
|
283
|
-
await onComplete(next);
|
|
284
|
-
})();
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
})();
|
|
288
|
-
return () => {
|
|
289
|
-
cancelled = true;
|
|
290
|
-
handle?.destroy();
|
|
291
|
-
};
|
|
292
|
-
}, [client, clientId, oneTap, onComplete]);
|
|
293
|
-
if (!clientId)
|
|
294
|
-
return null;
|
|
295
|
-
// Our styled button; GSI's transparent real button is overlaid onto this node.
|
|
296
|
-
// Defaults are driven by the appearance design tokens (`--atlas-*` set on
|
|
297
|
-
// `.atlas-root`), so the button matches the app's theme out of the box AND
|
|
298
|
-
// updates when a customer sets `appearance.variables`; `appearance.elements.socialButton`
|
|
299
|
-
// (appended to the class) still layers CSS on top. minHeight matches GSI's
|
|
300
|
-
// own button height so the transparent overlay lines up over the visible one.
|
|
301
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: ref, className: socialButtonClass, "data-atlas": "google-signin", "data-atlas-provider": "google", role: "button", tabIndex: 0, style: DEFAULT_SOCIAL_BUTTON_STYLE, children: [(0, jsx_runtime_1.jsx)(GoogleMark, {}), (0, jsx_runtime_1.jsx)("span", { className: "atlas-social-label", children: "Continue with Google" })] }));
|
|
302
|
-
}
|
|
303
|
-
/** Theme-token defaults so the social button looks right with no customer CSS. */
|
|
304
|
-
const DEFAULT_SOCIAL_BUTTON_STYLE = {
|
|
305
|
-
display: 'flex',
|
|
306
|
-
alignItems: 'center',
|
|
307
|
-
justifyContent: 'center',
|
|
308
|
-
gap: '8px',
|
|
309
|
-
width: '100%',
|
|
310
|
-
boxSizing: 'border-box',
|
|
311
|
-
minHeight: '40px',
|
|
312
|
-
padding: '10px 16px',
|
|
313
|
-
border: '1px solid var(--atlas-color-border, #e4e4e9)',
|
|
314
|
-
borderRadius: 'var(--atlas-border-radius, 8px)',
|
|
315
|
-
background: 'var(--atlas-color-background, #ffffff)',
|
|
316
|
-
color: 'var(--atlas-color-text, #1c1c1f)',
|
|
317
|
-
fontFamily: 'var(--atlas-font-family, inherit)',
|
|
318
|
-
fontWeight: 500,
|
|
319
|
-
cursor: 'pointer',
|
|
320
|
-
userSelect: 'none',
|
|
321
|
-
};
|
|
322
|
-
/** Human label for a provider key, for the redirect button text. */
|
|
323
|
-
const PROVIDER_LABEL = {
|
|
324
|
-
github: 'GitHub',
|
|
325
|
-
gitlab: 'GitLab',
|
|
326
|
-
microsoft: 'Microsoft',
|
|
327
|
-
apple: 'Apple',
|
|
328
|
-
facebook: 'Facebook',
|
|
329
|
-
discord: 'Discord',
|
|
330
|
-
linkedin: 'LinkedIn',
|
|
331
|
-
google: 'Google',
|
|
332
|
-
x: 'X',
|
|
333
|
-
twitter: 'X',
|
|
334
|
-
slack: 'Slack',
|
|
335
|
-
spotify: 'Spotify',
|
|
336
|
-
twitch: 'Twitch',
|
|
337
|
-
notion: 'Notion',
|
|
338
|
-
};
|
|
339
|
-
function providerLabel(p) {
|
|
340
|
-
return PROVIDER_LABEL[p] ?? p.charAt(0).toUpperCase() + p.slice(1).replace(/[_-]/g, ' ');
|
|
341
|
-
}
|
|
342
2
|
/**
|
|
343
|
-
* §
|
|
3
|
+
* §10.2 components — public aggregation point for the @atlasauth/react component
|
|
4
|
+
* library.
|
|
344
5
|
*
|
|
345
|
-
*
|
|
346
|
-
* button
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
|
|
352
|
-
function SocialSignInButtons({ client, onComplete, oneTap, }) {
|
|
353
|
-
const socialButtonClass = useClass('socialButton', 'atlas-social-button');
|
|
354
|
-
const [redirectProviders, setRedirectProviders] = (0, react_1.useState)([]);
|
|
355
|
-
(0, react_1.useEffect)(() => {
|
|
356
|
-
let alive = true;
|
|
357
|
-
void (async () => {
|
|
358
|
-
const res = await client.get('/v1/client/environment');
|
|
359
|
-
const list = (res.data?.social_providers ?? [])
|
|
360
|
-
.filter((p) => p.allow_sign_in !== false)
|
|
361
|
-
// Google renders as the native GSI button below. Any other native provider
|
|
362
|
-
// gets its own in-page button in a later pass; the rest use redirect.
|
|
363
|
-
.filter((p) => p.provider !== 'google')
|
|
364
|
-
.map((p) => p.provider);
|
|
365
|
-
if (alive)
|
|
366
|
-
setRedirectProviders(list);
|
|
367
|
-
})();
|
|
368
|
-
return () => {
|
|
369
|
-
alive = false;
|
|
370
|
-
};
|
|
371
|
-
}, [client]);
|
|
372
|
-
const startRedirect = (0, react_1.useCallback)((provider) => {
|
|
373
|
-
void (async () => {
|
|
374
|
-
const res = await (0, js_1.startOauthRedirect)(client, {
|
|
375
|
-
provider,
|
|
376
|
-
redirectUrl: typeof window !== 'undefined' ? window.location.href : '/',
|
|
377
|
-
});
|
|
378
|
-
if (res && typeof window !== 'undefined')
|
|
379
|
-
window.location.href = res.authorizationUrl;
|
|
380
|
-
})();
|
|
381
|
-
}, [client]);
|
|
382
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(GoogleSignInButton, { client: client, onComplete: onComplete, oneTap: oneTap }), redirectProviders.map((provider) => ((0, jsx_runtime_1.jsx)("div", { className: socialButtonClass, "data-atlas": "social-signin", "data-atlas-provider": provider, role: "button", tabIndex: 0, style: DEFAULT_SOCIAL_BUTTON_STYLE, onClick: () => startRedirect(provider), onKeyDown: (e) => {
|
|
383
|
-
if (e.key === 'Enter' || e.key === ' ')
|
|
384
|
-
startRedirect(provider);
|
|
385
|
-
}, children: (0, jsx_runtime_1.jsxs)("span", { className: "atlas-social-label", children: ["Continue with ", providerLabel(provider)] }) }, provider)))] }));
|
|
386
|
-
}
|
|
387
|
-
function SignIn(props = {}) {
|
|
388
|
-
const { state, submit, client, finish } = useFlow();
|
|
389
|
-
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(AttemptFlow, { attempt: state.attempt, busy: state.busy, errors: state.errors, titleKey: "signIn.title", onSubmit: submit }), !state.attempt ? ((0, jsx_runtime_1.jsx)(SocialSignInButtons, { client: client, onComplete: finish, oneTap: props.googleOneTap })) : null] }));
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* §6 headless native sign-in, for a consumer that renders its OWN Google button
|
|
393
|
-
* (or Apple / Facebook Limited Login). Mint a nonce with `mintNonce(provider)`,
|
|
394
|
-
* hand it to the provider SDK, then pass the resulting id_token to
|
|
395
|
-
* `signInWithIdToken` — it posts to the native endpoint, exchanges the ticket
|
|
396
|
-
* for cookies and re-boots the app on completion, returning the flow state so
|
|
397
|
-
* the caller can react to `needs_second_factor` etc.
|
|
398
|
-
*/
|
|
399
|
-
function useSignIn() {
|
|
400
|
-
const { publishableKey, frontendApi, reload } = (0, AtlasProvider_1.useAtlas)();
|
|
401
|
-
const client = (0, react_1.useMemo)(() => new fapi_1.FapiClient({ publishableKey, baseUrl: frontendApi }), [frontendApi, publishableKey]);
|
|
402
|
-
const mintNonce = (0, react_1.useCallback)((provider) => (0, js_1.mintNativeNonce)(client, provider), [client]);
|
|
403
|
-
const signInWithIdToken = (0, react_1.useCallback)(async (input) => {
|
|
404
|
-
const next = await (0, js_1.nativeSignIn)(client, input);
|
|
405
|
-
if (next.attempt?.status === 'complete' && next.ticket) {
|
|
406
|
-
await client.post('/v1/client/tickets/exchange', {
|
|
407
|
-
attempt_id: next.attempt.id,
|
|
408
|
-
ticket: next.ticket,
|
|
409
|
-
});
|
|
410
|
-
await reload();
|
|
411
|
-
}
|
|
412
|
-
return next;
|
|
413
|
-
}, [client, reload]);
|
|
414
|
-
return { mintNonce, signInWithIdToken };
|
|
415
|
-
}
|
|
416
|
-
function SignUp(_props = {}) {
|
|
417
|
-
const { state, submit } = useFlow();
|
|
418
|
-
return ((0, jsx_runtime_1.jsx)(AttemptFlow, { attempt: state.attempt, busy: state.busy, errors: state.errors, titleKey: "signUp.title", onSubmit: submit }));
|
|
419
|
-
}
|
|
420
|
-
/** A container for custom `<UserButton.Action>` / `<UserButton.Link>` rows. Renders nothing itself. */
|
|
421
|
-
function UserButtonMenuItems(_props) {
|
|
422
|
-
return null;
|
|
423
|
-
}
|
|
424
|
-
/** A custom action row inside `<UserButton.MenuItems>`. Renders nothing itself. */
|
|
425
|
-
function UserButtonAction(_props) {
|
|
426
|
-
return null;
|
|
427
|
-
}
|
|
428
|
-
/** A custom link row inside `<UserButton.MenuItems>`. Renders nothing itself. */
|
|
429
|
-
function UserButtonLink(_props) {
|
|
430
|
-
return null;
|
|
431
|
-
}
|
|
432
|
-
/** Walk a children tree and collect the extension descriptors matching `types`. */
|
|
433
|
-
function collectByType(children, match, out = []) {
|
|
434
|
-
react_1.Children.forEach(children, (child) => {
|
|
435
|
-
if (!(0, react_1.isValidElement)(child))
|
|
436
|
-
return;
|
|
437
|
-
const hit = match(child);
|
|
438
|
-
if (hit)
|
|
439
|
-
out.push(hit);
|
|
440
|
-
// Descend so `<Action>` inside `<MenuItems>` (or any wrapper) is still found.
|
|
441
|
-
const nested = child.props?.children;
|
|
442
|
-
if (nested)
|
|
443
|
-
collectByType(nested, match, out);
|
|
444
|
-
});
|
|
445
|
-
return out;
|
|
446
|
-
}
|
|
447
|
-
function collectMenuEntries(children) {
|
|
448
|
-
return collectByType(children, (el) => {
|
|
449
|
-
if (el.type === UserButtonAction)
|
|
450
|
-
return { kind: 'action', props: el.props };
|
|
451
|
-
if (el.type === UserButtonLink)
|
|
452
|
-
return { kind: 'link', props: el.props };
|
|
453
|
-
return undefined;
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
/**
|
|
457
|
-
* The signed-in user's avatar + dropdown. Pass `<UserButton.MenuItems>` children
|
|
458
|
-
* to add custom `<UserButton.Action>` / `<UserButton.Link>` rows above sign-out.
|
|
459
|
-
*/
|
|
460
|
-
function UserButton({ children } = {}) {
|
|
461
|
-
const { user } = (0, hooks_1.useUser)();
|
|
462
|
-
const { signOut } = (0, hooks_1.useAuth)();
|
|
463
|
-
const { session } = (0, hooks_1.useSession)();
|
|
464
|
-
const { sessions, setActive } = (0, hooks_1.useSessionList)();
|
|
465
|
-
const t = useText();
|
|
466
|
-
const [open, setOpen] = (0, react_1.useState)(false);
|
|
467
|
-
const avatarClass = useClass('avatar', 'atlas-avatar');
|
|
468
|
-
const menuClass = useClass('menu', 'atlas-menu');
|
|
469
|
-
const itemClass = useClass('menuItem', 'atlas-menu-item');
|
|
470
|
-
const custom = (0, react_1.useMemo)(() => collectMenuEntries(children), [children]);
|
|
471
|
-
if (!user)
|
|
472
|
-
return null;
|
|
473
|
-
const label = [user.first_name, user.last_name].filter(Boolean).join(' ') || user.username || '';
|
|
474
|
-
const nameOf = (u) => [u.first_name, u.last_name].filter(Boolean).join(' ') || u.username || '';
|
|
475
|
-
// §L1 the OTHER accounts signed in on this browser (switchable in one click).
|
|
476
|
-
const others = sessions.filter((s) => s.id !== session?.id);
|
|
477
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: "atlas-user-button", children: [(0, jsx_runtime_1.jsx)("button", { className: avatarClass, onClick: () => setOpen((o) => !o), "aria-expanded": open, children: user.image_url ? (0, jsx_runtime_1.jsx)("img", { alt: "", src: user.image_url }) : (0, jsx_runtime_1.jsx)("span", { children: label.slice(0, 1) }) }), open ? ((0, jsx_runtime_1.jsxs)("div", { className: menuClass, role: "menu", children: [(0, jsx_runtime_1.jsx)("span", { className: itemClass, children: label }), others.map((s) => ((0, jsx_runtime_1.jsxs)("button", { className: itemClass, role: "menuitem", "data-atlas": "switch-session", onClick: () => {
|
|
478
|
-
setOpen(false);
|
|
479
|
-
void setActive({ session: s.id });
|
|
480
|
-
}, children: [s.user.image_url ? ((0, jsx_runtime_1.jsx)("img", { alt: "", src: s.user.image_url, width: 16, height: 16 })) : null, t('userButton.switchTo'), " ", nameOf(s.user)] }, s.id))), custom.map((entry, i) => entry.kind === 'action' ? ((0, jsx_runtime_1.jsxs)("button", { className: itemClass, role: "menuitem", onClick: () => {
|
|
481
|
-
setOpen(false);
|
|
482
|
-
entry.props.onClick?.();
|
|
483
|
-
}, children: [entry.props.labelIcon, entry.props.label] }, i)) : ((0, jsx_runtime_1.jsxs)("a", { className: itemClass, role: "menuitem", href: entry.props.href, children: [entry.props.labelIcon, entry.props.label] }, i))), (0, jsx_runtime_1.jsx)("button", { className: itemClass, role: "menuitem", onClick: () => void signOut(), children: t('userButton.signOut') })] })) : null] }));
|
|
484
|
-
}
|
|
485
|
-
UserButton.MenuItems = UserButtonMenuItems;
|
|
486
|
-
UserButton.Action = UserButtonAction;
|
|
487
|
-
UserButton.Link = UserButtonLink;
|
|
488
|
-
/** Navigate the whole document. Guarded for SSR; `replace` for redirect helpers. */
|
|
489
|
-
function navigateTo(url, replace = false) {
|
|
490
|
-
if (typeof window === 'undefined')
|
|
491
|
-
return;
|
|
492
|
-
if (replace)
|
|
493
|
-
window.location.replace(url);
|
|
494
|
-
else
|
|
495
|
-
window.location.assign(url);
|
|
496
|
-
}
|
|
497
|
-
/** Append `?redirect_url=` (where to land after auth), preserving any existing query. */
|
|
498
|
-
function withRedirect(url, redirectUrl) {
|
|
499
|
-
if (!redirectUrl)
|
|
500
|
-
return url;
|
|
501
|
-
const sep = url.includes('?') ? '&' : '?';
|
|
502
|
-
return `${url}${sep}redirect_url=${encodeURIComponent(redirectUrl)}`;
|
|
503
|
-
}
|
|
504
|
-
/** Clerk-parity ergonomic: a button that sends the user to your sign-in page. */
|
|
505
|
-
function SignInButton({ url = '/sign-in', redirectUrl, children, className }) {
|
|
506
|
-
const t = useText();
|
|
507
|
-
return ((0, jsx_runtime_1.jsx)("button", { type: "button", className: className, onClick: () => navigateTo(withRedirect(url, redirectUrl)), children: children ?? t('signIn.title') }));
|
|
508
|
-
}
|
|
509
|
-
/** Clerk-parity ergonomic: a button that sends the user to your sign-up page. */
|
|
510
|
-
function SignUpButton({ url = '/sign-up', redirectUrl, children, className }) {
|
|
511
|
-
const t = useText();
|
|
512
|
-
return ((0, jsx_runtime_1.jsx)("button", { type: "button", className: className, onClick: () => navigateTo(withRedirect(url, redirectUrl)), children: children ?? t('signIn.signUpLink') }));
|
|
513
|
-
}
|
|
514
|
-
/** Clerk-parity ergonomic: a button that signs the user out, then optionally redirects. */
|
|
515
|
-
function SignOutButton({ redirectUrl, children, className, }) {
|
|
516
|
-
const { signOut } = (0, hooks_1.useAuth)();
|
|
517
|
-
const t = useText();
|
|
518
|
-
return ((0, jsx_runtime_1.jsx)("button", { type: "button", className: className, onClick: () => {
|
|
519
|
-
void signOut().then(() => {
|
|
520
|
-
if (redirectUrl)
|
|
521
|
-
navigateTo(redirectUrl);
|
|
522
|
-
});
|
|
523
|
-
}, children: children ?? t('userButton.signOut') }));
|
|
524
|
-
}
|
|
525
|
-
/** Declarative "render this to send the user to sign-in" (Clerk `<RedirectToSignIn>`). */
|
|
526
|
-
function RedirectToSignIn({ url = '/sign-in', redirectUrl }) {
|
|
527
|
-
(0, react_1.useEffect)(() => navigateTo(withRedirect(url, redirectUrl), true), [url, redirectUrl]);
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
/** Declarative "render this to send the user to sign-up" (Clerk `<RedirectToSignUp>`). */
|
|
531
|
-
function RedirectToSignUp({ url = '/sign-up', redirectUrl }) {
|
|
532
|
-
(0, react_1.useEffect)(() => navigateTo(withRedirect(url, redirectUrl), true), [url, redirectUrl]);
|
|
533
|
-
return null;
|
|
534
|
-
}
|
|
535
|
-
/**
|
|
536
|
-
* §L1 (Clerk `<MultisessionAppSupport>`). Re-mounts its subtree whenever the
|
|
537
|
-
* ACTIVE account changes, so per-user state held in component state or memo
|
|
538
|
-
* caches is discarded on a `setActive({ session })` switch rather than bleeding
|
|
539
|
-
* one account's data into the next. Keying on the session id (plus a stable key
|
|
540
|
-
* while signed out) is the whole mechanism — React unmounts the old tree and
|
|
541
|
-
* mounts a fresh one when the key changes.
|
|
542
|
-
*/
|
|
543
|
-
function MultisessionAppSupport({ children }) {
|
|
544
|
-
const { session } = (0, hooks_1.useSession)();
|
|
545
|
-
return (0, jsx_runtime_1.jsx)("div", { "data-atlas": "multisession-root", children: children }, session?.id ?? 'signed-out');
|
|
546
|
-
}
|
|
547
|
-
/**
|
|
548
|
-
* The signed-in user's avatar (image, or initials fallback) — the standalone
|
|
549
|
-
* renderer `<UserButton>` uses internally, exported for reuse (Clerk `<UserAvatar>`).
|
|
550
|
-
*/
|
|
551
|
-
function UserAvatar({ size = 32, className }) {
|
|
552
|
-
const { user } = (0, hooks_1.useUser)();
|
|
553
|
-
const avatarClass = useClass('avatar', 'atlas-avatar');
|
|
554
|
-
if (!user)
|
|
555
|
-
return null;
|
|
556
|
-
const label = [user.first_name, user.last_name].filter(Boolean).join(' ') || user.username || '';
|
|
557
|
-
const style = { width: size, height: size };
|
|
558
|
-
return ((0, jsx_runtime_1.jsx)("span", { className: className ?? avatarClass, style: style, "data-atlas": "user-avatar", children: user.image_url ? (0, jsx_runtime_1.jsx)("img", { alt: "", src: user.image_url, width: size, height: size }) : (0, jsx_runtime_1.jsx)("span", { children: label.slice(0, 1).toUpperCase() }) }));
|
|
559
|
-
}
|
|
560
|
-
/**
|
|
561
|
-
* §5.1 headless sign-up — the sign-up counterpart of {@link useSignIn}. Starts a
|
|
562
|
-
* sign-up attempt and returns the resulting flow (usually `needs_email_verification`;
|
|
563
|
-
* the shared `advance` then drives the code to completion), reloading the provider
|
|
564
|
-
* when a sign-up completes with a session ticket.
|
|
565
|
-
*/
|
|
566
|
-
function useSignUp() {
|
|
567
|
-
const { publishableKey, frontendApi, reload } = (0, AtlasProvider_1.useAtlas)();
|
|
568
|
-
const client = (0, react_1.useMemo)(() => new fapi_1.FapiClient({ publishableKey, baseUrl: frontendApi }), [frontendApi, publishableKey]);
|
|
569
|
-
const create = (0, react_1.useCallback)(async (input) => {
|
|
570
|
-
const next = await (0, js_1.startSignUp)(client, input);
|
|
571
|
-
if (next.attempt?.status === 'complete' && next.ticket) {
|
|
572
|
-
await client.post('/v1/client/tickets/exchange', {
|
|
573
|
-
attempt_id: next.attempt.id,
|
|
574
|
-
ticket: next.ticket,
|
|
575
|
-
});
|
|
576
|
-
await reload();
|
|
577
|
-
}
|
|
578
|
-
return next;
|
|
579
|
-
}, [client, reload]);
|
|
580
|
-
return { create };
|
|
581
|
-
}
|
|
582
|
-
function OrganizationSwitcher() {
|
|
583
|
-
const { memberships, organization, setActive } = (0, hooks_1.useOrganization)();
|
|
584
|
-
const t = useText();
|
|
585
|
-
const menuClass = useClass('menu', 'atlas-menu');
|
|
586
|
-
const itemClass = useClass('menuItem', 'atlas-menu-item');
|
|
587
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: menuClass, children: [(0, jsx_runtime_1.jsx)("button", { className: itemClass, onClick: () => void setActive(null), children: t('organization.personal') }), memberships.map((membership) => ((0, jsx_runtime_1.jsx)("button", { className: itemClass, disabled: membership.organization.id === organization?.id, onClick: () => void setActive(membership.organization.id), children: membership.organization.name }, membership.organization.id)))] }));
|
|
588
|
-
}
|
|
589
|
-
/**
|
|
590
|
-
* Clerk-parity `useOrganizationList()`. The memberships the signed-in user
|
|
591
|
-
* belongs to, plus a `createOrganization(name)` that creates one through FAPI and
|
|
592
|
-
* re-boots the provider so the new membership appears everywhere. `memberships`
|
|
593
|
-
* comes straight from {@link useOrganization} (the same list `<OrganizationSwitcher>`
|
|
594
|
-
* renders), so there is one source of truth for what the user belongs to.
|
|
595
|
-
*/
|
|
596
|
-
function useOrganizationList() {
|
|
597
|
-
const { memberships } = (0, hooks_1.useOrganization)();
|
|
598
|
-
const { reload } = (0, AtlasProvider_1.useAtlas)();
|
|
599
|
-
const client = useMeClient();
|
|
600
|
-
const createOrganization = (0, react_1.useCallback)(async (name) => {
|
|
601
|
-
const res = await client.post('/v1/client/organizations', {
|
|
602
|
-
name,
|
|
603
|
-
});
|
|
604
|
-
// Re-boot so the new membership lands in `memberships` for the whole app,
|
|
605
|
-
// not just this hook's caller.
|
|
606
|
-
await reload();
|
|
607
|
-
return res.data;
|
|
608
|
-
}, [client, reload]);
|
|
609
|
-
return { memberships, createOrganization };
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Clerk-parity `<CreateOrganization>`. A small name-and-submit form that creates
|
|
613
|
-
* an organization via {@link useOrganizationList} and immediately makes it the
|
|
614
|
-
* active org, so the user drops straight into the org they just made. Styled like
|
|
615
|
-
* the `<SignUp>` card. `onCreate` fires with the created org for a caller that
|
|
616
|
-
* wants to route afterwards.
|
|
617
|
-
*/
|
|
618
|
-
function CreateOrganization({ onCreate, } = {}) {
|
|
619
|
-
const { createOrganization } = useOrganizationList();
|
|
620
|
-
const { setActive } = (0, hooks_1.useOrganization)();
|
|
621
|
-
const t = useText();
|
|
622
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
623
|
-
const titleClass = useClass('headerTitle', 'atlas-title');
|
|
624
|
-
const inputClass = useClass('formFieldInput', 'atlas-input');
|
|
625
|
-
const buttonClass = useClass('formButtonPrimary', 'atlas-button');
|
|
626
|
-
const errorClass = useClass('formFieldError', 'atlas-error');
|
|
627
|
-
const [name, setName] = (0, react_1.useState)('');
|
|
628
|
-
const [busy, setBusy] = (0, react_1.useState)(false);
|
|
629
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
630
|
-
const submit = async () => {
|
|
631
|
-
const trimmed = name.trim();
|
|
632
|
-
if (!trimmed)
|
|
633
|
-
return;
|
|
634
|
-
setBusy(true);
|
|
635
|
-
setError(null);
|
|
636
|
-
try {
|
|
637
|
-
const org = await createOrganization(trimmed);
|
|
638
|
-
if (!org) {
|
|
639
|
-
setError(t('error.generic'));
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
await setActive(org.id);
|
|
643
|
-
setName('');
|
|
644
|
-
onCreate?.(org);
|
|
645
|
-
}
|
|
646
|
-
catch {
|
|
647
|
-
setError(t('error.generic'));
|
|
648
|
-
}
|
|
649
|
-
finally {
|
|
650
|
-
setBusy(false);
|
|
651
|
-
}
|
|
652
|
-
};
|
|
653
|
-
return ((0, jsx_runtime_1.jsxs)("form", { className: cardClass, "data-atlas": "create-organization", onSubmit: (event) => {
|
|
654
|
-
event.preventDefault();
|
|
655
|
-
void submit();
|
|
656
|
-
}, children: [(0, jsx_runtime_1.jsx)("h2", { className: titleClass, children: t('organization.createTitle') }), (0, jsx_runtime_1.jsxs)("label", { className: "atlas-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('organization.nameLabel') }), (0, jsx_runtime_1.jsx)("input", { className: inputClass, value: name, onChange: (event) => setName(event.target.value) })] }), error ? ((0, jsx_runtime_1.jsx)("p", { className: errorClass, role: "alert", children: error })) : null, (0, jsx_runtime_1.jsx)("button", { className: buttonClass, type: "submit", disabled: busy || !name.trim(), children: t('organization.create') })] }));
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Clerk-parity `<OrganizationList>`. Every organization the user belongs to as a
|
|
660
|
-
* button that switches to it (mirroring `<OrganizationSwitcher>`'s markup), plus
|
|
661
|
-
* an inline `<CreateOrganization>` so a user with no orgs — or who wants another —
|
|
662
|
-
* can make one without leaving the list.
|
|
663
|
-
*/
|
|
664
|
-
function OrganizationList() {
|
|
665
|
-
const { memberships, organization, setActive } = (0, hooks_1.useOrganization)();
|
|
666
|
-
const t = useText();
|
|
667
|
-
const menuClass = useClass('menu', 'atlas-menu');
|
|
668
|
-
const itemClass = useClass('menuItem', 'atlas-menu-item');
|
|
669
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: menuClass, "data-atlas": "organization-list", children: [memberships.map((membership) => ((0, jsx_runtime_1.jsx)("button", { className: itemClass, disabled: membership.organization.id === organization?.id, onClick: () => void setActive(membership.organization.id), children: membership.organization.name }, membership.organization.id))), (0, jsx_runtime_1.jsx)("span", { className: itemClass, "data-atlas": "organization-list-divider", children: t('organization.orCreate') }), (0, jsx_runtime_1.jsx)(CreateOrganization, {})] }));
|
|
670
|
-
}
|
|
671
|
-
/** §2 A FapiClient bound to this instance, for the `/v1/client/me` + session
|
|
672
|
-
* account-management calls (all cookie-authenticated, credentials:'include'). */
|
|
673
|
-
function useMeClient() {
|
|
674
|
-
const { publishableKey, frontendApi } = (0, AtlasProvider_1.useAtlas)();
|
|
675
|
-
return (0, react_1.useMemo)(() => new fapi_1.FapiClient({ publishableKey, baseUrl: frontendApi }), [publishableKey, frontendApi]);
|
|
676
|
-
}
|
|
677
|
-
/**
|
|
678
|
-
* §2 The signed-in user's active devices, with per-device and "sign out everyone
|
|
679
|
-
* else" revocation. A drop-in for a settings page — the composable half of the
|
|
680
|
-
* post-login account surface an integrator needs the moment login is Atlas.
|
|
681
|
-
*/
|
|
682
|
-
function ActiveSessions() {
|
|
683
|
-
const client = useMeClient();
|
|
684
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
685
|
-
const [sessions, setSessions] = (0, react_1.useState)([]);
|
|
686
|
-
const [busy, setBusy] = (0, react_1.useState)(false);
|
|
687
|
-
const load = (0, react_1.useCallback)(async () => {
|
|
688
|
-
const res = await client.get('/v1/client/sessions');
|
|
689
|
-
setSessions(res.data?.data ?? []);
|
|
690
|
-
}, [client]);
|
|
691
|
-
(0, react_1.useEffect)(() => {
|
|
692
|
-
void load();
|
|
693
|
-
}, [load]);
|
|
694
|
-
const revoke = (0, react_1.useCallback)(async (id) => {
|
|
695
|
-
setBusy(true);
|
|
696
|
-
await client.post(`/v1/client/sessions/${id}/revoke`).catch(() => undefined);
|
|
697
|
-
await load();
|
|
698
|
-
setBusy(false);
|
|
699
|
-
}, [client, load]);
|
|
700
|
-
const revokeOthers = (0, react_1.useCallback)(async () => {
|
|
701
|
-
setBusy(true);
|
|
702
|
-
await client.post('/v1/client/sessions/revoke_all').catch(() => undefined);
|
|
703
|
-
await load();
|
|
704
|
-
setBusy(false);
|
|
705
|
-
}, [client, load]);
|
|
706
|
-
const fmt = (ms) => new Date(ms).toLocaleString();
|
|
707
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className: cardClass, "data-atlas": "active-sessions", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Active devices" }), (0, jsx_runtime_1.jsx)("ul", { children: sessions.map((s) => ((0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: s.device_label ?? [s.browser, s.os].filter(Boolean).join(' on ') ?? 'Unknown device' }), s.current ? (0, jsx_runtime_1.jsx)("em", { children: " \u2014 this device" }) : null, (0, jsx_runtime_1.jsx)("div", { children: [s.location, fmt(s.last_active_at)].filter(Boolean).join(' · ') }), !s.current ? ((0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy, onClick: () => void revoke(s.id), children: "Sign out" })) : null] }, s.id))) }), sessions.some((s) => !s.current) ? ((0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy, onClick: () => void revokeOthers(), children: "Sign out of all other devices" })) : null] }));
|
|
708
|
-
}
|
|
709
|
-
/**
|
|
710
|
-
* §2 The signed-in user's profile — name (editable) and email addresses. Another
|
|
711
|
-
* composable piece of the account surface; writes go to `PATCH /v1/client/me` and
|
|
712
|
-
* re-boot the provider so the rest of the app sees the change.
|
|
713
|
-
*/
|
|
714
|
-
function ProfileCard() {
|
|
715
|
-
const { user } = (0, hooks_1.useUser)();
|
|
716
|
-
const { reload } = (0, AtlasProvider_1.useAtlas)();
|
|
717
|
-
const client = useMeClient();
|
|
718
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
719
|
-
const [firstName, setFirstName] = (0, react_1.useState)('');
|
|
720
|
-
const [lastName, setLastName] = (0, react_1.useState)('');
|
|
721
|
-
const [saving, setSaving] = (0, react_1.useState)(false);
|
|
722
|
-
(0, react_1.useEffect)(() => {
|
|
723
|
-
setFirstName(user?.first_name ?? '');
|
|
724
|
-
setLastName(user?.last_name ?? '');
|
|
725
|
-
}, [user?.first_name, user?.last_name]);
|
|
726
|
-
if (!user)
|
|
727
|
-
return null;
|
|
728
|
-
const save = async () => {
|
|
729
|
-
setSaving(true);
|
|
730
|
-
await client
|
|
731
|
-
.request('/v1/client/me', {
|
|
732
|
-
method: 'PATCH',
|
|
733
|
-
body: JSON.stringify({ first_name: firstName, last_name: lastName }),
|
|
734
|
-
})
|
|
735
|
-
.catch(() => undefined);
|
|
736
|
-
await reload();
|
|
737
|
-
setSaving(false);
|
|
738
|
-
};
|
|
739
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className: cardClass, "data-atlas": "profile-card", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Profile" }), (0, jsx_runtime_1.jsxs)("label", { children: ["First name", (0, jsx_runtime_1.jsx)("input", { value: firstName, onChange: (e) => setFirstName(e.target.value) })] }), (0, jsx_runtime_1.jsxs)("label", { children: ["Last name", (0, jsx_runtime_1.jsx)("input", { value: lastName, onChange: (e) => setLastName(e.target.value) })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: saving, onClick: () => void save(), children: "Save" }), (0, jsx_runtime_1.jsx)("ul", { "data-atlas": "emails", children: (user.email_addresses ?? []).map((email) => ((0, jsx_runtime_1.jsxs)("li", { children: [email.email_address, email.verified ? null : (0, jsx_runtime_1.jsx)("em", { children: " \u2014 unverified" }), email.primary ? (0, jsx_runtime_1.jsx)("em", { children: " \u2014 primary" }) : null] }, email.id))) })] }));
|
|
740
|
-
}
|
|
741
|
-
/**
|
|
742
|
-
* §2 Manage two-factor authentication (authenticator app / TOTP). The full
|
|
743
|
-
* enrollment ceremony — start (secret + otpauth URI), verify with a live code,
|
|
744
|
-
* then the one-time backup codes — plus turn-off, in one composable drop-in, so a
|
|
745
|
-
* team never rebuilds the FAPI dance to put 2FA in their own settings page. The
|
|
746
|
-
* secret + `otpauth://` URI are shown for scan-or-manual entry (render a QR from
|
|
747
|
-
* the URI if you like — kept dependency-free here).
|
|
748
|
-
*/
|
|
749
|
-
function ManageMfa() {
|
|
750
|
-
const client = useMeClient();
|
|
751
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
752
|
-
const [factors, setFactors] = (0, react_1.useState)([]);
|
|
753
|
-
const [remaining, setRemaining] = (0, react_1.useState)(null);
|
|
754
|
-
const [enroll, setEnroll] = (0, react_1.useState)(null);
|
|
755
|
-
const [codes, setCodes] = (0, react_1.useState)([]);
|
|
756
|
-
const [backupCodes, setBackupCodes] = (0, react_1.useState)(null);
|
|
757
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
758
|
-
const [busy, setBusy] = (0, react_1.useState)(false);
|
|
759
|
-
const load = (0, react_1.useCallback)(async () => {
|
|
760
|
-
const res = await client.get('/v1/client/me/mfa');
|
|
761
|
-
setFactors(res.data?.data ?? []);
|
|
762
|
-
setRemaining(res.data?.backup_codes_remaining ?? null);
|
|
763
|
-
}, [client]);
|
|
764
|
-
(0, react_1.useEffect)(() => {
|
|
765
|
-
void load();
|
|
766
|
-
}, [load]);
|
|
767
|
-
const totp = factors.find((f) => f.type === 'totp' && f.verified);
|
|
768
|
-
const startEnroll = async () => {
|
|
769
|
-
setError(null);
|
|
770
|
-
setBackupCodes(null);
|
|
771
|
-
setBusy(true);
|
|
772
|
-
const res = await client.post('/v1/client/me/mfa/totp');
|
|
773
|
-
setBusy(false);
|
|
774
|
-
if (!res.ok || !res.data) {
|
|
775
|
-
setError(res.errors[0]?.message ?? 'Could not start enrollment.');
|
|
776
|
-
return;
|
|
777
|
-
}
|
|
778
|
-
const required = res.data.required_codes || 1;
|
|
779
|
-
setEnroll({ id: res.data.id, secret: res.data.secret, uri: res.data.uri, requiredCodes: required });
|
|
780
|
-
setCodes(Array.from({ length: required }, () => ''));
|
|
781
|
-
};
|
|
782
|
-
const verify = async () => {
|
|
783
|
-
if (!enroll)
|
|
784
|
-
return;
|
|
785
|
-
setError(null);
|
|
786
|
-
setBusy(true);
|
|
787
|
-
const res = await client.post(`/v1/client/me/mfa/totp/${enroll.id}/verify`, { codes });
|
|
788
|
-
setBusy(false);
|
|
789
|
-
if (!res.ok) {
|
|
790
|
-
setError(res.errors[0]?.message ?? 'That code was not accepted.');
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
setBackupCodes(res.data?.backup_codes ?? []);
|
|
794
|
-
setEnroll(null);
|
|
795
|
-
await load();
|
|
796
|
-
};
|
|
797
|
-
const disable = async (id) => {
|
|
798
|
-
setBusy(true);
|
|
799
|
-
await client.request(`/v1/client/me/mfa/${id}`, { method: 'DELETE' }).catch(() => undefined);
|
|
800
|
-
await load();
|
|
801
|
-
setBusy(false);
|
|
802
|
-
};
|
|
803
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className: cardClass, "data-atlas": "manage-mfa", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Two-factor authentication" }), totp ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("p", { children: ["Authenticator app is on.", remaining != null ? ` ${remaining} backup codes remaining.` : ''] }), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy, onClick: () => void disable(totp.id), children: "Turn off" })] })) : enroll ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { children: "Add this to your authenticator app \u2014 scan the QR you render from the URI, or enter the key:" }), (0, jsx_runtime_1.jsx)("code", { "data-atlas": "totp-secret", children: enroll.secret }), (0, jsx_runtime_1.jsx)("p", { children: (0, jsx_runtime_1.jsx)("a", { href: enroll.uri, children: "Open in an authenticator app" }) }), codes.map((c, i) => ((0, jsx_runtime_1.jsx)("input", { inputMode: "numeric", autoComplete: "one-time-code", value: c, placeholder: enroll.requiredCodes > 1 ? `Code ${i + 1}` : 'Enter the 6-digit code', onChange: (e) => setCodes((prev) => prev.map((v, j) => (j === i ? e.target.value : v))) }, i))), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy, onClick: () => void verify(), children: "Verify & enable" })] })) : backupCodes ? ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("p", { children: "Two-factor is on. Save these backup codes now \u2014 they are shown only once:" }), (0, jsx_runtime_1.jsx)("ul", { "data-atlas": "backup-codes", children: backupCodes.map((c) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("code", { children: c }) }, c))) })] })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy, onClick: () => void startEnroll(), children: "Enable authenticator app" })), error ? ((0, jsx_runtime_1.jsx)("p", { "data-atlas": "mfa-error", role: "alert", children: error })) : null] }));
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* §2 Manage passkeys (WebAuthn). Lists the user's registered credentials and runs
|
|
807
|
-
* the full add ceremony — `POST /passkeys/begin` → `navigator.credentials.create`
|
|
808
|
-
* (via the SDK's {@link createPasskey}) → `POST /passkeys/finish` — plus rename-free
|
|
809
|
-
* removal, in one composable drop-in. Nothing renders the "Add" affordance when the
|
|
810
|
-
* runtime has no WebAuthn support, so it degrades cleanly on older browsers.
|
|
811
|
-
*/
|
|
812
|
-
function ManagePasskeys() {
|
|
813
|
-
const client = useMeClient();
|
|
814
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
815
|
-
const [passkeys, setPasskeys] = (0, react_1.useState)([]);
|
|
816
|
-
const [name, setName] = (0, react_1.useState)('');
|
|
817
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
818
|
-
const [busy, setBusy] = (0, react_1.useState)(false);
|
|
819
|
-
const supported = (0, js_1.passkeysSupported)();
|
|
820
|
-
const load = (0, react_1.useCallback)(async () => {
|
|
821
|
-
const res = await client.get('/v1/client/me/passkeys');
|
|
822
|
-
setPasskeys(res.data?.data ?? []);
|
|
823
|
-
}, [client]);
|
|
824
|
-
(0, react_1.useEffect)(() => {
|
|
825
|
-
void load();
|
|
826
|
-
}, [load]);
|
|
827
|
-
const add = async () => {
|
|
828
|
-
setError(null);
|
|
829
|
-
setBusy(true);
|
|
830
|
-
try {
|
|
831
|
-
const begin = await client.post('/v1/client/me/passkeys/begin');
|
|
832
|
-
if (!begin.ok || !begin.data) {
|
|
833
|
-
setError(begin.errors[0]?.message ?? 'Could not start passkey setup.');
|
|
834
|
-
return;
|
|
835
|
-
}
|
|
836
|
-
const body = await (0, js_1.createPasskey)(begin.data, { name: name.trim() || undefined });
|
|
837
|
-
const finish = await client.post('/v1/client/me/passkeys/finish', body);
|
|
838
|
-
if (!finish.ok) {
|
|
839
|
-
setError(finish.errors[0]?.message ?? 'Could not save the passkey.');
|
|
840
|
-
return;
|
|
841
|
-
}
|
|
842
|
-
setName('');
|
|
843
|
-
await load();
|
|
844
|
-
}
|
|
845
|
-
catch {
|
|
846
|
-
// A cancelled or timed-out WebAuthn prompt throws — treat as a no-op, not an error banner.
|
|
847
|
-
}
|
|
848
|
-
finally {
|
|
849
|
-
setBusy(false);
|
|
850
|
-
}
|
|
851
|
-
};
|
|
852
|
-
const remove = async (id) => {
|
|
853
|
-
setBusy(true);
|
|
854
|
-
await client.request(`/v1/client/me/passkeys/${id}`, { method: 'DELETE' }).catch(() => undefined);
|
|
855
|
-
await load();
|
|
856
|
-
setBusy(false);
|
|
857
|
-
};
|
|
858
|
-
const fmt = (ms) => (ms ? new Date(ms).toLocaleDateString() : null);
|
|
859
|
-
return ((0, jsx_runtime_1.jsxs)("section", { className: cardClass, "data-atlas": "manage-passkeys", children: [(0, jsx_runtime_1.jsx)("h2", { children: "Passkeys" }), (0, jsx_runtime_1.jsx)("ul", { "data-atlas": "passkey-list", children: passkeys.map((pk) => ((0, jsx_runtime_1.jsxs)("li", { children: [(0, jsx_runtime_1.jsx)("strong", { children: pk.name || 'Passkey' }), (0, jsx_runtime_1.jsx)("div", { children: [pk.last_used_at ? `Last used ${fmt(pk.last_used_at)}` : `Added ${fmt(pk.created_at)}`]
|
|
860
|
-
.filter(Boolean)
|
|
861
|
-
.join(' · ') }), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy, onClick: () => void remove(pk.id), children: "Remove" })] }, pk.id))) }), supported ? ((0, jsx_runtime_1.jsxs)("div", { "data-atlas": "passkey-add", children: [(0, jsx_runtime_1.jsx)("input", { value: name, placeholder: "Name this device (optional)", onChange: (e) => setName(e.target.value) }), (0, jsx_runtime_1.jsx)("button", { type: "button", disabled: busy, onClick: () => void add(), children: "Add a passkey" })] })) : ((0, jsx_runtime_1.jsx)("p", { children: "Passkeys are not supported in this browser." })), error ? ((0, jsx_runtime_1.jsx)("p", { "data-atlas": "passkey-error", role: "alert", children: error })) : null] }));
|
|
862
|
-
}
|
|
863
|
-
/** A custom content tab inside `<UserProfile>`. Renders nothing on its own. */
|
|
864
|
-
function UserProfilePage(_props) {
|
|
865
|
-
return null;
|
|
866
|
-
}
|
|
867
|
-
/** A custom external-link tab inside `<UserProfile>`. Renders nothing on its own. */
|
|
868
|
-
function UserProfileLink(_props) {
|
|
869
|
-
return null;
|
|
870
|
-
}
|
|
871
|
-
function UserProfile({ children } = {}) {
|
|
872
|
-
const { user } = (0, hooks_1.useUser)();
|
|
873
|
-
const t = useText();
|
|
874
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
875
|
-
const tabClass = useClass('menuItem', 'atlas-tab');
|
|
876
|
-
const pages = (0, react_1.useMemo)(() => collectByType(children, (el) => el.type === UserProfilePage ? el.props : undefined), [children]);
|
|
877
|
-
const links = (0, react_1.useMemo)(() => collectByType(children, (el) => el.type === UserProfileLink ? el.props : undefined), [children]);
|
|
878
|
-
// 0 = the built-in account page; 1..N = custom pages.
|
|
879
|
-
const [tab, setTab] = (0, react_1.useState)(0);
|
|
880
|
-
if (!user)
|
|
881
|
-
return null;
|
|
882
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, "data-atlas": "user-profile", children: [(0, jsx_runtime_1.jsx)("h1", { children: t('userProfile.title') }), pages.length || links.length ? ((0, jsx_runtime_1.jsxs)("nav", { className: "atlas-tabs", role: "tablist", children: [(0, jsx_runtime_1.jsx)("button", { className: tabClass, role: "tab", "aria-selected": tab === 0, onClick: () => setTab(0), children: t('userProfile.title') }), pages.map((p, i) => ((0, jsx_runtime_1.jsxs)("button", { className: tabClass, role: "tab", "aria-selected": tab === i + 1, onClick: () => setTab(i + 1), children: [p.labelIcon, p.label] }, `p${i}`))), links.map((l, i) => ((0, jsx_runtime_1.jsxs)("a", { className: tabClass, href: l.url, children: [l.labelIcon, l.label] }, `l${i}`)))] })) : null, tab === 0 ? ((0, jsx_runtime_1.jsxs)("div", { "data-atlas": "user-profile-account", role: "tabpanel", children: [(0, jsx_runtime_1.jsx)(ProfileCard, {}), (0, jsx_runtime_1.jsx)(ManageMfa, {}), (0, jsx_runtime_1.jsx)(ManagePasskeys, {}), (0, jsx_runtime_1.jsx)(ActiveSessions, {})] })) : ((0, jsx_runtime_1.jsx)("div", { "data-atlas": "user-profile-custom", role: "tabpanel", children: pages[tab - 1]?.children }))] }));
|
|
883
|
-
}
|
|
884
|
-
UserProfile.Page = UserProfilePage;
|
|
885
|
-
UserProfile.Link = UserProfileLink;
|
|
886
|
-
function OrganizationProfile() {
|
|
887
|
-
const { organization, membership } = (0, hooks_1.useOrganization)();
|
|
888
|
-
const t = useText();
|
|
889
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
890
|
-
if (!organization)
|
|
891
|
-
return null;
|
|
892
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, children: [(0, jsx_runtime_1.jsx)("h1", { children: organization.name }), (0, jsx_runtime_1.jsxs)("p", { children: [t('organization.roleLabel'), ": ", membership?.role] }), (0, jsx_runtime_1.jsx)(Protect, { permission: "org:sys_memberships:manage", children: (0, jsx_runtime_1.jsx)("button", { children: t('organization.invite') }) })] }));
|
|
893
|
-
}
|
|
894
|
-
/**
|
|
895
|
-
* Clerk-parity `useWaitlist()`. Headless join: `join(email)` posts the address to
|
|
896
|
-
* the waitlist endpoint and hands back the raw {@link FapiClient} response, so a
|
|
897
|
-
* caller rendering its own form decides what to show on success vs. failure.
|
|
898
|
-
*/
|
|
899
|
-
function useWaitlist() {
|
|
900
|
-
const client = useMeClient();
|
|
901
|
-
const join = (0, react_1.useCallback)((email) => client.post('/v1/client/waitlist', { email_address: email }), [client]);
|
|
902
|
-
return { join };
|
|
903
|
-
}
|
|
904
|
-
/**
|
|
905
|
-
* Clerk-parity `<Waitlist>`. An email-and-submit card (styled like `<SignUp>`)
|
|
906
|
-
* that calls {@link useWaitlist}. A 200 swaps the form for a success message; any
|
|
907
|
-
* other response surfaces the server's message as a field error, so a bad or
|
|
908
|
-
* duplicate address is reported in place rather than silently swallowed.
|
|
909
|
-
*/
|
|
910
|
-
function Waitlist() {
|
|
911
|
-
const { join } = useWaitlist();
|
|
912
|
-
const t = useText();
|
|
913
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
914
|
-
const titleClass = useClass('headerTitle', 'atlas-title');
|
|
915
|
-
const inputClass = useClass('formFieldInput', 'atlas-input');
|
|
916
|
-
const buttonClass = useClass('formButtonPrimary', 'atlas-button');
|
|
917
|
-
const errorClass = useClass('formFieldError', 'atlas-error');
|
|
918
|
-
const [email, setEmail] = (0, react_1.useState)('');
|
|
919
|
-
const [busy, setBusy] = (0, react_1.useState)(false);
|
|
920
|
-
const [joined, setJoined] = (0, react_1.useState)(false);
|
|
921
|
-
const [error, setError] = (0, react_1.useState)(null);
|
|
922
|
-
const submit = async () => {
|
|
923
|
-
setBusy(true);
|
|
924
|
-
setError(null);
|
|
925
|
-
const res = await join(email);
|
|
926
|
-
setBusy(false);
|
|
927
|
-
if (res.ok) {
|
|
928
|
-
setJoined(true);
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
setError(res.errors[0]?.message ?? t('error.generic'));
|
|
932
|
-
};
|
|
933
|
-
return ((0, jsx_runtime_1.jsxs)("form", { className: cardClass, "data-atlas": "waitlist", onSubmit: (event) => {
|
|
934
|
-
event.preventDefault();
|
|
935
|
-
void submit();
|
|
936
|
-
}, children: [(0, jsx_runtime_1.jsx)("h1", { className: titleClass, children: t('waitlist.title') }), joined ? ((0, jsx_runtime_1.jsx)("p", { "data-atlas": "waitlist-success", children: t('waitlist.success') })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("label", { className: "atlas-field", children: [(0, jsx_runtime_1.jsx)("span", { children: t('waitlist.emailLabel') }), (0, jsx_runtime_1.jsx)("input", { className: inputClass, type: "email", value: email, onChange: (event) => setEmail(event.target.value) }), error ? ((0, jsx_runtime_1.jsx)("span", { className: errorClass, role: "alert", children: error })) : null] }), (0, jsx_runtime_1.jsx)("button", { className: buttonClass, type: "submit", disabled: busy, children: t('waitlist.submit') })] }))] }));
|
|
937
|
-
}
|
|
938
|
-
/**
|
|
939
|
-
* Clerk-parity `useReverification()`. A thin, headless wrapper over the EXISTING
|
|
940
|
-
* step-up path (`@atlasauth/js`'s {@link reauthenticate} → `POST /v1/client/me/reauthenticate`)
|
|
941
|
-
* — it invents no server endpoint.
|
|
6
|
+
* The implementations live in per-component sibling files (`sign-in.tsx`,
|
|
7
|
+
* `sign-up.tsx`, `user-button.tsx`, `organization.tsx`, `account.tsx`,
|
|
8
|
+
* `auth-buttons.tsx`, `control-components.tsx`, `billing.tsx`, `waitlist.tsx`,
|
|
9
|
+
* `reverification.tsx`, and the shared `flow.tsx` / `components-shared.tsx`).
|
|
10
|
+
* This module re-exports every one of them so the package's public API — what
|
|
11
|
+
* `index.ts` re-exports via `export * from './components'` — stays byte-for-byte
|
|
12
|
+
* identical after the split. Import sites (`@atlasauth/react`) are unaffected.
|
|
942
13
|
*
|
|
943
|
-
*
|
|
944
|
-
*
|
|
945
|
-
*
|
|
946
|
-
*
|
|
947
|
-
* which re-authenticates in place and retries the action ONCE — resolving the
|
|
948
|
-
* original `withReverification` promise with the retry's result. `cancelReverification`
|
|
949
|
-
* abandons the parked call. A wrong password throws out of `completeReverification`
|
|
950
|
-
* with the parked call intact, so the prompt can let the user try again.
|
|
951
|
-
*/
|
|
952
|
-
function useReverification() {
|
|
953
|
-
const { publishableKey, frontendApi } = (0, AtlasProvider_1.useAtlas)();
|
|
954
|
-
const [needsReverification, setNeedsReverification] = (0, react_1.useState)(false);
|
|
955
|
-
const pending = (0, react_1.useRef)(null);
|
|
956
|
-
// A STEP_UP_REQUIRED signal, whether it arrived as a thrown Error carrying a
|
|
957
|
-
// `.code`, or as a returned FapiClient response whose `errors[]` names it.
|
|
958
|
-
const isStepUp = (candidate) => {
|
|
959
|
-
if (!candidate || typeof candidate !== 'object')
|
|
960
|
-
return false;
|
|
961
|
-
if (candidate.code === 'STEP_UP_REQUIRED')
|
|
962
|
-
return true;
|
|
963
|
-
const errors = candidate.errors;
|
|
964
|
-
return Array.isArray(errors) && errors.some((error) => error?.code === 'STEP_UP_REQUIRED');
|
|
965
|
-
};
|
|
966
|
-
const park = (action) => new Promise((resolve, reject) => {
|
|
967
|
-
pending.current = { action, resolve: resolve, reject };
|
|
968
|
-
setNeedsReverification(true);
|
|
969
|
-
});
|
|
970
|
-
const withReverification = (0, react_1.useCallback)(async (action) => {
|
|
971
|
-
try {
|
|
972
|
-
const result = await action();
|
|
973
|
-
return isStepUp(result) ? await park(action) : result;
|
|
974
|
-
}
|
|
975
|
-
catch (err) {
|
|
976
|
-
if (isStepUp(err))
|
|
977
|
-
return await park(action);
|
|
978
|
-
throw err;
|
|
979
|
-
}
|
|
980
|
-
}, []);
|
|
981
|
-
const completeReverification = (0, react_1.useCallback)(async (input) => {
|
|
982
|
-
// Throws on a bad credential; the parked call is left intact so the prompt
|
|
983
|
-
// can retry, and we neither clear it nor drop the flag until this resolves.
|
|
984
|
-
await (0, js_1.reauthenticate)(input, { api: frontendApi, publishableKey });
|
|
985
|
-
const job = pending.current;
|
|
986
|
-
pending.current = null;
|
|
987
|
-
setNeedsReverification(false);
|
|
988
|
-
if (!job)
|
|
989
|
-
return;
|
|
990
|
-
try {
|
|
991
|
-
job.resolve(await job.action());
|
|
992
|
-
}
|
|
993
|
-
catch (err) {
|
|
994
|
-
job.reject(err);
|
|
995
|
-
}
|
|
996
|
-
}, [frontendApi, publishableKey]);
|
|
997
|
-
const cancelReverification = (0, react_1.useCallback)(() => {
|
|
998
|
-
const job = pending.current;
|
|
999
|
-
pending.current = null;
|
|
1000
|
-
setNeedsReverification(false);
|
|
1001
|
-
job?.reject(new Error('Reverification cancelled.'));
|
|
1002
|
-
}, []);
|
|
1003
|
-
return { withReverification, needsReverification, completeReverification, cancelReverification };
|
|
1004
|
-
}
|
|
1005
|
-
/** The current page URL, guarded for SSR. */
|
|
1006
|
-
function currentUrl() {
|
|
1007
|
-
return typeof window === 'undefined' ? undefined : window.location.href;
|
|
1008
|
-
}
|
|
1009
|
-
/**
|
|
1010
|
-
* §commerce headless billing (Clerk `useCheckout`/subscription hooks combined).
|
|
1011
|
-
* Loads the instance's plans and — when signed in — the subject's subscription,
|
|
1012
|
-
* and exposes `checkout()` and `openPortal()`. Pass `{ orgId }` to operate on an
|
|
1013
|
-
* organization the user administers instead of on the user.
|
|
1014
|
-
*/
|
|
1015
|
-
function useBilling(opts = {}) {
|
|
1016
|
-
const { status } = (0, AtlasProvider_1.useAtlas)();
|
|
1017
|
-
const client = useMeClient();
|
|
1018
|
-
const orgId = opts.orgId;
|
|
1019
|
-
const [plans, setPlans] = (0, react_1.useState)([]);
|
|
1020
|
-
const [subscription, setSubscription] = (0, react_1.useState)(null);
|
|
1021
|
-
const [invoices, setInvoices] = (0, react_1.useState)([]);
|
|
1022
|
-
const [paymentMethod, setPaymentMethod] = (0, react_1.useState)(null);
|
|
1023
|
-
const [isLoaded, setIsLoaded] = (0, react_1.useState)(false);
|
|
1024
|
-
const reload = (0, react_1.useCallback)(async () => {
|
|
1025
|
-
const orgQuery = orgId ? `?org_id=${encodeURIComponent(orgId)}` : '';
|
|
1026
|
-
const plansRes = await client.get('/v1/client/billing/plans');
|
|
1027
|
-
if (plansRes.ok && plansRes.data)
|
|
1028
|
-
setPlans(plansRes.data.data ?? []);
|
|
1029
|
-
// Subscription, invoices and payment method are subject-scoped — signed in only.
|
|
1030
|
-
if (status === 'signed_in') {
|
|
1031
|
-
const [subRes, invRes, pmRes] = await Promise.all([
|
|
1032
|
-
client.get(`/v1/client/billing/subscription${orgQuery}`),
|
|
1033
|
-
client.get(`/v1/client/billing/invoices${orgQuery}`),
|
|
1034
|
-
client.get(`/v1/client/billing/payment_method${orgQuery}`),
|
|
1035
|
-
]);
|
|
1036
|
-
if (subRes.ok)
|
|
1037
|
-
setSubscription(subRes.data ?? null);
|
|
1038
|
-
if (invRes.ok && invRes.data)
|
|
1039
|
-
setInvoices(invRes.data.data ?? []);
|
|
1040
|
-
if (pmRes.ok)
|
|
1041
|
-
setPaymentMethod(pmRes.data?.payment_method ?? null);
|
|
1042
|
-
}
|
|
1043
|
-
else {
|
|
1044
|
-
setSubscription(null);
|
|
1045
|
-
setInvoices([]);
|
|
1046
|
-
setPaymentMethod(null);
|
|
1047
|
-
}
|
|
1048
|
-
setIsLoaded(true);
|
|
1049
|
-
}, [client, status, orgId]);
|
|
1050
|
-
(0, react_1.useEffect)(() => {
|
|
1051
|
-
void reload();
|
|
1052
|
-
}, [reload]);
|
|
1053
|
-
const checkout = (0, react_1.useCallback)(async (params) => {
|
|
1054
|
-
const res = await client.post('/v1/client/billing/checkout', {
|
|
1055
|
-
plan_id: params.planId,
|
|
1056
|
-
org_id: params.orgId ?? orgId,
|
|
1057
|
-
success_url: params.successUrl ?? currentUrl(),
|
|
1058
|
-
cancel_url: params.cancelUrl ?? currentUrl(),
|
|
1059
|
-
});
|
|
1060
|
-
if (res.ok && res.data?.url && params.redirect !== false)
|
|
1061
|
-
navigateTo(res.data.url);
|
|
1062
|
-
return res;
|
|
1063
|
-
}, [client, orgId]);
|
|
1064
|
-
const openPortal = (0, react_1.useCallback)(async (params = {}) => {
|
|
1065
|
-
const res = await client.post('/v1/client/billing/portal', {
|
|
1066
|
-
org_id: params.orgId ?? orgId,
|
|
1067
|
-
return_url: params.returnUrl ?? currentUrl(),
|
|
1068
|
-
});
|
|
1069
|
-
if (res.ok && res.data?.url && params.redirect !== false)
|
|
1070
|
-
navigateTo(res.data.url);
|
|
1071
|
-
return res;
|
|
1072
|
-
}, [client, orgId]);
|
|
1073
|
-
return { isLoaded, plans, subscription, invoices, paymentMethod, checkout, openPortal, reload };
|
|
1074
|
-
}
|
|
1075
|
-
/** §commerce Clerk-parity alias — just the `checkout` action from {@link useBilling}. */
|
|
1076
|
-
function useCheckout(opts = {}) {
|
|
1077
|
-
const { checkout } = useBilling(opts);
|
|
1078
|
-
return { checkout };
|
|
1079
|
-
}
|
|
1080
|
-
/** §commerce Clerk-parity alias — the subject's subscription from {@link useBilling}. */
|
|
1081
|
-
function useSubscription(opts = {}) {
|
|
1082
|
-
const { isLoaded, subscription, openPortal, reload } = useBilling(opts);
|
|
1083
|
-
return { isLoaded, subscription, openPortal, reload };
|
|
1084
|
-
}
|
|
1085
|
-
/** §commerce Clerk-parity alias — the subject's invoices from {@link useBilling}. */
|
|
1086
|
-
function useInvoices(opts = {}) {
|
|
1087
|
-
const { isLoaded, invoices, reload } = useBilling(opts);
|
|
1088
|
-
return { isLoaded, invoices, reload };
|
|
1089
|
-
}
|
|
1090
|
-
/** §commerce Clerk-parity alias — the subject's default payment method + Portal to change it. */
|
|
1091
|
-
function usePaymentMethods(opts = {}) {
|
|
1092
|
-
const { isLoaded, paymentMethod, openPortal, reload } = useBilling(opts);
|
|
1093
|
-
return { isLoaded, paymentMethod, openPortal, reload };
|
|
1094
|
-
}
|
|
1095
|
-
/** Format a minor-unit amount (cents) as a currency string, best-effort. */
|
|
1096
|
-
function formatPrice(amount, currency) {
|
|
1097
|
-
try {
|
|
1098
|
-
return new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(amount / 100);
|
|
1099
|
-
}
|
|
1100
|
-
catch {
|
|
1101
|
-
return `${(amount / 100).toFixed(2)} ${currency.toUpperCase()}`;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
/**
|
|
1105
|
-
* §commerce (Clerk `<PricingTable>`). Renders the instance's plans as cards with
|
|
1106
|
-
* price + features and a subscribe button that starts a real Stripe Checkout.
|
|
1107
|
-
* The plan the subject is currently on is marked as current (its button is
|
|
1108
|
-
* disabled and an "Manage billing" action opens the Billing Portal instead).
|
|
1109
|
-
*/
|
|
1110
|
-
function PricingTable({ orgId } = {}) {
|
|
1111
|
-
const { isLoaded, plans, subscription, checkout, openPortal } = useBilling({ orgId });
|
|
1112
|
-
const { status } = (0, AtlasProvider_1.useAtlas)();
|
|
1113
|
-
const t = useText();
|
|
1114
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
1115
|
-
const buttonClass = useClass('formButtonPrimary', 'atlas-button');
|
|
1116
|
-
const [busyPlan, setBusyPlan] = (0, react_1.useState)(null);
|
|
1117
|
-
const currentPlanId = subscription?.plan?.id ?? null;
|
|
1118
|
-
if (!isLoaded)
|
|
1119
|
-
return (0, jsx_runtime_1.jsx)("div", { "data-atlas": "pricing-table", "aria-busy": "true" });
|
|
1120
|
-
return ((0, jsx_runtime_1.jsx)("div", { className: "atlas-pricing-table", "data-atlas": "pricing-table", children: plans.map((plan) => {
|
|
1121
|
-
const isCurrent = plan.id === currentPlanId;
|
|
1122
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, "data-atlas": "pricing-plan", "data-current": isCurrent, children: [(0, jsx_runtime_1.jsx)("h3", { children: plan.name }), (0, jsx_runtime_1.jsxs)("p", { className: "atlas-price", children: [formatPrice(plan.amount, plan.currency), (0, jsx_runtime_1.jsxs)("span", { className: "atlas-interval", children: [" / ", plan.interval] })] }), plan.features.length ? ((0, jsx_runtime_1.jsx)("ul", { children: plan.features.map((f, i) => ((0, jsx_runtime_1.jsx)("li", { children: f }, i))) })) : null, isCurrent ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonClass, "data-atlas": "manage-billing", onClick: () => void openPortal(), children: t('commerce.manage') })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonClass, disabled: status !== 'signed_in' || busyPlan === plan.id, onClick: () => {
|
|
1123
|
-
setBusyPlan(plan.id);
|
|
1124
|
-
void checkout({ planId: plan.id }).finally(() => setBusyPlan(null));
|
|
1125
|
-
}, children: t('commerce.subscribe') }))] }, plan.id));
|
|
1126
|
-
}) }));
|
|
1127
|
-
}
|
|
1128
|
-
/**
|
|
1129
|
-
* §commerce (Clerk `<Invoices>`). The subject's billing history — number, status,
|
|
1130
|
-
* amount, and a link to Stripe's hosted invoice / PDF. Renders nothing until
|
|
1131
|
-
* loaded, and an empty state when there are no invoices yet.
|
|
1132
|
-
*/
|
|
1133
|
-
function Invoices({ orgId } = {}) {
|
|
1134
|
-
const { isLoaded, invoices } = useInvoices({ orgId });
|
|
1135
|
-
const t = useText();
|
|
1136
|
-
const cardClass = useClass('card', 'atlas-card');
|
|
1137
|
-
if (!isLoaded)
|
|
1138
|
-
return (0, jsx_runtime_1.jsx)("div", { "data-atlas": "invoices", "aria-busy": "true" });
|
|
1139
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, "data-atlas": "invoices", children: [(0, jsx_runtime_1.jsx)("h3", { children: t('commerce.invoices') }), invoices.length === 0 ? ((0, jsx_runtime_1.jsx)("p", { "data-atlas": "invoices-empty", children: t('commerce.noInvoices') })) : ((0, jsx_runtime_1.jsx)("ul", { children: invoices.map((inv) => ((0, jsx_runtime_1.jsxs)("li", { "data-atlas": "invoice", "data-status": inv.status, children: [(0, jsx_runtime_1.jsx)("span", { children: inv.number ?? inv.id }), ' ', (0, jsx_runtime_1.jsx)("span", { children: formatPrice(inv.amount_paid || inv.amount_due, inv.currency) }), ' ', (0, jsx_runtime_1.jsx)("span", { children: inv.status }), ' ', inv.hosted_invoice_url ? ((0, jsx_runtime_1.jsx)("a", { href: inv.hosted_invoice_url, target: "_blank", rel: "noreferrer", children: t('commerce.viewInvoice') })) : null] }, inv.id))) }))] }));
|
|
1140
|
-
}
|
|
1141
|
-
/** Lazily inject Stripe.js once and resolve the global `Stripe` factory. */
|
|
1142
|
-
let stripeJsPromise = null;
|
|
1143
|
-
function loadStripeJs() {
|
|
1144
|
-
if (typeof window === 'undefined')
|
|
1145
|
-
return Promise.resolve(null);
|
|
1146
|
-
const w = window;
|
|
1147
|
-
if (w.Stripe)
|
|
1148
|
-
return Promise.resolve(w.Stripe);
|
|
1149
|
-
if (stripeJsPromise)
|
|
1150
|
-
return stripeJsPromise;
|
|
1151
|
-
stripeJsPromise = new Promise((resolve, reject) => {
|
|
1152
|
-
const script = document.createElement('script');
|
|
1153
|
-
script.src = 'https://js.stripe.com/v3';
|
|
1154
|
-
script.async = true;
|
|
1155
|
-
script.onload = () => resolve(window.Stripe ?? null);
|
|
1156
|
-
script.onerror = () => reject(new Error('Failed to load Stripe.js'));
|
|
1157
|
-
document.head.appendChild(script);
|
|
1158
|
-
});
|
|
1159
|
-
return stripeJsPromise;
|
|
1160
|
-
}
|
|
1161
|
-
/**
|
|
1162
|
-
* §commerce (Clerk `<EmbeddedCheckout>`). In-page checkout for a plan: mounts
|
|
1163
|
-
* Stripe's own Embedded Checkout iframe — Stripe.js owns the card fields, so no
|
|
1164
|
-
* PCI-scoped data ever touches Atlas. Fetches the instance's Stripe publishable
|
|
1165
|
-
* key from `/billing/config`; when embedded checkout is not configured it renders
|
|
1166
|
-
* `fallback` (default: a hosted-redirect button) rather than a broken frame.
|
|
14
|
+
* `<SignIn/>` and `<SignUp/>` are "full multi-step flows driven entirely by the
|
|
15
|
+
* server-side attempt status": no client here decides what comes next; each step
|
|
16
|
+
* renders whatever `status` the server returned, via the exhaustive mapping in
|
|
17
|
+
* @atlasauth/js. See `flow.tsx` for that shell.
|
|
1167
18
|
*/
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
}, [client, planId, orgId, returnUrl, t]);
|
|
1224
|
-
if (unavailable) {
|
|
1225
|
-
return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: fallback ?? ((0, jsx_runtime_1.jsx)("button", { type: "button", "data-atlas": "checkout-fallback", onClick: () => void checkout({ planId, orgId }), children: t('commerce.subscribe') })) }));
|
|
1226
|
-
}
|
|
1227
|
-
return ((0, jsx_runtime_1.jsxs)("div", { "data-atlas": "embedded-checkout", children: [error ? (0, jsx_runtime_1.jsx)("p", { role: "alert", children: error }) : null, (0, jsx_runtime_1.jsx)("div", { ref: containerRef })] }));
|
|
1228
|
-
}
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.EmbeddedCheckout = exports.Invoices = exports.PricingTable = exports.usePaymentMethods = exports.useInvoices = exports.useSubscription = exports.useCheckout = exports.useBilling = exports.useReverification = exports.Waitlist = exports.useWaitlist = exports.UserProfile = exports.ManagePasskeys = exports.ManageMfa = exports.ProfileCard = exports.ActiveSessions = exports.OrganizationProfile = exports.OrganizationList = exports.CreateOrganization = exports.useOrganizationList = exports.OrganizationSwitcher = exports.RedirectToSignUp = exports.RedirectToSignIn = exports.SignOutButton = exports.SignUpButton = exports.SignInButton = exports.UserAvatar = exports.UserButton = exports.useSignUp = exports.SignUp = exports.useSignIn = exports.SocialSignInButton = exports.SignIn = exports.MultisessionAppSupport = exports.Protect = exports.AtlasLoaded = exports.AtlasLoading = exports.SignedOut = exports.SignedIn = void 0;
|
|
21
|
+
// Control / conditional-rendering components.
|
|
22
|
+
var control_components_1 = require("./control-components");
|
|
23
|
+
Object.defineProperty(exports, "SignedIn", { enumerable: true, get: function () { return control_components_1.SignedIn; } });
|
|
24
|
+
Object.defineProperty(exports, "SignedOut", { enumerable: true, get: function () { return control_components_1.SignedOut; } });
|
|
25
|
+
Object.defineProperty(exports, "AtlasLoading", { enumerable: true, get: function () { return control_components_1.AtlasLoading; } });
|
|
26
|
+
Object.defineProperty(exports, "AtlasLoaded", { enumerable: true, get: function () { return control_components_1.AtlasLoaded; } });
|
|
27
|
+
Object.defineProperty(exports, "Protect", { enumerable: true, get: function () { return control_components_1.Protect; } });
|
|
28
|
+
Object.defineProperty(exports, "MultisessionAppSupport", { enumerable: true, get: function () { return control_components_1.MultisessionAppSupport; } });
|
|
29
|
+
var sign_in_1 = require("./sign-in");
|
|
30
|
+
Object.defineProperty(exports, "SignIn", { enumerable: true, get: function () { return sign_in_1.SignIn; } });
|
|
31
|
+
Object.defineProperty(exports, "SocialSignInButton", { enumerable: true, get: function () { return sign_in_1.SocialSignInButton; } });
|
|
32
|
+
Object.defineProperty(exports, "useSignIn", { enumerable: true, get: function () { return sign_in_1.useSignIn; } });
|
|
33
|
+
var sign_up_1 = require("./sign-up");
|
|
34
|
+
Object.defineProperty(exports, "SignUp", { enumerable: true, get: function () { return sign_up_1.SignUp; } });
|
|
35
|
+
Object.defineProperty(exports, "useSignUp", { enumerable: true, get: function () { return sign_up_1.useSignUp; } });
|
|
36
|
+
var user_button_1 = require("./user-button");
|
|
37
|
+
Object.defineProperty(exports, "UserButton", { enumerable: true, get: function () { return user_button_1.UserButton; } });
|
|
38
|
+
Object.defineProperty(exports, "UserAvatar", { enumerable: true, get: function () { return user_button_1.UserAvatar; } });
|
|
39
|
+
var auth_buttons_1 = require("./auth-buttons");
|
|
40
|
+
Object.defineProperty(exports, "SignInButton", { enumerable: true, get: function () { return auth_buttons_1.SignInButton; } });
|
|
41
|
+
Object.defineProperty(exports, "SignUpButton", { enumerable: true, get: function () { return auth_buttons_1.SignUpButton; } });
|
|
42
|
+
Object.defineProperty(exports, "SignOutButton", { enumerable: true, get: function () { return auth_buttons_1.SignOutButton; } });
|
|
43
|
+
Object.defineProperty(exports, "RedirectToSignIn", { enumerable: true, get: function () { return auth_buttons_1.RedirectToSignIn; } });
|
|
44
|
+
Object.defineProperty(exports, "RedirectToSignUp", { enumerable: true, get: function () { return auth_buttons_1.RedirectToSignUp; } });
|
|
45
|
+
// Organizations.
|
|
46
|
+
var organization_1 = require("./organization");
|
|
47
|
+
Object.defineProperty(exports, "OrganizationSwitcher", { enumerable: true, get: function () { return organization_1.OrganizationSwitcher; } });
|
|
48
|
+
Object.defineProperty(exports, "useOrganizationList", { enumerable: true, get: function () { return organization_1.useOrganizationList; } });
|
|
49
|
+
Object.defineProperty(exports, "CreateOrganization", { enumerable: true, get: function () { return organization_1.CreateOrganization; } });
|
|
50
|
+
Object.defineProperty(exports, "OrganizationList", { enumerable: true, get: function () { return organization_1.OrganizationList; } });
|
|
51
|
+
Object.defineProperty(exports, "OrganizationProfile", { enumerable: true, get: function () { return organization_1.OrganizationProfile; } });
|
|
52
|
+
var account_1 = require("./account");
|
|
53
|
+
Object.defineProperty(exports, "ActiveSessions", { enumerable: true, get: function () { return account_1.ActiveSessions; } });
|
|
54
|
+
Object.defineProperty(exports, "ProfileCard", { enumerable: true, get: function () { return account_1.ProfileCard; } });
|
|
55
|
+
Object.defineProperty(exports, "ManageMfa", { enumerable: true, get: function () { return account_1.ManageMfa; } });
|
|
56
|
+
Object.defineProperty(exports, "ManagePasskeys", { enumerable: true, get: function () { return account_1.ManagePasskeys; } });
|
|
57
|
+
Object.defineProperty(exports, "UserProfile", { enumerable: true, get: function () { return account_1.UserProfile; } });
|
|
58
|
+
// Waitlist.
|
|
59
|
+
var waitlist_1 = require("./waitlist");
|
|
60
|
+
Object.defineProperty(exports, "useWaitlist", { enumerable: true, get: function () { return waitlist_1.useWaitlist; } });
|
|
61
|
+
Object.defineProperty(exports, "Waitlist", { enumerable: true, get: function () { return waitlist_1.Waitlist; } });
|
|
62
|
+
// Step-up reverification.
|
|
63
|
+
var reverification_1 = require("./reverification");
|
|
64
|
+
Object.defineProperty(exports, "useReverification", { enumerable: true, get: function () { return reverification_1.useReverification; } });
|
|
65
|
+
var billing_1 = require("./billing");
|
|
66
|
+
Object.defineProperty(exports, "useBilling", { enumerable: true, get: function () { return billing_1.useBilling; } });
|
|
67
|
+
Object.defineProperty(exports, "useCheckout", { enumerable: true, get: function () { return billing_1.useCheckout; } });
|
|
68
|
+
Object.defineProperty(exports, "useSubscription", { enumerable: true, get: function () { return billing_1.useSubscription; } });
|
|
69
|
+
Object.defineProperty(exports, "useInvoices", { enumerable: true, get: function () { return billing_1.useInvoices; } });
|
|
70
|
+
Object.defineProperty(exports, "usePaymentMethods", { enumerable: true, get: function () { return billing_1.usePaymentMethods; } });
|
|
71
|
+
Object.defineProperty(exports, "PricingTable", { enumerable: true, get: function () { return billing_1.PricingTable; } });
|
|
72
|
+
Object.defineProperty(exports, "Invoices", { enumerable: true, get: function () { return billing_1.Invoices; } });
|
|
73
|
+
Object.defineProperty(exports, "EmbeddedCheckout", { enumerable: true, get: function () { return billing_1.EmbeddedCheckout; } });
|
|
1229
74
|
//# sourceMappingURL=components.js.map
|