@atlasauth/react 0.13.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/AtlasProvider.d.ts +8 -1
- package/dist/AtlasProvider.js +54 -4
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/account.d.ts +19 -1
- package/dist/account.js +121 -16
- package/dist/account.js.map +1 -1
- package/dist/active-atlas.d.ts +29 -0
- package/dist/active-atlas.js +40 -0
- package/dist/active-atlas.js.map +1 -0
- package/dist/billing.d.ts +55 -1
- package/dist/billing.js +115 -8
- package/dist/billing.js.map +1 -1
- package/dist/captcha.js +1 -1
- package/dist/captcha.js.map +1 -1
- package/dist/compat.d.ts +307 -0
- package/dist/compat.js +387 -0
- package/dist/compat.js.map +1 -0
- package/dist/components.d.ts +10 -4
- package/dist/components.js +15 -1
- package/dist/components.js.map +1 -1
- package/dist/device-authorization.d.ts +42 -0
- package/dist/device-authorization.js +137 -0
- package/dist/device-authorization.js.map +1 -0
- package/dist/flow.d.ts +33 -9
- package/dist/flow.js +130 -11
- package/dist/flow.js.map +1 -1
- package/dist/i18n.js +68 -0
- package/dist/i18n.js.map +1 -1
- package/dist/impersonation.d.ts +25 -0
- package/dist/impersonation.js +52 -0
- package/dist/impersonation.js.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mfa-steps.d.ts +53 -0
- package/dist/mfa-steps.js +148 -0
- package/dist/mfa-steps.js.map +1 -0
- package/dist/organization.d.ts +82 -1
- package/dist/organization.js +222 -4
- package/dist/organization.js.map +1 -1
- package/dist/reverification.d.ts +19 -0
- package/dist/reverification.js +49 -1
- package/dist/reverification.js.map +1 -1
- package/dist/sign-in.js +2 -2
- package/dist/sign-in.js.map +1 -1
- package/dist/sign-up.d.ts +6 -0
- package/dist/sign-up.js +69 -13
- package/dist/sign-up.js.map +1 -1
- package/dist/styles.js +8 -0
- package/dist/styles.js.map +1 -1
- package/dist/types.d.ts +52 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +15 -4
package/dist/index.js
CHANGED
|
@@ -72,4 +72,7 @@ var localizations_1 = require("@atlasauth/localizations");
|
|
|
72
72
|
Object.defineProperty(exports, "LOCALIZATION_KEYS", { enumerable: true, get: function () { return localizations_1.LOCALIZATION_KEYS; } });
|
|
73
73
|
Object.defineProperty(exports, "missingKeys", { enumerable: true, get: function () { return localizations_1.missingKeys; } });
|
|
74
74
|
Object.defineProperty(exports, "resolveLocale", { enumerable: true, get: function () { return localizations_1.resolveLocale; } });
|
|
75
|
+
// Drop-in compatibility helpers for apps migrated from other auth providers
|
|
76
|
+
// live on their own entry point, `@atlasauth/react/compat`, so the main API
|
|
77
|
+
// stays Atlas-named.
|
|
75
78
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,2CAAyB;AACzB,4CAA0B;AAC1B,yCAAuB;AACvB,0CAAwB;AAKxB,iDAA0D;AAAjD,8GAAA,aAAa,OAAA;AAAE,yGAAA,QAAQ,OAAA;AAEhC,iCASiB;AARf,wGAAA,eAAe,OAAA;AACf,gGAAA,OAAO,OAAA;AACP,wGAAA,eAAe,OAAA;AACf,mGAAA,UAAU,OAAA;AACV,uGAAA,cAAc,OAAA;AACd,2GAAA,kBAAkB,OAAA;AAClB,sGAAA,aAAa,OAAA;AACb,gGAAA,OAAO,OAAA;AAET,qCAAiE;AAAxD,0GAAA,eAAe,OAAA;AAAE,8GAAA,mBAAmB,OAAA;AAC7C,oFAAoF;AACpF,kCAAkC;AAClC,oCAA+D;AAAtD,oGAAA,cAAc,OAAA;AAAE,oGAAA,cAAc,OAAA;AAEvC,yCAAuB;AACvB,+CAA6B;AAE7B,2DAK8B;AAJ5B,4HAAA,sBAAsB,OAAA;AACtB,6HAAA,uBAAuB,OAAA;AACvB,0HAAA,oBAAoB,OAAA;AACpB,2HAAA,qBAAqB,OAAA;AAEvB,iFAAiF;AACjF,gDAAgD;AAChD,oCAAmE;AAA1D,kGAAA,YAAY,OAAA;AAAE,0GAAA,oBAAoB,OAAA;AAE3C,qFAAqF;AACrF,gFAAgF;AAChF,gFAAgF;AAChF,oCAAiD;AAAxC,sGAAA,gBAAgB,OAAA;AACzB,0EAA0E;AAC1E,4DAA4D;AAC5D,oCAAyF;AAAhF,sGAAA,gBAAgB,OAAA;AAAE,8GAAA,wBAAwB,OAAA;AAAE,kGAAA,YAAY,OAAA;AAEjE,qFAAqF;AACrF,+CAA+C;AAC/C,oCAAuD;AAA9C,4GAAA,sBAAsB,OAAA;AAE/B,4EAA4E;AAC5E,0DAAyF;AAAhF,kHAAA,iBAAiB,OAAA;AAAE,4GAAA,WAAW,OAAA;AAAE,8GAAA,aAAa,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,+CAA6B;AAC7B,2CAAyB;AACzB,4CAA0B;AAC1B,yCAAuB;AACvB,0CAAwB;AAKxB,iDAA0D;AAAjD,8GAAA,aAAa,OAAA;AAAE,yGAAA,QAAQ,OAAA;AAEhC,iCASiB;AARf,wGAAA,eAAe,OAAA;AACf,gGAAA,OAAO,OAAA;AACP,wGAAA,eAAe,OAAA;AACf,mGAAA,UAAU,OAAA;AACV,uGAAA,cAAc,OAAA;AACd,2GAAA,kBAAkB,OAAA;AAClB,sGAAA,aAAa,OAAA;AACb,gGAAA,OAAO,OAAA;AAET,qCAAiE;AAAxD,0GAAA,eAAe,OAAA;AAAE,8GAAA,mBAAmB,OAAA;AAC7C,oFAAoF;AACpF,kCAAkC;AAClC,oCAA+D;AAAtD,oGAAA,cAAc,OAAA;AAAE,oGAAA,cAAc,OAAA;AAEvC,yCAAuB;AACvB,+CAA6B;AAE7B,2DAK8B;AAJ5B,4HAAA,sBAAsB,OAAA;AACtB,6HAAA,uBAAuB,OAAA;AACvB,0HAAA,oBAAoB,OAAA;AACpB,2HAAA,qBAAqB,OAAA;AAEvB,iFAAiF;AACjF,gDAAgD;AAChD,oCAAmE;AAA1D,kGAAA,YAAY,OAAA;AAAE,0GAAA,oBAAoB,OAAA;AAE3C,qFAAqF;AACrF,gFAAgF;AAChF,gFAAgF;AAChF,oCAAiD;AAAxC,sGAAA,gBAAgB,OAAA;AACzB,0EAA0E;AAC1E,4DAA4D;AAC5D,oCAAyF;AAAhF,sGAAA,gBAAgB,OAAA;AAAE,8GAAA,wBAAwB,OAAA;AAAE,kGAAA,YAAY,OAAA;AAEjE,qFAAqF;AACrF,+CAA+C;AAC/C,oCAAuD;AAA9C,4GAAA,sBAAsB,OAAA;AAE/B,4EAA4E;AAC5E,0DAAyF;AAAhF,kHAAA,iBAAiB,OAAA;AAAE,4GAAA,WAAW,OAAA;AAAE,8GAAA,aAAa,OAAA;AAQtD,4EAA4E;AAC5E,4EAA4E;AAC5E,qBAAqB"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type MfaChoices, type MfaEnrollmentController, type MfaEnrollmentState } from '@atlasauth/js';
|
|
3
|
+
/**
|
|
4
|
+
* The sign-in's MFA steps for `<SignIn>`: setting up a second factor when the
|
|
5
|
+
* app requires one (`needs_mfa_enrollment`) and the texted / emailed code at the
|
|
6
|
+
* second-factor step. The requests and the step's state live in
|
|
7
|
+
* `@atlasauth/js` (`createMfaEnrollment`), shared with every framework SDK.
|
|
8
|
+
*/
|
|
9
|
+
/** What the instance lets the MFA steps offer (`account.mfa`, `account.emailSecondFactor`). */
|
|
10
|
+
export declare function useMfaChoices(): MfaChoices;
|
|
11
|
+
/**
|
|
12
|
+
* The enrolment step's controller and state, started for `attemptId` while
|
|
13
|
+
* `active` (the flow is at `needs_mfa_enrollment`) and reset when it leaves.
|
|
14
|
+
*/
|
|
15
|
+
export declare function useMfaEnrollment(attemptId: string | null, active: boolean): {
|
|
16
|
+
controller: MfaEnrollmentController;
|
|
17
|
+
state: MfaEnrollmentState;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The enrolment step's body: the method choice, the authenticator's QR code and
|
|
21
|
+
* setup key with its code field(s), or the phone number then the texted code.
|
|
22
|
+
* `field` draws an input the way the rest of the form does.
|
|
23
|
+
*/
|
|
24
|
+
export declare function MfaEnrollmentFields({ controller, state, busy, field, clearValues, }: {
|
|
25
|
+
controller: MfaEnrollmentController;
|
|
26
|
+
state: MfaEnrollmentState;
|
|
27
|
+
busy: boolean;
|
|
28
|
+
field: (name: string, labelKey: string, type?: string, extra?: {
|
|
29
|
+
autoComplete?: string;
|
|
30
|
+
inputMode?: 'numeric' | 'tel';
|
|
31
|
+
}) => ReactNode;
|
|
32
|
+
clearValues: () => void;
|
|
33
|
+
}): import("react").JSX.Element;
|
|
34
|
+
/**
|
|
35
|
+
* "Text me a code instead" / "Email me a code instead" at the second factor:
|
|
36
|
+
* sends the code for this attempt, then says where it went. The code is typed
|
|
37
|
+
* into the step's code field like any second-factor code.
|
|
38
|
+
*/
|
|
39
|
+
export declare function SecondFactorCodeOption({ attemptId, strategy }: {
|
|
40
|
+
attemptId: string;
|
|
41
|
+
strategy: 'sms' | 'email_code';
|
|
42
|
+
}): import("react").JSX.Element;
|
|
43
|
+
/**
|
|
44
|
+
* The recovery codes a mid-sign-in enrolment came with, shown once before the
|
|
45
|
+
* sign-in finishes: copy them, download them as a .txt file, then "I've saved
|
|
46
|
+
* my codes" (`onSaved`) exchanges the ticket. The codes live only in the flow's
|
|
47
|
+
* memory and are forgotten once saved.
|
|
48
|
+
*/
|
|
49
|
+
export declare function RecoveryCodes({ codes, busy, onSaved }: {
|
|
50
|
+
codes: string[];
|
|
51
|
+
busy: boolean;
|
|
52
|
+
onSaved(): void;
|
|
53
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useMfaChoices = useMfaChoices;
|
|
5
|
+
exports.useMfaEnrollment = useMfaEnrollment;
|
|
6
|
+
exports.MfaEnrollmentFields = MfaEnrollmentFields;
|
|
7
|
+
exports.SecondFactorCodeOption = SecondFactorCodeOption;
|
|
8
|
+
exports.RecoveryCodes = RecoveryCodes;
|
|
9
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
10
|
+
const react_1 = require("react");
|
|
11
|
+
const js_1 = require("@atlasauth/js");
|
|
12
|
+
const AtlasProvider_1 = require("./AtlasProvider");
|
|
13
|
+
const components_shared_1 = require("./components-shared");
|
|
14
|
+
/**
|
|
15
|
+
* The sign-in's MFA steps for `<SignIn>`: setting up a second factor when the
|
|
16
|
+
* app requires one (`needs_mfa_enrollment`) and the texted / emailed code at the
|
|
17
|
+
* second-factor step. The requests and the step's state live in
|
|
18
|
+
* `@atlasauth/js` (`createMfaEnrollment`), shared with every framework SDK.
|
|
19
|
+
*/
|
|
20
|
+
/** What the instance lets the MFA steps offer (`account.mfa`, `account.emailSecondFactor`). */
|
|
21
|
+
function useMfaChoices() {
|
|
22
|
+
const { instanceSettings } = (0, AtlasProvider_1.useAtlas)();
|
|
23
|
+
return (0, js_1.mfaChoicesFrom)(instanceSettings?.account);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The enrolment step's controller and state, started for `attemptId` while
|
|
27
|
+
* `active` (the flow is at `needs_mfa_enrollment`) and reset when it leaves.
|
|
28
|
+
*/
|
|
29
|
+
function useMfaEnrollment(attemptId, active) {
|
|
30
|
+
const client = (0, components_shared_1.useFapiClient)();
|
|
31
|
+
const choices = useMfaChoices();
|
|
32
|
+
const choicesRef = (0, react_1.useRef)(choices);
|
|
33
|
+
choicesRef.current = choices;
|
|
34
|
+
const controller = (0, react_1.useMemo)(() => (0, js_1.createMfaEnrollment)(client, () => choicesRef.current), [client]);
|
|
35
|
+
const [state, setState] = (0, react_1.useState)(controller.state);
|
|
36
|
+
(0, react_1.useEffect)(() => controller.subscribe(setState), [controller]);
|
|
37
|
+
(0, react_1.useEffect)(() => {
|
|
38
|
+
if (active && attemptId)
|
|
39
|
+
controller.start(attemptId);
|
|
40
|
+
else
|
|
41
|
+
controller.reset();
|
|
42
|
+
}, [controller, active, attemptId]);
|
|
43
|
+
return { controller, state };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The enrolment step's body: the method choice, the authenticator's QR code and
|
|
47
|
+
* setup key with its code field(s), or the phone number then the texted code.
|
|
48
|
+
* `field` draws an input the way the rest of the form does.
|
|
49
|
+
*/
|
|
50
|
+
function MfaEnrollmentFields({ controller, state, busy, field, clearValues, }) {
|
|
51
|
+
const t = (0, components_shared_1.useText)();
|
|
52
|
+
const cls = (0, components_shared_1.useClasses)();
|
|
53
|
+
const errorClass = (0, components_shared_1.useClass)('formFieldError', 'atlas-error');
|
|
54
|
+
const secondaryClass = (0, components_shared_1.useClass)('formButtonSecondary', 'atlas-button-secondary');
|
|
55
|
+
const { method, enrollment, options } = state;
|
|
56
|
+
const out = [];
|
|
57
|
+
if (method === null) {
|
|
58
|
+
out.push((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t('mfa.enrollChoose') }, "choose"), ...options.map((m) => ((0, jsx_runtime_1.jsx)("button", { type: "button", className: secondaryClass, "data-atlas-mfa-method": m, disabled: busy || state.busy, onClick: () => {
|
|
59
|
+
clearValues();
|
|
60
|
+
void controller.choose(m);
|
|
61
|
+
}, children: t(m === 'totp' ? 'mfa.enrollTotpOption' : 'mfa.enrollSmsOption') }, m))));
|
|
62
|
+
}
|
|
63
|
+
else if (method === 'totp') {
|
|
64
|
+
if (!enrollment) {
|
|
65
|
+
out.push((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), "aria-live": "polite", children: state.busy ? t('mfa.enrollPreparing') : null }, "preparing"));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
out.push((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t('mfa.enrollScan') }, "scan"));
|
|
69
|
+
if (enrollment.qrCode) {
|
|
70
|
+
out.push((0, jsx_runtime_1.jsx)("img", { className: "atlas-mfa-qr", "data-atlas": "mfa-qr", src: enrollment.qrCode, alt: t('mfa.enrollQrAlt'), width: 180, height: 180 }, "qr"));
|
|
71
|
+
}
|
|
72
|
+
if (enrollment.secret) {
|
|
73
|
+
out.push((0, jsx_runtime_1.jsxs)("p", { className: cls('text', 'atlas-text'), "data-atlas": "mfa-setup-key", children: [t('mfa.enrollSetupKey'), ": ", (0, jsx_runtime_1.jsx)("code", { children: (0, js_1.groupSetupKey)(enrollment.secret) })] }, "secret"));
|
|
74
|
+
}
|
|
75
|
+
out.push(field('code', 'mfa.enrollFirstCodeLabel', 'text', { autoComplete: 'one-time-code', inputMode: 'numeric' }));
|
|
76
|
+
if (state.totpCodes >= 2) {
|
|
77
|
+
out.push(field('secondCode', 'mfa.enrollSecondCodeLabel', 'text', { autoComplete: 'one-time-code', inputMode: 'numeric' }));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (!enrollment) {
|
|
82
|
+
out.push(field('phone', 'mfa.phoneLabel', 'tel', { autoComplete: 'tel', inputMode: 'tel' }), (0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t('mfa.phoneHint') }, "phone-hint"));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
out.push((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), "data-atlas": "mfa-code-texted", "aria-live": "polite", children: t('mfa.codeTextedTo', { phone: enrollment.phone ?? '' }) }, "texted"), field('code', 'mfa.codeLabel', 'text', { autoComplete: 'one-time-code', inputMode: 'numeric' }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: cls('link', 'atlas-link'), "data-atlas-resend": "", disabled: busy || state.busy, onClick: () => void controller.resend(), children: t('signIn.resendCode') }, "resend"));
|
|
86
|
+
}
|
|
87
|
+
for (const error of state.errors) {
|
|
88
|
+
out.push((0, jsx_runtime_1.jsx)("p", { className: errorClass, role: "alert", children: error.message }, `enrol-error-${error.code}-${error.param ?? ''}`));
|
|
89
|
+
}
|
|
90
|
+
if (method !== null && options.length > 1) {
|
|
91
|
+
const other = options.find((m) => m !== method);
|
|
92
|
+
out.push((0, jsx_runtime_1.jsx)("button", { type: "button", className: cls('link', 'atlas-link'), "data-atlas-mfa-method": other, disabled: busy || state.busy, onClick: () => {
|
|
93
|
+
clearValues();
|
|
94
|
+
void controller.choose(other);
|
|
95
|
+
}, children: t(other === 'totp' ? 'mfa.enrollTotpOption' : 'mfa.enrollSmsOption') }, "switch"));
|
|
96
|
+
}
|
|
97
|
+
return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: out });
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* "Text me a code instead" / "Email me a code instead" at the second factor:
|
|
101
|
+
* sends the code for this attempt, then says where it went. The code is typed
|
|
102
|
+
* into the step's code field like any second-factor code.
|
|
103
|
+
*/
|
|
104
|
+
function SecondFactorCodeOption({ attemptId, strategy }) {
|
|
105
|
+
const client = (0, components_shared_1.useFapiClient)();
|
|
106
|
+
const t = (0, components_shared_1.useText)();
|
|
107
|
+
const cls = (0, components_shared_1.useClasses)();
|
|
108
|
+
const [state, setState] = (0, react_1.useState)({});
|
|
109
|
+
const name = strategy === 'sms' ? 'sms-second-factor' : 'email-second-factor';
|
|
110
|
+
const send = () => {
|
|
111
|
+
setState({ busy: true });
|
|
112
|
+
(0, js_1.prepareSecondFactorCode)(client, attemptId, strategy)
|
|
113
|
+
.then(({ sentTo }) => setState({ sentTo }))
|
|
114
|
+
.catch((e) => setState({ error: e instanceof Error ? e.message : String(e) }));
|
|
115
|
+
};
|
|
116
|
+
if (state.sentTo !== undefined) {
|
|
117
|
+
return ((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), "data-atlas": `${name}-sent`, "aria-live": "polite", children: strategy === 'sms' ? t('mfa.codeTextedTo', { phone: state.sentTo }) : t('signIn.codeSentTo', { email: state.sentTo }) }));
|
|
118
|
+
}
|
|
119
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: cls('link', 'atlas-link'), "data-atlas": name, disabled: state.busy, onClick: send, children: state.busy ? t('signIn.sending') : t(strategy === 'sms' ? 'mfa.smsCodeOption' : 'signIn.emailCodeOption') }), state.error ? ((0, jsx_runtime_1.jsx)("div", { className: cls('alert', 'atlas-alert'), role: "alert", children: state.error })) : null] }));
|
|
120
|
+
}
|
|
121
|
+
const RECOVERY_LIST_STYLE = {
|
|
122
|
+
listStyle: 'none',
|
|
123
|
+
padding: 0,
|
|
124
|
+
margin: '8px 0 12px',
|
|
125
|
+
display: 'grid',
|
|
126
|
+
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
|
127
|
+
gap: 6,
|
|
128
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
|
|
129
|
+
textAlign: 'center',
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* The recovery codes a mid-sign-in enrolment came with, shown once before the
|
|
133
|
+
* sign-in finishes: copy them, download them as a .txt file, then "I've saved
|
|
134
|
+
* my codes" (`onSaved`) exchanges the ticket. The codes live only in the flow's
|
|
135
|
+
* memory and are forgotten once saved.
|
|
136
|
+
*/
|
|
137
|
+
function RecoveryCodes({ codes, busy, onSaved }) {
|
|
138
|
+
const t = (0, components_shared_1.useText)();
|
|
139
|
+
const cls = (0, components_shared_1.useClasses)();
|
|
140
|
+
const cardClass = (0, components_shared_1.useClass)('card', 'atlas-card');
|
|
141
|
+
const titleClass = (0, components_shared_1.useClass)('headerTitle', 'atlas-title');
|
|
142
|
+
const buttonClass = (0, components_shared_1.useClass)('formButtonPrimary', 'atlas-button');
|
|
143
|
+
const secondaryClass = (0, components_shared_1.useClass)('formButtonSecondary', 'atlas-button-secondary');
|
|
144
|
+
const [copied, setCopied] = (0, react_1.useState)(false);
|
|
145
|
+
const canCopy = typeof navigator !== 'undefined' && !!navigator.clipboard?.writeText;
|
|
146
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, "data-atlas": "recovery-codes", children: [(0, jsx_runtime_1.jsx)("h1", { className: titleClass, children: t('mfa.recoveryCodesTitle') }), (0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t('mfa.recoveryCodesHelp') }), (0, jsx_runtime_1.jsx)("ul", { "data-atlas": "recovery-codes-list", style: RECOVERY_LIST_STYLE, children: codes.map((code) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)("code", { children: code }) }, code))) }), (0, jsx_runtime_1.jsxs)("div", { className: "atlas-alternatives", children: [canCopy ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: secondaryClass, "data-atlas": "recovery-codes-copy", onClick: () => void (0, js_1.copyRecoveryCodes)(codes).then(setCopied), children: t('mfa.recoveryCodesCopy') })) : null, (0, js_1.canDownloadRecoveryCodes)() ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: secondaryClass, "data-atlas": "recovery-codes-download", onClick: () => (0, js_1.downloadRecoveryCodes)(codes), children: t('mfa.recoveryCodesDownload') })) : null] }), copied ? ((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), "aria-live": "polite", "data-atlas": "recovery-codes-copied", children: t('mfa.recoveryCodesCopied') })) : null, (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonClass, "data-atlas": "recovery-codes-saved", disabled: busy, onClick: onSaved, children: t('mfa.recoveryCodesSaved') })] }));
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=mfa-steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mfa-steps.js","sourceRoot":"","sources":["../src/mfa-steps.tsx"],"names":[],"mappings":";AAAA,YAAY,CAAC;;AA0Bb,sCAGC;AAMD,4CAgBC;AAOD,kDA4HC;AAOD,wDA+BC;AAmBD,sCAoDC;;AAjSD,iCAA6E;AAC7E,sCAWuB;AACvB,mDAA2C;AAC3C,2DAAmF;AAEnF;;;;;GAKG;AAEH,+FAA+F;AAC/F,SAAgB,aAAa;IAC3B,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAA,wBAAQ,GAAE,CAAC;IACxC,OAAO,IAAA,mBAAc,EAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED;;;GAGG;AACH,SAAgB,gBAAgB,CAC9B,SAAwB,EACxB,MAAe;IAEf,MAAM,MAAM,GAAG,IAAA,iCAAa,GAAE,CAAC;IAC/B,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;IAChC,MAAM,UAAU,GAAG,IAAA,cAAM,EAAC,OAAO,CAAC,CAAC;IACnC,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;IAC7B,MAAM,UAAU,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,IAAA,wBAAmB,EAAC,MAAM,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAClG,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAC,UAAU,CAAC,KAAK,CAAC,CAAC;IACrD,IAAA,iBAAS,EAAC,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAC9D,IAAA,iBAAS,EAAC,GAAG,EAAE;QACb,IAAI,MAAM,IAAI,SAAS;YAAE,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;;YAChD,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,EAClC,UAAU,EACV,KAAK,EACL,IAAI,EACJ,KAAK,EACL,WAAW,GAOZ;IACC,MAAM,CAAC,GAAG,IAAA,2BAAO,GAAE,CAAC;IACpB,MAAM,GAAG,GAAG,IAAA,8BAAU,GAAE,CAAC;IACzB,MAAM,UAAU,GAAG,IAAA,4BAAQ,EAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;IAC7D,MAAM,cAAc,GAAG,IAAA,4BAAQ,EAAC,qBAAqB,EAAE,wBAAwB,CAAC,CAAC;IACjF,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,KAAK,CAAC;IAC9C,MAAM,GAAG,GAAgB,EAAE,CAAC;IAE5B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,GAAG,CAAC,IAAI,CACN,8BAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,YACjD,CAAC,CAAC,kBAAkB,CAAC,IADjB,QAAQ,CAEX,EACJ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CACpB,mCAEE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,cAAc,2BACF,CAAC,EACxB,QAAQ,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,EAC5B,OAAO,EAAE,GAAG,EAAE;gBACZ,WAAW,EAAE,CAAC;gBACd,KAAK,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC5B,CAAC,YAEA,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAV5D,CAAC,CAWC,CACV,CAAC,CACH,CAAC;IACJ,CAAC;SAAM,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QAC7B,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,GAAG,CAAC,IAAI,CACN,8BAAmB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,eAAY,QAAQ,YACxE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,IAAI,IADxC,WAAW,CAEd,CACL,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,IAAI,CACN,8BAAc,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,YAC/C,CAAC,CAAC,gBAAgB,CAAC,IADf,MAAM,CAET,CACL,CAAC;YACF,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;gBACtB,GAAG,CAAC,IAAI,CACN,gCAAc,SAAS,EAAC,cAAc,gBAAY,QAAQ,EAAC,GAAG,EAAE,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,IAA5H,IAAI,CAA4H,CAC1I,CAAC;YACJ,CAAC;YACD,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;gBACtB,GAAG,CAAC,IAAI,CACN,+BAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,gBAAa,eAAe,aAC7E,CAAC,CAAC,oBAAoB,CAAC,QAAG,2CAAO,IAAA,kBAAa,EAAC,UAAU,CAAC,MAAM,CAAC,GAAQ,KADrE,QAAQ,CAEX,CACL,CAAC;YACJ,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,0BAA0B,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YACrH,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,CAAC;gBACzB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,2BAA2B,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAC9H,CAAC;QACH,CAAC;IACH,CAAC;SAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACvB,GAAG,CAAC,IAAI,CACN,KAAK,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAClF,8BAAoB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,YACrD,CAAC,CAAC,eAAe,CAAC,IADd,YAAY,CAEf,CACL,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,IAAI,CACN,8BAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,gBAAa,iBAAiB,eAAW,QAAQ,YAClG,CAAC,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC,IADpD,QAAQ,CAEX,EACJ,KAAK,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,EAC/F,mCAEE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,uBAClB,EAAE,EACpB,QAAQ,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,EAC5B,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,UAAU,CAAC,MAAM,EAAE,YAEtC,CAAC,CAAC,mBAAmB,CAAC,IAPnB,QAAQ,CAQL,CACV,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,GAAG,CAAC,IAAI,CACN,8BAA0D,SAAS,EAAE,UAAU,EAAE,IAAI,EAAC,OAAO,YAC1F,KAAK,CAAC,OAAO,IADR,eAAe,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,CAEpD,CACL,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,MAAM,CAAE,CAAC;QACjD,GAAG,CAAC,IAAI,CACN,mCAEE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,2BACb,KAAK,EAC5B,QAAQ,EAAE,IAAI,IAAI,KAAK,CAAC,IAAI,EAC5B,OAAO,EAAE,GAAG,EAAE;gBACZ,WAAW,EAAE,CAAC;gBACd,KAAK,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC,YAEA,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,qBAAqB,CAAC,IAVjE,QAAQ,CAWL,CACV,CAAC;IACJ,CAAC;IACD,OAAO,2DAAG,GAAG,GAAI,CAAC;AACpB,CAAC;AAED;;;;GAIG;AACH,SAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAyD;IACnH,MAAM,MAAM,GAAG,IAAA,iCAAa,GAAE,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAA,2BAAO,GAAE,CAAC;IACpB,MAAM,GAAG,GAAG,IAAA,8BAAU,GAAE,CAAC;IACzB,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,IAAA,gBAAQ,EAAsD,EAAE,CAAC,CAAC;IAC5F,MAAM,IAAI,GAAG,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAC9E,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,QAAQ,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACzB,IAAA,4BAAuB,EAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,CAAC;aACjD,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;aAC1C,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC5F,CAAC,CAAC;IACF,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,OAAO,CACL,8BAAG,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,gBAAc,GAAG,IAAI,OAAO,eAAY,QAAQ,YACpF,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,kBAAkB,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,GACpH,CACL,CAAC;IACJ,CAAC;IACD,OAAO,CACL,6DACE,mCAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,gBAAc,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,YAC9G,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,wBAAwB,CAAC,GACnG,EACR,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CACb,gCAAK,SAAS,EAAE,GAAG,CAAC,OAAO,EAAE,aAAa,CAAC,EAAE,IAAI,EAAC,OAAO,YACtD,KAAK,CAAC,KAAK,GACR,CACP,CAAC,CAAC,CAAC,IAAI,IACP,CACJ,CAAC;AACJ,CAAC;AAED,MAAM,mBAAmB,GAAG;IAC1B,SAAS,EAAE,MAAM;IACjB,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,MAAM;IACf,mBAAmB,EAAE,2BAA2B;IAChD,GAAG,EAAE,CAAC;IACN,UAAU,EAAE,gDAAgD;IAC5D,SAAS,EAAE,QAAQ;CACX,CAAC;AAEX;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAuD;IACzG,MAAM,CAAC,GAAG,IAAA,2BAAO,GAAE,CAAC;IACpB,MAAM,GAAG,GAAG,IAAA,8BAAU,GAAE,CAAC;IACzB,MAAM,SAAS,GAAG,IAAA,4BAAQ,EAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,IAAA,4BAAQ,EAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IAC1D,MAAM,WAAW,GAAG,IAAA,4BAAQ,EAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;IAClE,MAAM,cAAc,GAAG,IAAA,4BAAQ,EAAC,qBAAqB,EAAE,wBAAwB,CAAC,CAAC;IACjF,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,IAAA,gBAAQ,EAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC;IACrF,OAAO,CACL,iCAAK,SAAS,EAAE,SAAS,gBAAa,gBAAgB,aACpD,+BAAI,SAAS,EAAE,UAAU,YAAG,CAAC,CAAC,wBAAwB,CAAC,GAAM,EAC7D,8BAAG,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,YAAG,CAAC,CAAC,uBAAuB,CAAC,GAAK,EACzE,6CAAe,qBAAqB,EAAC,KAAK,EAAE,mBAAmB,YAC5D,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACnB,yCACE,2CAAO,IAAI,GAAQ,IADZ,IAAI,CAER,CACN,CAAC,GACC,EACL,iCAAK,SAAS,EAAC,oBAAoB,aAChC,OAAO,CAAC,CAAC,CAAC,CACT,mCACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,cAAc,gBACd,qBAAqB,EAChC,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,IAAA,sBAAiB,EAAC,KAAK,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,YAE3D,CAAC,CAAC,uBAAuB,CAAC,GACpB,CACV,CAAC,CAAC,CAAC,IAAI,EACP,IAAA,6BAAwB,GAAE,CAAC,CAAC,CAAC,CAC5B,mCACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,cAAc,gBACd,yBAAyB,EACpC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAA,0BAAqB,EAAC,KAAK,CAAC,YAE1C,CAAC,CAAC,2BAA2B,CAAC,GACxB,CACV,CAAC,CAAC,CAAC,IAAI,IACJ,EACL,MAAM,CAAC,CAAC,CAAC,CACR,8BAAG,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,eAAY,QAAQ,gBAAY,uBAAuB,YAC3F,CAAC,CAAC,yBAAyB,CAAC,GAC3B,CACL,CAAC,CAAC,CAAC,IAAI,EACR,mCAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAE,WAAW,gBAAa,sBAAsB,EAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,YAC7G,CAAC,CAAC,wBAAwB,CAAC,GACrB,IACL,CACP,CAAC;AACJ,CAAC"}
|
package/dist/organization.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { OrganizationProfileController, type OrganizationProfileState } from '@atlasauth/js';
|
|
1
2
|
import { useOrganization } from './hooks';
|
|
3
|
+
import { useReverification } from './reverification';
|
|
2
4
|
export declare function OrganizationSwitcher(): import("react").JSX.Element;
|
|
3
5
|
/**
|
|
4
6
|
* Clerk-parity `useOrganizationList()`. The memberships the signed-in user
|
|
@@ -34,4 +36,83 @@ export declare function CreateOrganization({ onCreate, }?: {
|
|
|
34
36
|
* can make one without leaving the list.
|
|
35
37
|
*/
|
|
36
38
|
export declare function OrganizationList(): import("react").JSX.Element;
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Headless state for the active organization's profile: members, pending
|
|
41
|
+
* invitations and roles, with the admin actions (change role, remove, invite,
|
|
42
|
+
* revoke, resend) and the member actions (leave; delete for an admin, which
|
|
43
|
+
* parks for a password re-authentication on a stale session). The same
|
|
44
|
+
* `OrganizationProfileController` backs every SDK's `<OrganizationProfile>`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useOrganizationProfile(): {
|
|
47
|
+
organizationId: string | null;
|
|
48
|
+
state: OrganizationProfileState;
|
|
49
|
+
canManage: boolean;
|
|
50
|
+
isAdmin: boolean;
|
|
51
|
+
controller: OrganizationProfileController | null;
|
|
52
|
+
deleteOrganization(): Promise<boolean>;
|
|
53
|
+
reverification: ReturnType<typeof useReverification>;
|
|
54
|
+
};
|
|
55
|
+
export interface OrganizationProfileProps {
|
|
56
|
+
/** Called after the user leaves or deletes the organization (e.g. to navigate away). */
|
|
57
|
+
onExit?(reason: 'left' | 'deleted'): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Clerk-parity `<OrganizationProfile>`: the active organization's members
|
|
61
|
+
* (admins change roles and remove), invitations (admins invite, revoke and
|
|
62
|
+
* resend) and settings (leave; an admin deletes, after re-authenticating).
|
|
63
|
+
* Every control is gated on the ACTIVE org role, matching the server's rule.
|
|
64
|
+
*/
|
|
65
|
+
export declare function OrganizationProfile({ onExit }?: OrganizationProfileProps): import("react").JSX.Element | null;
|
|
66
|
+
/** One entry of an organization's audit trail, as the Frontend API returns it. */
|
|
67
|
+
export interface OrganizationAuditLogEntry {
|
|
68
|
+
object: 'audit_log';
|
|
69
|
+
id: string;
|
|
70
|
+
action: string;
|
|
71
|
+
actor: {
|
|
72
|
+
type: string;
|
|
73
|
+
id: string | null;
|
|
74
|
+
};
|
|
75
|
+
target: {
|
|
76
|
+
type: string | null;
|
|
77
|
+
id: string | null;
|
|
78
|
+
};
|
|
79
|
+
organization_id: string;
|
|
80
|
+
ip: string | null;
|
|
81
|
+
user_agent: string | null;
|
|
82
|
+
metadata: Record<string, unknown>;
|
|
83
|
+
created_at: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The active organization's audit trail, for org admins: one page at a time,
|
|
87
|
+
* newest first, with a "load more" cursor. `action` narrows it (exact, or a
|
|
88
|
+
* prefix ending in `.`). Returns `forbidden: true` when the signed-in member is
|
|
89
|
+
* not allowed to read it, so the caller can hide the surface.
|
|
90
|
+
*/
|
|
91
|
+
export declare function useOrganizationAuditLog(options?: {
|
|
92
|
+
pageSize?: number;
|
|
93
|
+
action?: string;
|
|
94
|
+
}): {
|
|
95
|
+
entries: OrganizationAuditLogEntry[];
|
|
96
|
+
loading: boolean;
|
|
97
|
+
forbidden: boolean;
|
|
98
|
+
hasMore: boolean;
|
|
99
|
+
loadMore(): Promise<void>;
|
|
100
|
+
reload(): Promise<void>;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* `<OrganizationAuditLog/>` — the active organization's audit trail for its
|
|
104
|
+
* admins, next to `<OrganizationProfile/>`. Renders nothing for a member who may
|
|
105
|
+
* not read it (the server is the authority; the component just follows it).
|
|
106
|
+
* Labels are props so an app can localise them.
|
|
107
|
+
*/
|
|
108
|
+
export declare function OrganizationAuditLog({ pageSize, action, labels, formatDate, }?: {
|
|
109
|
+
pageSize?: number;
|
|
110
|
+
action?: string;
|
|
111
|
+
labels?: {
|
|
112
|
+
title?: string;
|
|
113
|
+
empty?: string;
|
|
114
|
+
loadMore?: string;
|
|
115
|
+
actor?: string;
|
|
116
|
+
};
|
|
117
|
+
formatDate?(ms: number): string;
|
|
118
|
+
}): import("react").JSX.Element | null;
|
package/dist/organization.js
CHANGED
|
@@ -5,13 +5,17 @@ exports.OrganizationSwitcher = OrganizationSwitcher;
|
|
|
5
5
|
exports.useOrganizationList = useOrganizationList;
|
|
6
6
|
exports.CreateOrganization = CreateOrganization;
|
|
7
7
|
exports.OrganizationList = OrganizationList;
|
|
8
|
+
exports.useOrganizationProfile = useOrganizationProfile;
|
|
8
9
|
exports.OrganizationProfile = OrganizationProfile;
|
|
10
|
+
exports.useOrganizationAuditLog = useOrganizationAuditLog;
|
|
11
|
+
exports.OrganizationAuditLog = OrganizationAuditLog;
|
|
9
12
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
10
13
|
const react_1 = require("react");
|
|
14
|
+
const js_1 = require("@atlasauth/js");
|
|
11
15
|
const AtlasProvider_1 = require("./AtlasProvider");
|
|
12
16
|
const hooks_1 = require("./hooks");
|
|
13
17
|
const components_shared_1 = require("./components-shared");
|
|
14
|
-
const
|
|
18
|
+
const reverification_1 = require("./reverification");
|
|
15
19
|
function OrganizationSwitcher() {
|
|
16
20
|
const { memberships, organization, setActive } = (0, hooks_1.useOrganization)();
|
|
17
21
|
const t = (0, components_shared_1.useText)();
|
|
@@ -102,13 +106,227 @@ function OrganizationList() {
|
|
|
102
106
|
const itemClass = (0, components_shared_1.useClass)('menuItem', 'atlas-menu-item');
|
|
103
107
|
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, {})] }));
|
|
104
108
|
}
|
|
105
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Headless state for the active organization's profile: members, pending
|
|
111
|
+
* invitations and roles, with the admin actions (change role, remove, invite,
|
|
112
|
+
* revoke, resend) and the member actions (leave; delete for an admin, which
|
|
113
|
+
* parks for a password re-authentication on a stale session). The same
|
|
114
|
+
* `OrganizationProfileController` backs every SDK's `<OrganizationProfile>`.
|
|
115
|
+
*/
|
|
116
|
+
function useOrganizationProfile() {
|
|
117
|
+
const { organization, membership } = (0, hooks_1.useOrganization)();
|
|
118
|
+
const { has } = (0, hooks_1.useAuth)();
|
|
119
|
+
const { reload } = (0, AtlasProvider_1.useAtlas)();
|
|
120
|
+
const client = (0, components_shared_1.useMeClient)();
|
|
121
|
+
const reverification = (0, reverification_1.useReverification)();
|
|
122
|
+
const canManage = has({ permission: 'org:sys_memberships:manage' });
|
|
123
|
+
const isAdmin = membership?.role === 'org:admin';
|
|
124
|
+
const organizationId = organization?.id ?? null;
|
|
125
|
+
const controller = (0, react_1.useMemo)(() => organizationId
|
|
126
|
+
? new js_1.OrganizationProfileController(client, organizationId, {
|
|
127
|
+
canManage,
|
|
128
|
+
onChange: async () => {
|
|
129
|
+
await reload();
|
|
130
|
+
},
|
|
131
|
+
})
|
|
132
|
+
: null, [client, organizationId, canManage, reload]);
|
|
133
|
+
(0, react_1.useEffect)(() => {
|
|
134
|
+
void controller?.load();
|
|
135
|
+
}, [controller]);
|
|
136
|
+
const empty = (0, react_1.useMemo)(() => ({ status: 'idle', members: [], membersTotal: 0, invitations: [], roles: [], permissions: [], error: null, busy: null }), []);
|
|
137
|
+
const state = (0, react_1.useSyncExternalStore)((0, react_1.useCallback)((cb) => (controller ? controller.subscribe(cb) : () => undefined), [controller]), () => controller?.getState() ?? empty, () => controller?.getState() ?? empty);
|
|
138
|
+
const { withReverification } = reverification;
|
|
139
|
+
const remove = (0, react_1.useCallback)(async () => {
|
|
140
|
+
if (!organizationId)
|
|
141
|
+
return false;
|
|
142
|
+
await withReverification(() => (0, js_1.deleteOrganization)(client, organizationId));
|
|
143
|
+
await reload();
|
|
144
|
+
return true;
|
|
145
|
+
}, [client, organizationId, reload, withReverification]);
|
|
146
|
+
return { organizationId, state, canManage, isAdmin, controller, deleteOrganization: remove, reverification };
|
|
147
|
+
}
|
|
148
|
+
function memberName(member, fallback) {
|
|
149
|
+
const name = [member.user?.first_name, member.user?.last_name].filter(Boolean).join(' ');
|
|
150
|
+
return name || fallback;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Clerk-parity `<OrganizationProfile>`: the active organization's members
|
|
154
|
+
* (admins change roles and remove), invitations (admins invite, revoke and
|
|
155
|
+
* resend) and settings (leave; an admin deletes, after re-authenticating).
|
|
156
|
+
* Every control is gated on the ACTIVE org role, matching the server's rule.
|
|
157
|
+
*/
|
|
158
|
+
function OrganizationProfile({ onExit } = {}) {
|
|
106
159
|
const { organization, membership } = (0, hooks_1.useOrganization)();
|
|
160
|
+
const { userId } = (0, hooks_1.useAuth)();
|
|
107
161
|
const t = (0, components_shared_1.useText)();
|
|
162
|
+
const errorText = (0, components_shared_1.useErrorText)();
|
|
163
|
+
const cardClass = (0, components_shared_1.useClass)('card', 'atlas-card');
|
|
164
|
+
const cls = (0, components_shared_1.useClasses)();
|
|
165
|
+
const profile = useOrganizationProfile();
|
|
166
|
+
const { state, controller, canManage, isAdmin, reverification } = profile;
|
|
167
|
+
const [tab, setTab] = (0, react_1.useState)('members');
|
|
168
|
+
// The custom-role editor: null = creating a new role, else the id being edited.
|
|
169
|
+
const [editingRole, setEditingRole] = (0, react_1.useState)(null);
|
|
170
|
+
const [roleKey, setRoleKey] = (0, react_1.useState)('');
|
|
171
|
+
const [roleName, setRoleName] = (0, react_1.useState)('');
|
|
172
|
+
const [rolePerms, setRolePerms] = (0, react_1.useState)([]);
|
|
173
|
+
const [email, setEmail] = (0, react_1.useState)('');
|
|
174
|
+
const [inviteRole, setInviteRole] = (0, react_1.useState)('org:member');
|
|
175
|
+
const [confirm, setConfirm] = (0, react_1.useState)(null);
|
|
176
|
+
const [password, setPassword] = (0, react_1.useState)('');
|
|
177
|
+
const [localError, setLocalError] = (0, react_1.useState)(null);
|
|
178
|
+
if (!organization || !controller)
|
|
179
|
+
return null;
|
|
180
|
+
const buttonSecondary = cls('formButtonSecondary', 'atlas-button-secondary');
|
|
181
|
+
const buttonPrimary = cls('formButtonPrimary', 'atlas-button');
|
|
182
|
+
const busy = (key) => state.busy === key;
|
|
183
|
+
const error = localError ?? (state.error ? errorText(state.error) : null);
|
|
184
|
+
const sendInvite = async () => {
|
|
185
|
+
const address = email.trim();
|
|
186
|
+
if (!address)
|
|
187
|
+
return;
|
|
188
|
+
const out = await controller.invite(address, inviteRole);
|
|
189
|
+
if (out)
|
|
190
|
+
setEmail('');
|
|
191
|
+
};
|
|
192
|
+
const doLeave = async () => {
|
|
193
|
+
setLocalError(null);
|
|
194
|
+
if (await controller.leave())
|
|
195
|
+
onExit?.('left');
|
|
196
|
+
setConfirm(null);
|
|
197
|
+
};
|
|
198
|
+
const doDelete = async () => {
|
|
199
|
+
setLocalError(null);
|
|
200
|
+
try {
|
|
201
|
+
if (await profile.deleteOrganization())
|
|
202
|
+
onExit?.('deleted');
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
if (!(err instanceof Error && err.message === 'Reverification cancelled.')) {
|
|
206
|
+
setLocalError(err instanceof Error ? errorText(err) : t('error.generic'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
setConfirm(null);
|
|
210
|
+
};
|
|
211
|
+
const roleOptions = state.roles.length > 0 ? state.roles : [{ key: 'org:member', name: 'org:member' }];
|
|
212
|
+
const customRoles = state.roles.filter((r) => Boolean(r.organization_id));
|
|
213
|
+
const resetRoleForm = () => {
|
|
214
|
+
setEditingRole(null);
|
|
215
|
+
setRoleKey('');
|
|
216
|
+
setRoleName('');
|
|
217
|
+
setRolePerms([]);
|
|
218
|
+
};
|
|
219
|
+
const saveRole = async () => {
|
|
220
|
+
const out = editingRole
|
|
221
|
+
? await controller.updateRole(editingRole, { name: roleName.trim(), permissions: rolePerms })
|
|
222
|
+
: await controller.createRole({ key: roleKey.trim(), name: roleName.trim(), permissions: rolePerms });
|
|
223
|
+
if (out)
|
|
224
|
+
resetRoleForm();
|
|
225
|
+
};
|
|
226
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, "data-atlas": "organization-profile", children: [(0, jsx_runtime_1.jsx)("h1", { className: cls('headerTitle', 'atlas-heading'), children: organization.name }), (0, jsx_runtime_1.jsxs)("p", { className: cls('headerSubtitle', 'atlas-subtitle'), children: [t('organization.roleLabel'), ": ", membership?.role] }), (0, jsx_runtime_1.jsx)("div", { className: cls('menu', 'atlas-menu'), role: "tablist", children: ['members', ...(canManage ? ['invitations', 'roles'] : []), 'settings'].map((key) => ((0, jsx_runtime_1.jsx)("button", { type: "button", role: "tab", "aria-selected": tab === key, className: cls('menuItem', 'atlas-menu-item'), "data-atlas": `organization-tab-${key}`, onClick: () => setTab(key), children: t(`organization.${key}`) }, key))) }), error ? ((0, jsx_runtime_1.jsx)("p", { className: cls('alert', 'atlas-alert'), role: "alert", children: error })) : null, tab === 'members' ? ((0, jsx_runtime_1.jsx)("ul", { className: cls('list', 'atlas-list'), "data-atlas": "organization-members", children: state.members.map((member) => {
|
|
227
|
+
const self = member.user?.id === userId;
|
|
228
|
+
const id = member.user?.id ?? member.id;
|
|
229
|
+
return ((0, jsx_runtime_1.jsxs)("li", { "data-atlas": "organization-member", children: [(0, jsx_runtime_1.jsxs)("span", { children: [memberName(member, t('organization.unnamedMember')), self ? ` ${t('organization.you')}` : ''] }), canManage && !self && member.user ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("select", { "aria-label": t('organization.roleLabel'), className: cls('formFieldInput', 'atlas-input'), value: member.role, disabled: busy(`role:${id}`), onChange: (event) => void controller.changeRole(id, event.target.value), children: roleOptions.map((role) => ((0, jsx_runtime_1.jsx)("option", { value: role.key, children: role.name }, role.key))) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, disabled: busy(`remove:${id}`), onClick: () => void controller.removeMember(id), children: t('organization.remove') })] })) : ((0, jsx_runtime_1.jsxs)("span", { children: [" \u00B7 ", member.role] }))] }, member.id));
|
|
230
|
+
}) })) : null, tab === 'invitations' && canManage ? ((0, jsx_runtime_1.jsxs)("div", { "data-atlas": "organization-invitations", children: [(0, jsx_runtime_1.jsxs)("form", { onSubmit: (event) => {
|
|
231
|
+
event.preventDefault();
|
|
232
|
+
void sendInvite();
|
|
233
|
+
}, children: [(0, jsx_runtime_1.jsxs)("label", { className: cls('formField', 'atlas-field'), children: [(0, jsx_runtime_1.jsx)("span", { className: cls('formFieldLabel', 'atlas-field-label'), children: t('organization.emailLabel') }), (0, jsx_runtime_1.jsx)("input", { className: cls('formFieldInput', 'atlas-input'), type: "email", value: email, onChange: (event) => setEmail(event.target.value) })] }), (0, jsx_runtime_1.jsxs)("label", { className: cls('formField', 'atlas-field'), children: [(0, jsx_runtime_1.jsx)("span", { className: cls('formFieldLabel', 'atlas-field-label'), children: t('organization.roleLabel') }), (0, jsx_runtime_1.jsx)("select", { className: cls('formFieldInput', 'atlas-input'), value: inviteRole, onChange: (event) => setInviteRole(event.target.value), children: roleOptions.map((role) => ((0, jsx_runtime_1.jsx)("option", { value: role.key, children: role.name }, role.key))) })] }), (0, jsx_runtime_1.jsx)("button", { type: "submit", className: buttonPrimary, disabled: busy('invite') || !email.trim(), children: t('organization.sendInvite') })] }), state.invitations.length === 0 ? ((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t('organization.noInvitations') })) : ((0, jsx_runtime_1.jsx)("ul", { className: cls('list', 'atlas-list'), children: state.invitations.map((inv) => ((0, jsx_runtime_1.jsxs)("li", { "data-atlas": "organization-invitation", children: [(0, jsx_runtime_1.jsxs)("span", { children: [inv.email_address, " \u00B7 ", inv.role] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, disabled: busy(`resend:${inv.id}`), onClick: () => void controller.resendInvitation(inv.id), children: t('organization.resend') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, disabled: busy(`revoke:${inv.id}`), onClick: () => void controller.revokeInvitation(inv.id), children: t('organization.revoke') })] }, inv.id))) }))] })) : null, tab === 'roles' && canManage ? ((0, jsx_runtime_1.jsxs)("div", { "data-atlas": "organization-roles", children: [customRoles.length === 0 ? ((0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t('organization.noCustomRoles') })) : ((0, jsx_runtime_1.jsx)("ul", { className: cls('list', 'atlas-list'), children: customRoles.map((role) => ((0, jsx_runtime_1.jsxs)("li", { "data-atlas": "organization-role", children: [(0, jsx_runtime_1.jsxs)("span", { children: [role.name, " \u00B7 ", role.key] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, onClick: () => {
|
|
234
|
+
setEditingRole(role.id ?? null);
|
|
235
|
+
setRoleKey(role.key);
|
|
236
|
+
setRoleName(role.name);
|
|
237
|
+
setRolePerms(role.permissions ?? []);
|
|
238
|
+
}, children: t('organization.editRole') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, disabled: !role.id || busy(`role:delete:${role.id}`), onClick: () => role.id && void controller.deleteRole(role.id), children: t('organization.deleteRole') })] }, role.id ?? role.key))) })), (0, jsx_runtime_1.jsxs)("form", { "data-atlas": "organization-role-form", onSubmit: (event) => {
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
void saveRole();
|
|
241
|
+
}, children: [editingRole ? null : ((0, jsx_runtime_1.jsxs)("label", { className: cls('formField', 'atlas-field'), children: [(0, jsx_runtime_1.jsx)("span", { className: cls('formFieldLabel', 'atlas-field-label'), children: t('organization.roleKey') }), (0, jsx_runtime_1.jsx)("input", { className: cls('formFieldInput', 'atlas-input'), name: "role_key", value: roleKey, onChange: (event) => setRoleKey(event.target.value) })] })), (0, jsx_runtime_1.jsxs)("label", { className: cls('formField', 'atlas-field'), children: [(0, jsx_runtime_1.jsx)("span", { className: cls('formFieldLabel', 'atlas-field-label'), children: t('organization.roleName') }), (0, jsx_runtime_1.jsx)("input", { className: cls('formFieldInput', 'atlas-input'), name: "role_name", value: roleName, onChange: (event) => setRoleName(event.target.value) })] }), (0, jsx_runtime_1.jsxs)("fieldset", { className: cls('formField', 'atlas-field'), children: [(0, jsx_runtime_1.jsx)("legend", { className: cls('formFieldLabel', 'atlas-field-label'), children: t('organization.rolePermissions') }), state.permissions.map((permission) => ((0, jsx_runtime_1.jsxs)("label", { "data-atlas": "organization-role-permission", children: [(0, jsx_runtime_1.jsx)("input", { type: "checkbox", value: permission.key, checked: rolePerms.includes(permission.key), onChange: (event) => setRolePerms((current) => event.target.checked
|
|
242
|
+
? [...current, permission.key]
|
|
243
|
+
: current.filter((key) => key !== permission.key)) }), ' ', permission.name] }, permission.key)))] }), (0, jsx_runtime_1.jsx)("button", { type: "submit", className: buttonPrimary, disabled: !roleName.trim() || (!editingRole && !roleKey.trim()) || state.busy?.startsWith('role:') === true, children: editingRole ? t('organization.saveRole') : t('organization.createRole') }), editingRole ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, onClick: resetRoleForm, children: t('organization.cancel') })) : null] })] })) : null, tab === 'settings' ? ((0, jsx_runtime_1.jsx)("div", { "data-atlas": "organization-settings", children: confirm ? ((0, jsx_runtime_1.jsxs)("div", { role: "dialog", "data-atlas": `organization-confirm-${confirm}`, children: [(0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t(confirm === 'leave' ? 'organization.leaveConfirm' : 'organization.deleteConfirm', {
|
|
244
|
+
name: organization.name,
|
|
245
|
+
}) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonPrimary, disabled: busy('leave') || busy('delete'), onClick: () => void (confirm === 'leave' ? doLeave() : doDelete()), children: t('organization.confirm') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, onClick: () => setConfirm(null), children: t('organization.cancel') })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, onClick: () => setConfirm('leave'), children: t('organization.leave') }), isAdmin ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, onClick: () => setConfirm('delete'), children: t('organization.delete') })) : null] })) })) : null, reverification.needsReverification ? ((0, jsx_runtime_1.jsxs)("div", { "data-atlas": "organization-reverification", role: "dialog", "aria-label": t('reverification.title'), children: [(0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: (0, jsx_runtime_1.jsx)("strong", { children: t('reverification.title') }) }), (0, jsx_runtime_1.jsx)("p", { className: cls('text', 'atlas-text'), children: t('reverification.subtitle') }), (0, jsx_runtime_1.jsxs)("label", { className: cls('formField', 'atlas-field'), children: [(0, jsx_runtime_1.jsx)("span", { className: cls('formFieldLabel', 'atlas-field-label'), children: t('reverification.passwordLabel') }), (0, jsx_runtime_1.jsx)("input", { className: cls('formFieldInput', 'atlas-account-input'), type: "password", autoComplete: "current-password", value: password, onChange: (event) => setPassword(event.target.value) })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonPrimary, onClick: () => void reverification
|
|
246
|
+
.completeReverification({ strategy: 'password', password })
|
|
247
|
+
.then(() => setPassword(''))
|
|
248
|
+
.catch((err) => setLocalError(err instanceof Error ? errorText(err) : t('error.generic'))), children: t('reverification.submit') }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: buttonSecondary, onClick: () => {
|
|
249
|
+
setPassword('');
|
|
250
|
+
reverification.cancelReverification();
|
|
251
|
+
}, children: t('reverification.cancel') })] })) : null] }));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The active organization's audit trail, for org admins: one page at a time,
|
|
255
|
+
* newest first, with a "load more" cursor. `action` narrows it (exact, or a
|
|
256
|
+
* prefix ending in `.`). Returns `forbidden: true` when the signed-in member is
|
|
257
|
+
* not allowed to read it, so the caller can hide the surface.
|
|
258
|
+
*/
|
|
259
|
+
function useOrganizationAuditLog(options = {}) {
|
|
260
|
+
const { organization } = (0, hooks_1.useOrganization)();
|
|
261
|
+
const client = (0, components_shared_1.useMeClient)();
|
|
262
|
+
const [entries, setEntries] = (0, react_1.useState)([]);
|
|
263
|
+
const [cursor, setCursor] = (0, react_1.useState)(null);
|
|
264
|
+
const [hasMore, setHasMore] = (0, react_1.useState)(false);
|
|
265
|
+
const [loading, setLoading] = (0, react_1.useState)(false);
|
|
266
|
+
const [forbidden, setForbidden] = (0, react_1.useState)(false);
|
|
267
|
+
const orgId = organization?.id;
|
|
268
|
+
const pageSize = options.pageSize ?? 20;
|
|
269
|
+
const action = options.action;
|
|
270
|
+
const fetchPage = (0, react_1.useCallback)(async (after) => {
|
|
271
|
+
if (!orgId)
|
|
272
|
+
return;
|
|
273
|
+
setLoading(true);
|
|
274
|
+
try {
|
|
275
|
+
const params = new URLSearchParams({ limit: String(pageSize) });
|
|
276
|
+
if (after)
|
|
277
|
+
params.set('starting_after', after);
|
|
278
|
+
if (action)
|
|
279
|
+
params.set('action', action);
|
|
280
|
+
const res = await client.get(`/v1/client/organizations/${encodeURIComponent(orgId)}/audit_logs?${params.toString()}`);
|
|
281
|
+
if (!res.ok || !res.data) {
|
|
282
|
+
setForbidden(res.status === 403 || res.status === 404);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const page = res.data;
|
|
286
|
+
setForbidden(false);
|
|
287
|
+
setEntries((prev) => (after ? [...prev, ...page.data] : page.data));
|
|
288
|
+
setCursor(page.next_cursor);
|
|
289
|
+
setHasMore(page.has_more);
|
|
290
|
+
}
|
|
291
|
+
finally {
|
|
292
|
+
setLoading(false);
|
|
293
|
+
}
|
|
294
|
+
}, [client, orgId, pageSize, action]);
|
|
295
|
+
(0, react_1.useEffect)(() => {
|
|
296
|
+
setEntries([]);
|
|
297
|
+
setCursor(null);
|
|
298
|
+
void fetchPage(null);
|
|
299
|
+
}, [fetchPage]);
|
|
300
|
+
return {
|
|
301
|
+
entries,
|
|
302
|
+
loading,
|
|
303
|
+
forbidden,
|
|
304
|
+
hasMore,
|
|
305
|
+
loadMore: async () => {
|
|
306
|
+
if (cursor)
|
|
307
|
+
await fetchPage(cursor);
|
|
308
|
+
},
|
|
309
|
+
reload: () => fetchPage(null),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/** `organization_invitation.created` → `Organization invitation created`. */
|
|
313
|
+
function describeAction(action) {
|
|
314
|
+
const text = action.replace(/[._]+/g, ' ').trim();
|
|
315
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* `<OrganizationAuditLog/>` — the active organization's audit trail for its
|
|
319
|
+
* admins, next to `<OrganizationProfile/>`. Renders nothing for a member who may
|
|
320
|
+
* not read it (the server is the authority; the component just follows it).
|
|
321
|
+
* Labels are props so an app can localise them.
|
|
322
|
+
*/
|
|
323
|
+
function OrganizationAuditLog({ pageSize, action, labels = {}, formatDate = (ms) => new Date(ms).toLocaleString(), } = {}) {
|
|
324
|
+
const { organization } = (0, hooks_1.useOrganization)();
|
|
325
|
+
const log = useOrganizationAuditLog({ pageSize, action });
|
|
108
326
|
const cardClass = (0, components_shared_1.useClass)('card', 'atlas-card');
|
|
109
327
|
const cls = (0, components_shared_1.useClasses)();
|
|
110
|
-
if (!organization)
|
|
328
|
+
if (!organization || log.forbidden)
|
|
111
329
|
return null;
|
|
112
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, children: [(0, jsx_runtime_1.jsx)("
|
|
330
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: cardClass, "data-atlas": "organization-audit-log", children: [(0, jsx_runtime_1.jsx)("h2", { className: cls('headerTitle', 'atlas-title'), children: labels.title ?? 'Audit log' }), log.entries.length === 0 && !log.loading ? ((0, jsx_runtime_1.jsx)("p", { className: cls('headerSubtitle', 'atlas-subtitle'), children: labels.empty ?? 'No activity recorded yet.' })) : ((0, jsx_runtime_1.jsx)("ul", { className: cls('list', 'atlas-audit-list'), children: log.entries.map((entry) => ((0, jsx_runtime_1.jsxs)("li", { className: cls('listItem', 'atlas-audit-item'), "data-action": entry.action, children: [(0, jsx_runtime_1.jsx)("strong", { children: describeAction(entry.action) }), (0, jsx_runtime_1.jsxs)("span", { className: "atlas-audit-meta", children: [' · ', formatDate(entry.created_at), entry.actor.id ? ` · ${labels.actor ?? 'by'} ${entry.actor.type} ${entry.actor.id}` : ''] })] }, entry.id))) })), log.hasMore ? ((0, jsx_runtime_1.jsx)("button", { className: cls('formButtonSecondary', 'atlas-button-secondary'), disabled: log.loading, onClick: () => void log.loadMore(), children: labels.loadMore ?? 'Load more' })) : null] }));
|
|
113
331
|
}
|
|
114
332
|
//# sourceMappingURL=organization.js.map
|