cauthy-auth 0.34.0 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/react.d.mts +20 -8
- package/dist/react.d.ts +20 -8
- package/dist/react.js +135 -42
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +136 -43
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +45 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -187,13 +187,21 @@ const Form: AuthComponents['Form'] = ({ as, children, ...rest }) =>
|
|
|
187
187
|
as === 'form' ? <Stack as="form" gap={3} {...rest}>{children}</Stack> : <Stack gap={3} {...rest}>{children}</Stack>
|
|
188
188
|
```
|
|
189
189
|
|
|
190
|
-
`Button` is told what kind of button it is: `primary` sends the form; `secondary` is any other way (a provider, a passkey, a sign-in link, SSO, cancel, send again); `danger` removes, leaves, deletes, disconnects, signs out everywhere or turns the second step off. `loading` is true on the one button whose action is under way; every button is `disabled` meanwhile
|
|
190
|
+
`Button` is told what kind of button it is: `primary` sends the form; `secondary` is any other way (a provider, a passkey, a sign-in link, SSO, cancel, send again); `danger` removes, leaves, deletes, disconnects, signs out everywhere or turns the second step off. `loading` is true on the one button whose action is under way; every button and field is `disabled` meanwhile, and the form carries `aria-busy`. After a sign-in, `loading` stays true until the page is left, so draw your design system's spinner whenever it is true and keep the label:
|
|
191
|
+
|
|
192
|
+
```tsx
|
|
193
|
+
const Button = ({ variant, loading, children, ...rest }: AuthButtonProps) => (
|
|
194
|
+
<MyButton tone={variant} isLoading={loading} aria-busy={loading || undefined} {...rest}>{children}</MyButton>
|
|
195
|
+
)
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The forms stay busy from the press until the work is really over. A sign-in that leaves the form (the password, the second step, a passkey, a provider, SSO, a sign-up done, an invitation accepted) stays busy until the browser leaves the page; with `onSuccess`, return a Promise when the form stays on screen while your application moves on (`onSuccess={() => router.navigate({ to: '/' })}`), and the form waits for it, then is free again if still shown. A plain `onSuccess` keeps it busy until it unmounts. A refusal, a sign-in link sent or a sign-up that asks to confirm the address frees it; so does coming back from the browser's back-forward cache. Until the workspace says what it offers, `<SignIn />` shows its form busy with nothing to press, and `<SignUp />`, `<Account />` and `<OrganizationMembers />` show a spinner alone, centred, with `aria-busy` on their wrapper. Loaders are icons, never words, so they read the same in any language. The plain button draws a spinner before its label (`.cauthy-spinner` in `styles.css`, still under `prefers-reduced-motion`); with `unstyled`, an SVG icon that turns by itself (no stylesheet needed) and stands still under `prefers-reduced-motion`. The icon is `aria-hidden`; the button says `aria-busy`.
|
|
191
199
|
|
|
192
200
|
| Part | Is given |
|
|
193
201
|
| --- | --- |
|
|
194
202
|
| `Form` | `as` (`form` or `div`), `onSubmit`, `noValidate`, `aria-busy`, `className`, `section`, `children`. Render a `<form>` for `as="form"`, for Enter to send it. `section` names each part of `<Account />` (`profile`, `passkeys`… as in `AccountSection`) and of `<OrganizationMembers />` (`members`, `add-member`, `organization`, `rename`), to draw each as a card |
|
|
195
203
|
| `Button` | `type`, `variant` (`primary`, `secondary`, `danger`), `loading`, `disabled`, `className`, `onClick`, `children` |
|
|
196
|
-
| `Input` | `id`, `name`, `type`, `value`, `autoComplete`, `required`, `className`, `aria-invalid`, `aria-describedby`, `onChange(e)` |
|
|
204
|
+
| `Input` | `id`, `name`, `type`, `value`, `autoComplete`, `required`, `disabled`, `className`, `aria-invalid`, `aria-describedby`, `onChange(e)` |
|
|
197
205
|
| `Checkbox` | `id`, `name`, `checked`, `required`, `disabled`, `className`, `onChange(checked)`, `children` (its label) |
|
|
198
206
|
| `Select` | `id`, `name`, `value`, `options: { value, label }[]`, `disabled`, `className`, `aria-label`, `onChange(value)` |
|
|
199
207
|
| `Field` | `id`, `label`, `aside`, `help`, `error`, `required`, `className`, `children` (the control). Give the help the id `${id}-hint` and the error `${id}-error` |
|
|
@@ -212,7 +220,7 @@ What Cauthy mails from these forms (a sign-in link, a new password, a confirmati
|
|
|
212
220
|
<CauthyProvider locale={() => i18n.language}>
|
|
213
221
|
```
|
|
214
222
|
|
|
215
|
-
`texts` changes any sentence. Its keys are stable: `email`, `password`, `name`, `code`, `signIn`, `signUp`, `continueWith`, `orWithEmail`, `forgotPassword`, `noAccount`, `createAccount`, `company`, `iAccept`, `and`, `termsOfService`, `privacyPolicy`, `haveAccount`, `sendLink`, `linkSent`, `codeHelp`, `codeHelpPhone`, `codeHelpEmail`, `sendEmailAgain`, `sendCodeSms`, `sendCodeWhatsapp`, `codeSent`, `pushSend`, `pushPick`, `pushWaiting`, `phone`, `phoneHelp`, `phoneOn`, `phoneOff`, `addPhone`, `removePhone`, `bySms`, `byWhatsapp`, `channel`, `verify`, `checkEmail`, `backToSignIn`, `emailMeALink`, `usePasskey`, `codeFromMail`, `signInLinkSent`, `newPassword`, `savePassword`, `passwordChanged`, `confirming`, `emailConfirmed`, `invitedTo`, `invitedToOrganization`, `invitedAs`, `choosePassword`, `acceptInvite`, `openingInvitation`, `invitationInvalid`, `profile`, `save`, `saved`, `currentPassword`, `changePassword`, `passwordChangedHere`, `secondStep`, `secondStepOn`, `secondStepOff`, `turnOn`, `turnOff`, `addToApp`, `openInApp`, `recoveryCodes`, `newRecoveryCodes`, `keepCodes`, `passkeys`, `managePasskeys`, `passkeysHelp`, `noPasskeys`, `passkeyName`, `addPasskey` (unused), `addPasskeyDevice`, `addSecurityKey`, `passkeyKinds`, `removePasskey`, `passkeyAdded`, `passkeyUnnamed`, `sessions`, `signOutEverywhere`, `agents`, `agentsHelp`, `disconnect`, `anAgent`, `unknownDevice`, `emailAddress`, `newEmail`, `changeEmail`, `emailSent`, `linkedAccounts`, `noLinkedAccounts`, `linkWith`, `unlink`, `linked`, `ssoOnly`, `members`, `membersHidden`, `organizationMissing`, `memberYou`, `memberInvited`, `memberManager`, `memberRole`, `memberAdd`, `memberAdded`, `memberRemove`, `memberRemoveConfirm`, `memberLeave`, `memberLeaveConfirm`, `memberInviteAgain`, `memberInvitationSent`, `organization`, `organizationRename`, `organizationRenamed`, `organizationArchive`, `organizationArchiveConfirm`, `organizationArchived`, `organizationDelete`, `organizationDeleteConfirm`, `organizationDeleted`, `yes`, `cancel`, `termsChanged`, `termsChangedHelp`, `acceptTerms`, and `errors` by error code. Built in: English and Spanish (`locale`). The dashboard can start the look from your stylesheet (shadcn/ui and Tailwind, Stackmyth, or variables of your own); `npx cauthy-auth theme --css src/app/globals.css` reads it the same way from a terminal.
|
|
223
|
+
`texts` changes any sentence. Its keys are stable: `email`, `password`, `name`, `code`, `signIn`, `signUp`, `continueWith`, `orWithEmail`, `forgotPassword`, `noAccount`, `createAccount`, `company`, `iAccept`, `and`, `termsOfService`, `privacyPolicy`, `haveAccount`, `sendLink`, `linkSent`, `codeHelp`, `codeHelpPhone`, `codeHelpEmail`, `sendEmailAgain`, `sendCodeSms`, `sendCodeWhatsapp`, `codeSent`, `pushSend`, `pushPick`, `pushWaiting`, `phone`, `phoneHelp`, `phoneOn`, `phoneOff`, `addPhone`, `removePhone`, `bySms`, `byWhatsapp`, `channel`, `verify`, `checkEmail`, `backToSignIn`, `emailMeALink`, `usePasskey`, `codeFromMail`, `signInLinkSent`, `newPassword`, `savePassword`, `passwordChanged`, `confirming`, `loading`, `emailConfirmed`, `invitedTo`, `invitedToOrganization`, `invitedAs`, `choosePassword`, `acceptInvite`, `openingInvitation`, `invitationInvalid`, `profile`, `save`, `saved`, `currentPassword`, `changePassword`, `passwordChangedHere`, `secondStep`, `secondStepOn`, `secondStepOff`, `turnOn`, `turnOff`, `addToApp`, `openInApp`, `recoveryCodes`, `newRecoveryCodes`, `keepCodes`, `passkeys`, `managePasskeys`, `passkeysHelp`, `noPasskeys`, `passkeyName`, `addPasskey` (unused), `addPasskeyDevice`, `addSecurityKey`, `passkeyKinds`, `removePasskey`, `passkeyAdded`, `passkeyUnnamed`, `sessions`, `signOutEverywhere`, `agents`, `agentsHelp`, `disconnect`, `anAgent`, `unknownDevice`, `emailAddress`, `newEmail`, `changeEmail`, `emailSent`, `linkedAccounts`, `noLinkedAccounts`, `linkWith`, `unlink`, `linked`, `ssoOnly`, `members`, `membersHidden`, `organizationMissing`, `memberYou`, `memberInvited`, `memberManager`, `memberRole`, `memberAdd`, `memberAdded`, `memberRemove`, `memberRemoveConfirm`, `memberLeave`, `memberLeaveConfirm`, `memberInviteAgain`, `memberInvitationSent`, `organization`, `organizationRename`, `organizationRenamed`, `organizationArchive`, `organizationArchiveConfirm`, `organizationArchived`, `organizationDelete`, `organizationDeleteConfirm`, `organizationDeleted`, `yes`, `cancel`, `termsChanged`, `termsChangedHelp`, `acceptTerms`, and `errors` by error code. Built in: English and Spanish (`locale`). The dashboard can start the look from your stylesheet (shadcn/ui and Tailwind, Stackmyth, or variables of your own); `npx cauthy-auth theme --css src/app/globals.css` reads it the same way from a terminal.
|
|
216
224
|
|
|
217
225
|
Connect your application to Cauthy in one step, from Cauthy's dashboard ("Connect a System") or from a terminal. In the application's folder, `init` reads its framework (Next.js, TanStack Start, React Router or Remix, SvelteKit, Vite + React, Express, Hono, Fastify), makes the client, writes every variable to its env file, `CAUTHY_SECRET` included, and lays down the route and a sign-in page, never over a file that is there:
|
|
218
226
|
|
package/dist/react.d.mts
CHANGED
|
@@ -1283,7 +1283,7 @@ interface AuthTexts {
|
|
|
1283
1283
|
/** By error code; one missing shows what the server said. */
|
|
1284
1284
|
errors: Record<string, string>;
|
|
1285
1285
|
}
|
|
1286
|
-
type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
|
|
1286
|
+
type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'pending' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
|
|
1287
1287
|
interface AuthFormProps {
|
|
1288
1288
|
/** The language of the built-in sentences. Default: `en`. */
|
|
1289
1289
|
locale?: 'en' | 'es';
|
|
@@ -1291,8 +1291,14 @@ interface AuthFormProps {
|
|
|
1291
1291
|
texts?: Partial<Omit<AuthTexts, 'errors'>> & {
|
|
1292
1292
|
errors?: Record<string, string>;
|
|
1293
1293
|
};
|
|
1294
|
-
/**
|
|
1295
|
-
|
|
1294
|
+
/**
|
|
1295
|
+
* Called once the person is signed in, instead of going to `returnTo`. The
|
|
1296
|
+
* form stays busy (its button loading, every control disabled) until it
|
|
1297
|
+
* leaves the page. When the application keeps the form on screen while it
|
|
1298
|
+
* moves on (a router's navigation, loading the session), return a Promise:
|
|
1299
|
+
* the form waits for it, then is free again if it is still shown.
|
|
1300
|
+
*/
|
|
1301
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1296
1302
|
/** Where to go after signing in, and where a provider returns to. Default: `/`. */
|
|
1297
1303
|
returnTo?: string;
|
|
1298
1304
|
/**
|
|
@@ -1335,7 +1341,9 @@ interface AuthFormProps {
|
|
|
1335
1341
|
* other action (a provider, a passkey, a sign-in link, cancel, send again);
|
|
1336
1342
|
* `danger` removes, leaves, deletes, disconnects or turns something off.
|
|
1337
1343
|
* `loading` is true on the one button whose action is under way; every
|
|
1338
|
-
* button is `disabled` meanwhile
|
|
1344
|
+
* button is `disabled` meanwhile. Draw a spinner (or the like) on the button
|
|
1345
|
+
* while `loading` is true, and keep its label: after a sign-in it stays true
|
|
1346
|
+
* until the page is left, so the person sees the work go on.
|
|
1339
1347
|
*/
|
|
1340
1348
|
interface AuthButtonProps {
|
|
1341
1349
|
type: 'submit' | 'button';
|
|
@@ -1376,6 +1384,7 @@ interface AuthInputProps {
|
|
|
1376
1384
|
value: string;
|
|
1377
1385
|
autoComplete?: string;
|
|
1378
1386
|
required?: boolean;
|
|
1387
|
+
disabled?: boolean;
|
|
1379
1388
|
className?: string;
|
|
1380
1389
|
'aria-invalid'?: boolean;
|
|
1381
1390
|
'aria-describedby'?: string;
|
|
@@ -1533,8 +1542,11 @@ interface AcceptInviteProps extends AuthFormProps {
|
|
|
1533
1542
|
}
|
|
1534
1543
|
declare function AcceptInvite(props: AcceptInviteProps): react.JSX.Element;
|
|
1535
1544
|
interface PendingTermsProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'createAccounts'> {
|
|
1536
|
-
/**
|
|
1537
|
-
|
|
1545
|
+
/**
|
|
1546
|
+
* Called once the person accepted. Nothing else happens: the application
|
|
1547
|
+
* decides what follows. A Promise keeps the button loading until it settles.
|
|
1548
|
+
*/
|
|
1549
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1538
1550
|
}
|
|
1539
1551
|
/**
|
|
1540
1552
|
* The legal documents the signed-in person still owes (a new version of the
|
|
@@ -1587,8 +1599,8 @@ type AccountEvent = {
|
|
|
1587
1599
|
kind: 'sessions.ended';
|
|
1588
1600
|
};
|
|
1589
1601
|
interface AccountProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'onSuccess'> {
|
|
1590
|
-
/** Called after "sign out everywhere". Default: go to `/`. */
|
|
1591
|
-
onSignedOut?: () => void
|
|
1602
|
+
/** Called after "sign out everywhere". Default: go to `/`. A Promise keeps the button loading until it settles. */
|
|
1603
|
+
onSignedOut?: () => void | Promise<unknown>;
|
|
1592
1604
|
/** The moment of a session, as the application writes dates. Default: the browser's own way, in the reader's time zone. */
|
|
1593
1605
|
formatDate?: (iso: string) => string;
|
|
1594
1606
|
/**
|
package/dist/react.d.ts
CHANGED
|
@@ -1283,7 +1283,7 @@ interface AuthTexts {
|
|
|
1283
1283
|
/** By error code; one missing shows what the server said. */
|
|
1284
1284
|
errors: Record<string, string>;
|
|
1285
1285
|
}
|
|
1286
|
-
type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
|
|
1286
|
+
type Slot = 'form' | 'field' | 'label' | 'input' | 'button' | 'providerButton' | 'providerIcon' | 'link' | 'error' | 'fieldError' | 'hint' | 'notice' | 'pending' | 'separator' | 'footer' | 'check' | 'account' | 'section' | 'heading' | 'secret' | 'codes' | 'sessions' | 'session';
|
|
1287
1287
|
interface AuthFormProps {
|
|
1288
1288
|
/** The language of the built-in sentences. Default: `en`. */
|
|
1289
1289
|
locale?: 'en' | 'es';
|
|
@@ -1291,8 +1291,14 @@ interface AuthFormProps {
|
|
|
1291
1291
|
texts?: Partial<Omit<AuthTexts, 'errors'>> & {
|
|
1292
1292
|
errors?: Record<string, string>;
|
|
1293
1293
|
};
|
|
1294
|
-
/**
|
|
1295
|
-
|
|
1294
|
+
/**
|
|
1295
|
+
* Called once the person is signed in, instead of going to `returnTo`. The
|
|
1296
|
+
* form stays busy (its button loading, every control disabled) until it
|
|
1297
|
+
* leaves the page. When the application keeps the form on screen while it
|
|
1298
|
+
* moves on (a router's navigation, loading the session), return a Promise:
|
|
1299
|
+
* the form waits for it, then is free again if it is still shown.
|
|
1300
|
+
*/
|
|
1301
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1296
1302
|
/** Where to go after signing in, and where a provider returns to. Default: `/`. */
|
|
1297
1303
|
returnTo?: string;
|
|
1298
1304
|
/**
|
|
@@ -1335,7 +1341,9 @@ interface AuthFormProps {
|
|
|
1335
1341
|
* other action (a provider, a passkey, a sign-in link, cancel, send again);
|
|
1336
1342
|
* `danger` removes, leaves, deletes, disconnects or turns something off.
|
|
1337
1343
|
* `loading` is true on the one button whose action is under way; every
|
|
1338
|
-
* button is `disabled` meanwhile
|
|
1344
|
+
* button is `disabled` meanwhile. Draw a spinner (or the like) on the button
|
|
1345
|
+
* while `loading` is true, and keep its label: after a sign-in it stays true
|
|
1346
|
+
* until the page is left, so the person sees the work go on.
|
|
1339
1347
|
*/
|
|
1340
1348
|
interface AuthButtonProps {
|
|
1341
1349
|
type: 'submit' | 'button';
|
|
@@ -1376,6 +1384,7 @@ interface AuthInputProps {
|
|
|
1376
1384
|
value: string;
|
|
1377
1385
|
autoComplete?: string;
|
|
1378
1386
|
required?: boolean;
|
|
1387
|
+
disabled?: boolean;
|
|
1379
1388
|
className?: string;
|
|
1380
1389
|
'aria-invalid'?: boolean;
|
|
1381
1390
|
'aria-describedby'?: string;
|
|
@@ -1533,8 +1542,11 @@ interface AcceptInviteProps extends AuthFormProps {
|
|
|
1533
1542
|
}
|
|
1534
1543
|
declare function AcceptInvite(props: AcceptInviteProps): react.JSX.Element;
|
|
1535
1544
|
interface PendingTermsProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'createAccounts'> {
|
|
1536
|
-
/**
|
|
1537
|
-
|
|
1545
|
+
/**
|
|
1546
|
+
* Called once the person accepted. Nothing else happens: the application
|
|
1547
|
+
* decides what follows. A Promise keeps the button loading until it settles.
|
|
1548
|
+
*/
|
|
1549
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1538
1550
|
}
|
|
1539
1551
|
/**
|
|
1540
1552
|
* The legal documents the signed-in person still owes (a new version of the
|
|
@@ -1587,8 +1599,8 @@ type AccountEvent = {
|
|
|
1587
1599
|
kind: 'sessions.ended';
|
|
1588
1600
|
};
|
|
1589
1601
|
interface AccountProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'onSuccess'> {
|
|
1590
|
-
/** Called after "sign out everywhere". Default: go to `/`. */
|
|
1591
|
-
onSignedOut?: () => void
|
|
1602
|
+
/** Called after "sign out everywhere". Default: go to `/`. A Promise keeps the button loading until it settles. */
|
|
1603
|
+
onSignedOut?: () => void | Promise<unknown>;
|
|
1592
1604
|
/** The moment of a session, as the application writes dates. Default: the browser's own way, in the reader's time zone. */
|
|
1593
1605
|
formatDate?: (iso: string) => string;
|
|
1594
1606
|
/**
|
package/dist/react.js
CHANGED
|
@@ -1136,7 +1136,38 @@ var TEXTS = {
|
|
|
1136
1136
|
}
|
|
1137
1137
|
}
|
|
1138
1138
|
};
|
|
1139
|
-
var PlainButton = ({ variant: _variant, loading
|
|
1139
|
+
var PlainButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { ...p, "aria-busy": loading || void 0, children: [
|
|
1140
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {}),
|
|
1141
|
+
children
|
|
1142
|
+
] });
|
|
1143
|
+
var UnstyledButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { ...p, "aria-busy": loading || void 0, children: [
|
|
1144
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { plain: true }),
|
|
1146
|
+
" "
|
|
1147
|
+
] }),
|
|
1148
|
+
children
|
|
1149
|
+
] });
|
|
1150
|
+
function Spinner({ plain }) {
|
|
1151
|
+
if (!plain) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cauthy-spinner", "aria-hidden": "true" });
|
|
1152
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerIcon, {});
|
|
1153
|
+
}
|
|
1154
|
+
var LESS_MOTION = "(prefers-reduced-motion: reduce)";
|
|
1155
|
+
function watchMotion(changed) {
|
|
1156
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {
|
|
1157
|
+
};
|
|
1158
|
+
const query = window.matchMedia(LESS_MOTION);
|
|
1159
|
+
query.addEventListener?.("change", changed);
|
|
1160
|
+
return () => query.removeEventListener?.("change", changed);
|
|
1161
|
+
}
|
|
1162
|
+
var stillHere = () => typeof window.matchMedia === "function" && window.matchMedia(LESS_MOTION).matches;
|
|
1163
|
+
var stillOnServer = () => true;
|
|
1164
|
+
function SpinnerIcon() {
|
|
1165
|
+
const still = (0, import_react.useSyncExternalStore)(watchMotion, stillHere, stillOnServer);
|
|
1166
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { className: "cauthy-spinner-icon", width: "1em", height: "1em", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", focusable: "false", children: [
|
|
1167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "8", cy: "8", r: "6.5", stroke: "currentColor", strokeOpacity: "0.25", strokeWidth: "2" }),
|
|
1168
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M14.5 8A6.5 6.5 0 0 0 8 1.5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: !still && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("animateTransform", { attributeName: "transform", type: "rotate", from: "0 8 8", to: "360 8 8", dur: "0.7s", repeatCount: "indefinite" }) })
|
|
1169
|
+
] });
|
|
1170
|
+
}
|
|
1140
1171
|
var PlainInput = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ...p });
|
|
1141
1172
|
var looks = /* @__PURE__ */ new Map();
|
|
1142
1173
|
var loaded = /* @__PURE__ */ new Set();
|
|
@@ -1244,12 +1275,15 @@ function sameOrigin(to) {
|
|
|
1244
1275
|
return void 0;
|
|
1245
1276
|
}
|
|
1246
1277
|
}
|
|
1247
|
-
function
|
|
1278
|
+
function isPending(v) {
|
|
1279
|
+
return typeof v?.then === "function";
|
|
1280
|
+
}
|
|
1281
|
+
function useForm(props, opts = {}) {
|
|
1248
1282
|
const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
|
|
1249
1283
|
const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
|
|
1250
1284
|
const own = (slot) => props.classNames?.[slot];
|
|
1251
1285
|
const C = props.components ?? {};
|
|
1252
|
-
const Button = C.Button ?? PlainButton;
|
|
1286
|
+
const Button = C.Button ?? (props.unstyled ? UnstyledButton : PlainButton);
|
|
1253
1287
|
const Input = C.Input ?? PlainInput;
|
|
1254
1288
|
const [error, setOnlyError] = (0, import_react.useState)(null);
|
|
1255
1289
|
const [problems, setProblems] = (0, import_react.useState)({ list: [], round: 0 });
|
|
@@ -1266,9 +1300,32 @@ function useForm(props) {
|
|
|
1266
1300
|
const fieldError = (name) => problems.list.find((e) => e.field === name) ?? (error?.field === name ? error : null);
|
|
1267
1301
|
const firstWrong = (name) => problems.list[0]?.field === name ? problems.round : 0;
|
|
1268
1302
|
const [acting, setActing] = (0, import_react.useState)(null);
|
|
1269
|
-
const busy = acting !== null;
|
|
1303
|
+
const busy = acting !== null || opts.waiting === true;
|
|
1270
1304
|
const setBusy = (0, import_react.useCallback)((on) => setActing(on === false ? null : on === true ? SUBMIT : on), []);
|
|
1271
1305
|
const loading = (key = SUBMIT) => acting === key;
|
|
1306
|
+
const shown = (0, import_react.useRef)(true);
|
|
1307
|
+
(0, import_react.useEffect)(() => {
|
|
1308
|
+
shown.current = true;
|
|
1309
|
+
const back = (e) => {
|
|
1310
|
+
if (e.persisted) setActing(null);
|
|
1311
|
+
};
|
|
1312
|
+
window.addEventListener("pageshow", back);
|
|
1313
|
+
return () => {
|
|
1314
|
+
shown.current = false;
|
|
1315
|
+
window.removeEventListener("pageshow", back);
|
|
1316
|
+
};
|
|
1317
|
+
}, []);
|
|
1318
|
+
const hold = (after) => {
|
|
1319
|
+
if (!isPending(after)) return;
|
|
1320
|
+
const free = () => {
|
|
1321
|
+
if (shown.current) setActing(null);
|
|
1322
|
+
};
|
|
1323
|
+
after.then(free, free);
|
|
1324
|
+
};
|
|
1325
|
+
const fail = (e) => {
|
|
1326
|
+
setActing(null);
|
|
1327
|
+
setError(e);
|
|
1328
|
+
};
|
|
1272
1329
|
const [policy, setPolicy] = (0, import_react.useState)(void 0);
|
|
1273
1330
|
const locale = props.locale ?? "en";
|
|
1274
1331
|
const say = (e) => {
|
|
@@ -1277,15 +1334,18 @@ function useForm(props) {
|
|
|
1277
1334
|
};
|
|
1278
1335
|
const passwordRule = describePasswordPolicy(policy, locale);
|
|
1279
1336
|
const done = (to) => {
|
|
1280
|
-
|
|
1337
|
+
setActing((a) => a ?? SUBMIT);
|
|
1338
|
+
if (props.onSuccess) hold(props.onSuccess());
|
|
1281
1339
|
else window.location.assign(sitePath(to) ?? sameOrigin(props.returnTo) ?? "/");
|
|
1282
1340
|
};
|
|
1283
|
-
const root = useRoot(props);
|
|
1284
|
-
|
|
1341
|
+
const root = useRoot(opts.part ? { ...props, unstyled: true } : props);
|
|
1342
|
+
const plain = props.unstyled === true;
|
|
1343
|
+
return { t, cx, own, C, Button, Input, error, setError, fail, check, fieldError, firstWrong, busy, setBusy, loading, hold, say, done, root, plain, policy, setPolicy, passwordRule };
|
|
1285
1344
|
}
|
|
1286
1345
|
var SUBMIT = "submit";
|
|
1287
1346
|
function Wrap(p) {
|
|
1288
1347
|
const { Form: OwnForm } = p.f.C;
|
|
1348
|
+
const busy = p.busy ?? (p.f.busy || void 0);
|
|
1289
1349
|
if (OwnForm) {
|
|
1290
1350
|
const own = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1291
1351
|
OwnForm,
|
|
@@ -1293,7 +1353,7 @@ function Wrap(p) {
|
|
|
1293
1353
|
as: p.as === "form" ? "form" : "div",
|
|
1294
1354
|
onSubmit: p.onSubmit,
|
|
1295
1355
|
noValidate: p.noValidate,
|
|
1296
|
-
"aria-busy":
|
|
1356
|
+
"aria-busy": busy,
|
|
1297
1357
|
className: p.slot ? p.f.own(p.slot) : void 0,
|
|
1298
1358
|
section: p.section,
|
|
1299
1359
|
children: p.children
|
|
@@ -1302,7 +1362,11 @@ function Wrap(p) {
|
|
|
1302
1362
|
return p.root?.["data-cauthy"] !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...p.root, children: own }) : own;
|
|
1303
1363
|
}
|
|
1304
1364
|
const Tag2 = p.as;
|
|
1305
|
-
|
|
1365
|
+
const children = p.busy && p.children === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pending, { f: p.f }) : p.children;
|
|
1366
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tag2, { className: p.slot ? p.f.cx(p.slot) : void 0, ...p.root, onSubmit: p.onSubmit, noValidate: p.noValidate, "aria-busy": busy, children });
|
|
1367
|
+
}
|
|
1368
|
+
function Pending({ f }) {
|
|
1369
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: f.cx("pending"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, { plain: f.plain }) });
|
|
1306
1370
|
}
|
|
1307
1371
|
function Note({ f, children, footer }) {
|
|
1308
1372
|
const { Text } = f.C;
|
|
@@ -1343,10 +1407,10 @@ function Check(p) {
|
|
|
1343
1407
|
const { f } = p;
|
|
1344
1408
|
const { Checkbox } = f.C;
|
|
1345
1409
|
if (Checkbox) {
|
|
1346
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox, { id, name: p.name, checked: p.checked, required: p.required, className: f.own("check"), onChange: p.onChange, children: p.children });
|
|
1410
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Checkbox, { id, name: p.name, checked: p.checked, required: p.required, disabled: f.busy, className: f.own("check"), onChange: p.onChange, children: p.children });
|
|
1347
1411
|
}
|
|
1348
1412
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("check"), children: [
|
|
1349
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", name: p.name, checked: p.checked, required: p.required, onChange: (e) => p.onChange(e.target.checked) }),
|
|
1413
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "checkbox", name: p.name, checked: p.checked, required: p.required, disabled: f.busy, onChange: (e) => p.onChange(e.target.checked) }),
|
|
1350
1414
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: p.children })
|
|
1351
1415
|
] });
|
|
1352
1416
|
}
|
|
@@ -1355,10 +1419,11 @@ function Pick(p) {
|
|
|
1355
1419
|
const id = p.id ?? made;
|
|
1356
1420
|
const { f } = p;
|
|
1357
1421
|
const { Select } = f.C;
|
|
1422
|
+
const disabled = p.disabled || f.busy;
|
|
1358
1423
|
if (Select) {
|
|
1359
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Select, { id, name: p.name, value: p.value, options: p.options, disabled
|
|
1424
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Select, { id, name: p.name, value: p.value, options: p.options, disabled, "aria-label": p.label, className: f.own("input"), onChange: p.onChange });
|
|
1360
1425
|
}
|
|
1361
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { id, name: p.name, "aria-label": p.label, className: f.cx("input"), value: p.value, disabled
|
|
1426
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("select", { id, name: p.name, "aria-label": p.label, className: f.cx("input"), value: p.value, disabled, onChange: (e) => p.onChange(e.target.value), children: p.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: o.value, children: o.label }, o.value)) });
|
|
1362
1427
|
}
|
|
1363
1428
|
function Labelled({ f, id, label, children }) {
|
|
1364
1429
|
const { Field: OwnField } = f.C;
|
|
@@ -1412,6 +1477,7 @@ function Field(p) {
|
|
|
1412
1477
|
value: p.value,
|
|
1413
1478
|
autoComplete: p.autoComplete,
|
|
1414
1479
|
required: p.required,
|
|
1480
|
+
disabled: f.busy,
|
|
1415
1481
|
className: f.cx("input"),
|
|
1416
1482
|
"aria-invalid": bad ? true : void 0,
|
|
1417
1483
|
"aria-describedby": described || void 0,
|
|
@@ -1501,6 +1567,7 @@ function SecondStepForm(p) {
|
|
|
1501
1567
|
if (ctl.signal.aborted) return;
|
|
1502
1568
|
setPushNumber(0);
|
|
1503
1569
|
if (!done.ok) return f.setError(done.error);
|
|
1570
|
+
f.setBusy("push");
|
|
1504
1571
|
f.done();
|
|
1505
1572
|
};
|
|
1506
1573
|
const sendCode = async () => {
|
|
@@ -1516,8 +1583,7 @@ function SecondStepForm(p) {
|
|
|
1516
1583
|
f.setBusy(true);
|
|
1517
1584
|
f.setError(null);
|
|
1518
1585
|
const r = await auth.signIn.mfa({ code });
|
|
1519
|
-
f.
|
|
1520
|
-
if (!r.ok) return f.setError(r.error);
|
|
1586
|
+
if (!r.ok) return f.fail(r.error);
|
|
1521
1587
|
f.done();
|
|
1522
1588
|
};
|
|
1523
1589
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -1545,7 +1611,11 @@ function SecondStepForm(p) {
|
|
|
1545
1611
|
}
|
|
1546
1612
|
function ProviderButtons(p) {
|
|
1547
1613
|
const { f } = p;
|
|
1548
|
-
|
|
1614
|
+
const pick = (id) => {
|
|
1615
|
+
f.setBusy(`provider:${id}`);
|
|
1616
|
+
p.onPick(id);
|
|
1617
|
+
};
|
|
1618
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: p.providers.map((provider) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(f.Button, { type: "button", variant: "secondary", loading: f.loading(`provider:${provider.id}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => pick(provider.id), children: [
|
|
1549
1619
|
provider.iconUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: provider.iconUrl, alt: "", width: 18, height: 18, className: f.cx("providerIcon") }),
|
|
1550
1620
|
f.t.continueWith,
|
|
1551
1621
|
" ",
|
|
@@ -1558,8 +1628,8 @@ function signInOffers(methods) {
|
|
|
1558
1628
|
}
|
|
1559
1629
|
function SignIn(props) {
|
|
1560
1630
|
const auth = useAuth();
|
|
1561
|
-
const f = useForm(props);
|
|
1562
1631
|
const [methods, setMethods] = (0, import_react.useState)(null);
|
|
1632
|
+
const f = useForm(props, { waiting: methods === null });
|
|
1563
1633
|
const [email, setEmail] = (0, import_react.useState)("");
|
|
1564
1634
|
const [password, setPassword] = (0, import_react.useState)("");
|
|
1565
1635
|
const [code, setCode] = (0, import_react.useState)("");
|
|
@@ -1631,14 +1701,16 @@ function SignIn(props) {
|
|
|
1631
1701
|
f.setBusy(true);
|
|
1632
1702
|
f.setError(null);
|
|
1633
1703
|
const r = step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
|
|
1634
|
-
f.setBusy(false);
|
|
1635
1704
|
if (step === "password") challenge.renew();
|
|
1636
1705
|
if (!r.ok && r.error.code === "sso_required") return toCompany();
|
|
1637
1706
|
if (!r.ok) {
|
|
1638
1707
|
setOffer((n) => n + 1);
|
|
1639
|
-
return f.
|
|
1708
|
+
return f.fail(r.error);
|
|
1709
|
+
}
|
|
1710
|
+
if (r.status === "mfa_required") {
|
|
1711
|
+
f.setBusy(false);
|
|
1712
|
+
return setStep("code");
|
|
1640
1713
|
}
|
|
1641
|
-
if (r.status === "mfa_required") return setStep("code");
|
|
1642
1714
|
f.done("returnTo" in r && typeof r.returnTo === "string" ? r.returnTo : void 0);
|
|
1643
1715
|
};
|
|
1644
1716
|
const usePasskey = async () => {
|
|
@@ -1647,10 +1719,10 @@ function SignIn(props) {
|
|
|
1647
1719
|
f.setError(null);
|
|
1648
1720
|
const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale, email });
|
|
1649
1721
|
if (r.ok && r.status === "redirected") return;
|
|
1722
|
+
if (r.ok) return f.done();
|
|
1650
1723
|
f.setBusy(false);
|
|
1651
|
-
|
|
1652
|
-
if (
|
|
1653
|
-
f.done();
|
|
1724
|
+
setOffer((n) => n + 1);
|
|
1725
|
+
if (r.error.code !== "cancelled") f.setError(r.error);
|
|
1654
1726
|
};
|
|
1655
1727
|
const sendLink = async () => {
|
|
1656
1728
|
autofill.current?.abort();
|
|
@@ -1767,10 +1839,12 @@ function SignUp(props) {
|
|
|
1767
1839
|
...legal ? { acceptLegal, consents } : {},
|
|
1768
1840
|
captchaToken: challenge.token
|
|
1769
1841
|
});
|
|
1770
|
-
f.setBusy(false);
|
|
1771
1842
|
challenge.renew();
|
|
1772
|
-
if (!r.ok) return f.
|
|
1773
|
-
if (r.status === "verification_required")
|
|
1843
|
+
if (!r.ok) return f.fail(r.error);
|
|
1844
|
+
if (r.status === "verification_required") {
|
|
1845
|
+
f.setBusy(false);
|
|
1846
|
+
return setSent(true);
|
|
1847
|
+
}
|
|
1774
1848
|
f.done();
|
|
1775
1849
|
};
|
|
1776
1850
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -1933,13 +2007,16 @@ function AcceptInvite(props) {
|
|
|
1933
2007
|
f.setBusy(true);
|
|
1934
2008
|
f.setError(null);
|
|
1935
2009
|
const r = await auth.acceptInvitation(token, password);
|
|
1936
|
-
f.setBusy(false);
|
|
1937
2010
|
if (!r.ok) {
|
|
2011
|
+
f.setBusy(false);
|
|
1938
2012
|
if (r.error.code === "invalid_token") return setState("invalid");
|
|
1939
2013
|
return f.setError(r.error);
|
|
1940
2014
|
}
|
|
1941
2015
|
props.onSaved?.({ kind: "invitation.accepted", email: invitation?.email ?? "", ...invitation?.organization ? { organization: invitation.organization } : {} });
|
|
1942
|
-
if (r.status === "mfa_required")
|
|
2016
|
+
if (r.status === "mfa_required") {
|
|
2017
|
+
f.setBusy(false);
|
|
2018
|
+
return setState("code");
|
|
2019
|
+
}
|
|
1943
2020
|
f.done();
|
|
1944
2021
|
};
|
|
1945
2022
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -1977,10 +2054,11 @@ function PendingTerms(props) {
|
|
|
1977
2054
|
f.setBusy(true);
|
|
1978
2055
|
f.setError(null);
|
|
1979
2056
|
const r = await auth.account.acceptLegal({ kinds: owed.map((d) => d.kind) });
|
|
2057
|
+
if (!r.ok) return f.fail(r.error);
|
|
2058
|
+
const after = props.onSuccess?.();
|
|
2059
|
+
if (isPending(after)) await Promise.resolve(after).catch(() => void 0);
|
|
1980
2060
|
f.setBusy(false);
|
|
1981
|
-
if (!r.ok) return f.setError(r.error);
|
|
1982
2061
|
setOwed([]);
|
|
1983
|
-
props.onSuccess?.();
|
|
1984
2062
|
};
|
|
1985
2063
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
|
|
1986
2064
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Heading, { f, children: f.t.termsChanged }),
|
|
@@ -1995,15 +2073,15 @@ function PendingTerms(props) {
|
|
|
1995
2073
|
}
|
|
1996
2074
|
var ACCOUNT_SECTIONS = ["profile", "email", "password", "linked", "secondStep", "phone", "passkeys", "agents", "sessions"];
|
|
1997
2075
|
function useSection(props) {
|
|
1998
|
-
const f = useForm(
|
|
2076
|
+
const f = useForm(props, { part: true });
|
|
1999
2077
|
const [done, setDone] = (0, import_react.useState)(null);
|
|
2000
|
-
const run = async (action, key = SUBMIT) => {
|
|
2078
|
+
const run = async (action, key = SUBMIT, leaves = false) => {
|
|
2001
2079
|
f.setBusy(key);
|
|
2002
2080
|
f.setError(null);
|
|
2003
2081
|
setDone(null);
|
|
2004
2082
|
const r = await action();
|
|
2005
|
-
f.
|
|
2006
|
-
if (!
|
|
2083
|
+
if (!r.ok) f.fail(r.error);
|
|
2084
|
+
else if (!leaves) f.setBusy(false);
|
|
2007
2085
|
return r;
|
|
2008
2086
|
};
|
|
2009
2087
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -2416,11 +2494,26 @@ function LinkedPart({ props }) {
|
|
|
2416
2494
|
" ",
|
|
2417
2495
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(f.Button, { type: "button", variant: "danger", loading: f.loading(`unlink:${x.provider}`), disabled: f.busy, className: f.cx("providerButton"), onClick: () => void unlink(x.provider), children: f.t.unlink })
|
|
2418
2496
|
] }, x.provider)) }),
|
|
2419
|
-
offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2420
|
-
f.
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2497
|
+
offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2498
|
+
f.Button,
|
|
2499
|
+
{
|
|
2500
|
+
type: "button",
|
|
2501
|
+
variant: "secondary",
|
|
2502
|
+
loading: f.loading(`link:${p.id}`),
|
|
2503
|
+
disabled: f.busy,
|
|
2504
|
+
className: f.cx("providerButton"),
|
|
2505
|
+
onClick: () => {
|
|
2506
|
+
f.setBusy(`link:${p.id}`);
|
|
2507
|
+
auth.account.link(p.id, { returnTo: here });
|
|
2508
|
+
},
|
|
2509
|
+
children: [
|
|
2510
|
+
f.t.linkWith,
|
|
2511
|
+
" ",
|
|
2512
|
+
p.name
|
|
2513
|
+
]
|
|
2514
|
+
},
|
|
2515
|
+
p.id
|
|
2516
|
+
))
|
|
2424
2517
|
] });
|
|
2425
2518
|
}
|
|
2426
2519
|
function PasswordPart({ props }) {
|
|
@@ -2578,7 +2671,7 @@ function PhonePart({ props, account, onChanged }) {
|
|
|
2578
2671
|
!ways.phone && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, { f, name: "phone", type: "tel", label: f.t.phone, value: number, autoComplete: "tel", required: true, onChange: setNumber }),
|
|
2579
2672
|
!ways.phone && ways.phoneChannels.length > 1 && f.C.Select && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChannelPick, { f, value: channel, channels: ways.phoneChannels, onChange: setChannel }),
|
|
2580
2673
|
!ways.phone && ways.phoneChannels.length > 1 && !f.C.Select && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: f.cx("field"), role: "radiogroup", children: ways.phoneChannels.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: f.cx("label"), children: [
|
|
2581
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "radio", name: "channel", value: c, checked: channel === c, onChange: () => setChannel(c) }),
|
|
2674
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "radio", name: "channel", value: c, checked: channel === c, disabled: f.busy, onChange: () => setChannel(c) }),
|
|
2582
2675
|
" ",
|
|
2583
2676
|
c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
|
|
2584
2677
|
] }, c)) }),
|
|
@@ -2625,10 +2718,10 @@ function SessionsPart({ props, when }) {
|
|
|
2625
2718
|
void auth.account.sessions().then(setSessions);
|
|
2626
2719
|
}, []);
|
|
2627
2720
|
const end = async () => {
|
|
2628
|
-
const r = await run(() => auth.account.signOutEverywhere());
|
|
2721
|
+
const r = await run(() => auth.account.signOutEverywhere(), SUBMIT, true);
|
|
2629
2722
|
if (!r.ok) return;
|
|
2630
2723
|
props.onSaved?.({ kind: "sessions.ended" });
|
|
2631
|
-
if (props.onSignedOut) props.onSignedOut();
|
|
2724
|
+
if (props.onSignedOut) f.hold(props.onSignedOut());
|
|
2632
2725
|
else window.location.assign("/");
|
|
2633
2726
|
};
|
|
2634
2727
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Wrap, { f, as: "section", slot: "section", section: "sessions", children: [
|