cauthy-auth 0.34.0 → 0.35.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/README.md +11 -3
- package/dist/react.d.mts +22 -8
- package/dist/react.d.ts +22 -8
- package/dist/react.js +118 -42
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +118 -42
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +43 -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 />` say "Loading…" (`texts.loading`). The plain button draws a spinner before its label (`.cauthy-spinner` in `styles.css`, still under `prefers-reduced-motion`); with `unstyled`, it adds "…" after the label.
|
|
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
|
@@ -1197,6 +1197,8 @@ interface AuthTexts {
|
|
|
1197
1197
|
savePassword: string;
|
|
1198
1198
|
passwordChanged: string;
|
|
1199
1199
|
confirming: string;
|
|
1200
|
+
/** While a form waits on what the workspace offers, or on the account it shows. */
|
|
1201
|
+
loading: string;
|
|
1200
1202
|
emailConfirmed: string;
|
|
1201
1203
|
invitedTo: string;
|
|
1202
1204
|
invitedToOrganization: string;
|
|
@@ -1283,7 +1285,7 @@ interface AuthTexts {
|
|
|
1283
1285
|
/** By error code; one missing shows what the server said. */
|
|
1284
1286
|
errors: Record<string, string>;
|
|
1285
1287
|
}
|
|
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';
|
|
1288
|
+
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
1289
|
interface AuthFormProps {
|
|
1288
1290
|
/** The language of the built-in sentences. Default: `en`. */
|
|
1289
1291
|
locale?: 'en' | 'es';
|
|
@@ -1291,8 +1293,14 @@ interface AuthFormProps {
|
|
|
1291
1293
|
texts?: Partial<Omit<AuthTexts, 'errors'>> & {
|
|
1292
1294
|
errors?: Record<string, string>;
|
|
1293
1295
|
};
|
|
1294
|
-
/**
|
|
1295
|
-
|
|
1296
|
+
/**
|
|
1297
|
+
* Called once the person is signed in, instead of going to `returnTo`. The
|
|
1298
|
+
* form stays busy (its button loading, every control disabled) until it
|
|
1299
|
+
* leaves the page. When the application keeps the form on screen while it
|
|
1300
|
+
* moves on (a router's navigation, loading the session), return a Promise:
|
|
1301
|
+
* the form waits for it, then is free again if it is still shown.
|
|
1302
|
+
*/
|
|
1303
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1296
1304
|
/** Where to go after signing in, and where a provider returns to. Default: `/`. */
|
|
1297
1305
|
returnTo?: string;
|
|
1298
1306
|
/**
|
|
@@ -1335,7 +1343,9 @@ interface AuthFormProps {
|
|
|
1335
1343
|
* other action (a provider, a passkey, a sign-in link, cancel, send again);
|
|
1336
1344
|
* `danger` removes, leaves, deletes, disconnects or turns something off.
|
|
1337
1345
|
* `loading` is true on the one button whose action is under way; every
|
|
1338
|
-
* button is `disabled` meanwhile
|
|
1346
|
+
* button is `disabled` meanwhile. Draw a spinner (or the like) on the button
|
|
1347
|
+
* while `loading` is true, and keep its label: after a sign-in it stays true
|
|
1348
|
+
* until the page is left, so the person sees the work go on.
|
|
1339
1349
|
*/
|
|
1340
1350
|
interface AuthButtonProps {
|
|
1341
1351
|
type: 'submit' | 'button';
|
|
@@ -1376,6 +1386,7 @@ interface AuthInputProps {
|
|
|
1376
1386
|
value: string;
|
|
1377
1387
|
autoComplete?: string;
|
|
1378
1388
|
required?: boolean;
|
|
1389
|
+
disabled?: boolean;
|
|
1379
1390
|
className?: string;
|
|
1380
1391
|
'aria-invalid'?: boolean;
|
|
1381
1392
|
'aria-describedby'?: string;
|
|
@@ -1533,8 +1544,11 @@ interface AcceptInviteProps extends AuthFormProps {
|
|
|
1533
1544
|
}
|
|
1534
1545
|
declare function AcceptInvite(props: AcceptInviteProps): react.JSX.Element;
|
|
1535
1546
|
interface PendingTermsProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'createAccounts'> {
|
|
1536
|
-
/**
|
|
1537
|
-
|
|
1547
|
+
/**
|
|
1548
|
+
* Called once the person accepted. Nothing else happens: the application
|
|
1549
|
+
* decides what follows. A Promise keeps the button loading until it settles.
|
|
1550
|
+
*/
|
|
1551
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1538
1552
|
}
|
|
1539
1553
|
/**
|
|
1540
1554
|
* The legal documents the signed-in person still owes (a new version of the
|
|
@@ -1587,8 +1601,8 @@ type AccountEvent = {
|
|
|
1587
1601
|
kind: 'sessions.ended';
|
|
1588
1602
|
};
|
|
1589
1603
|
interface AccountProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'onSuccess'> {
|
|
1590
|
-
/** Called after "sign out everywhere". Default: go to `/`. */
|
|
1591
|
-
onSignedOut?: () => void
|
|
1604
|
+
/** Called after "sign out everywhere". Default: go to `/`. A Promise keeps the button loading until it settles. */
|
|
1605
|
+
onSignedOut?: () => void | Promise<unknown>;
|
|
1592
1606
|
/** The moment of a session, as the application writes dates. Default: the browser's own way, in the reader's time zone. */
|
|
1593
1607
|
formatDate?: (iso: string) => string;
|
|
1594
1608
|
/**
|
package/dist/react.d.ts
CHANGED
|
@@ -1197,6 +1197,8 @@ interface AuthTexts {
|
|
|
1197
1197
|
savePassword: string;
|
|
1198
1198
|
passwordChanged: string;
|
|
1199
1199
|
confirming: string;
|
|
1200
|
+
/** While a form waits on what the workspace offers, or on the account it shows. */
|
|
1201
|
+
loading: string;
|
|
1200
1202
|
emailConfirmed: string;
|
|
1201
1203
|
invitedTo: string;
|
|
1202
1204
|
invitedToOrganization: string;
|
|
@@ -1283,7 +1285,7 @@ interface AuthTexts {
|
|
|
1283
1285
|
/** By error code; one missing shows what the server said. */
|
|
1284
1286
|
errors: Record<string, string>;
|
|
1285
1287
|
}
|
|
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';
|
|
1288
|
+
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
1289
|
interface AuthFormProps {
|
|
1288
1290
|
/** The language of the built-in sentences. Default: `en`. */
|
|
1289
1291
|
locale?: 'en' | 'es';
|
|
@@ -1291,8 +1293,14 @@ interface AuthFormProps {
|
|
|
1291
1293
|
texts?: Partial<Omit<AuthTexts, 'errors'>> & {
|
|
1292
1294
|
errors?: Record<string, string>;
|
|
1293
1295
|
};
|
|
1294
|
-
/**
|
|
1295
|
-
|
|
1296
|
+
/**
|
|
1297
|
+
* Called once the person is signed in, instead of going to `returnTo`. The
|
|
1298
|
+
* form stays busy (its button loading, every control disabled) until it
|
|
1299
|
+
* leaves the page. When the application keeps the form on screen while it
|
|
1300
|
+
* moves on (a router's navigation, loading the session), return a Promise:
|
|
1301
|
+
* the form waits for it, then is free again if it is still shown.
|
|
1302
|
+
*/
|
|
1303
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1296
1304
|
/** Where to go after signing in, and where a provider returns to. Default: `/`. */
|
|
1297
1305
|
returnTo?: string;
|
|
1298
1306
|
/**
|
|
@@ -1335,7 +1343,9 @@ interface AuthFormProps {
|
|
|
1335
1343
|
* other action (a provider, a passkey, a sign-in link, cancel, send again);
|
|
1336
1344
|
* `danger` removes, leaves, deletes, disconnects or turns something off.
|
|
1337
1345
|
* `loading` is true on the one button whose action is under way; every
|
|
1338
|
-
* button is `disabled` meanwhile
|
|
1346
|
+
* button is `disabled` meanwhile. Draw a spinner (or the like) on the button
|
|
1347
|
+
* while `loading` is true, and keep its label: after a sign-in it stays true
|
|
1348
|
+
* until the page is left, so the person sees the work go on.
|
|
1339
1349
|
*/
|
|
1340
1350
|
interface AuthButtonProps {
|
|
1341
1351
|
type: 'submit' | 'button';
|
|
@@ -1376,6 +1386,7 @@ interface AuthInputProps {
|
|
|
1376
1386
|
value: string;
|
|
1377
1387
|
autoComplete?: string;
|
|
1378
1388
|
required?: boolean;
|
|
1389
|
+
disabled?: boolean;
|
|
1379
1390
|
className?: string;
|
|
1380
1391
|
'aria-invalid'?: boolean;
|
|
1381
1392
|
'aria-describedby'?: string;
|
|
@@ -1533,8 +1544,11 @@ interface AcceptInviteProps extends AuthFormProps {
|
|
|
1533
1544
|
}
|
|
1534
1545
|
declare function AcceptInvite(props: AcceptInviteProps): react.JSX.Element;
|
|
1535
1546
|
interface PendingTermsProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'createAccounts'> {
|
|
1536
|
-
/**
|
|
1537
|
-
|
|
1547
|
+
/**
|
|
1548
|
+
* Called once the person accepted. Nothing else happens: the application
|
|
1549
|
+
* decides what follows. A Promise keeps the button loading until it settles.
|
|
1550
|
+
*/
|
|
1551
|
+
onSuccess?: () => void | Promise<unknown>;
|
|
1538
1552
|
}
|
|
1539
1553
|
/**
|
|
1540
1554
|
* The legal documents the signed-in person still owes (a new version of the
|
|
@@ -1587,8 +1601,8 @@ type AccountEvent = {
|
|
|
1587
1601
|
kind: 'sessions.ended';
|
|
1588
1602
|
};
|
|
1589
1603
|
interface AccountProps extends Omit<AuthFormProps, 'links' | 'returnTo' | 'onSuccess'> {
|
|
1590
|
-
/** Called after "sign out everywhere". Default: go to `/`. */
|
|
1591
|
-
onSignedOut?: () => void
|
|
1604
|
+
/** Called after "sign out everywhere". Default: go to `/`. A Promise keeps the button loading until it settles. */
|
|
1605
|
+
onSignedOut?: () => void | Promise<unknown>;
|
|
1592
1606
|
/** The moment of a session, as the application writes dates. Default: the browser's own way, in the reader's time zone. */
|
|
1593
1607
|
formatDate?: (iso: string) => string;
|
|
1594
1608
|
/**
|
package/dist/react.js
CHANGED
|
@@ -823,6 +823,7 @@ var TEXTS = {
|
|
|
823
823
|
savePassword: "Save the password",
|
|
824
824
|
passwordChanged: "Your password was changed. You can sign in with it now.",
|
|
825
825
|
confirming: "Confirming your address\u2026",
|
|
826
|
+
loading: "Loading\u2026",
|
|
826
827
|
emailConfirmed: "Your address is confirmed. You can sign in now.",
|
|
827
828
|
invitedTo: "You've been invited to {workspace}",
|
|
828
829
|
invitedToOrganization: "You've been invited to {organization} on {workspace}",
|
|
@@ -1004,6 +1005,7 @@ var TEXTS = {
|
|
|
1004
1005
|
savePassword: "Guardar la contrase\xF1a",
|
|
1005
1006
|
passwordChanged: "Tu contrase\xF1a se cambi\xF3. Ya puedes entrar con ella.",
|
|
1006
1007
|
confirming: "Confirmando tu correo\u2026",
|
|
1008
|
+
loading: "Cargando\u2026",
|
|
1007
1009
|
emailConfirmed: "Tu correo qued\xF3 confirmado. Ya puedes entrar.",
|
|
1008
1010
|
invitedTo: "Te invitaron a {workspace}",
|
|
1009
1011
|
invitedToOrganization: "Te invitaron a {organization} en {workspace}",
|
|
@@ -1136,7 +1138,14 @@ var TEXTS = {
|
|
|
1136
1138
|
}
|
|
1137
1139
|
}
|
|
1138
1140
|
};
|
|
1139
|
-
var PlainButton = ({ variant: _variant, loading
|
|
1141
|
+
var PlainButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { ...p, "aria-busy": loading || void 0, children: [
|
|
1142
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cauthy-spinner", "aria-hidden": "true" }),
|
|
1143
|
+
children
|
|
1144
|
+
] });
|
|
1145
|
+
var UnstyledButton = ({ variant: _variant, loading, children, ...p }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { ...p, "aria-busy": loading || void 0, children: [
|
|
1146
|
+
children,
|
|
1147
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: "\u2026" })
|
|
1148
|
+
] });
|
|
1140
1149
|
var PlainInput = (p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { ...p });
|
|
1141
1150
|
var looks = /* @__PURE__ */ new Map();
|
|
1142
1151
|
var loaded = /* @__PURE__ */ new Set();
|
|
@@ -1244,12 +1253,15 @@ function sameOrigin(to) {
|
|
|
1244
1253
|
return void 0;
|
|
1245
1254
|
}
|
|
1246
1255
|
}
|
|
1247
|
-
function
|
|
1256
|
+
function isPending(v) {
|
|
1257
|
+
return typeof v?.then === "function";
|
|
1258
|
+
}
|
|
1259
|
+
function useForm(props, opts = {}) {
|
|
1248
1260
|
const t = { ...TEXTS[props.locale ?? "en"], ...props.texts, errors: { ...TEXTS[props.locale ?? "en"].errors, ...props.texts?.errors } };
|
|
1249
1261
|
const cx = (slot) => props.classNames?.[slot] ?? `cauthy-${slot}`;
|
|
1250
1262
|
const own = (slot) => props.classNames?.[slot];
|
|
1251
1263
|
const C = props.components ?? {};
|
|
1252
|
-
const Button = C.Button ?? PlainButton;
|
|
1264
|
+
const Button = C.Button ?? (props.unstyled ? UnstyledButton : PlainButton);
|
|
1253
1265
|
const Input = C.Input ?? PlainInput;
|
|
1254
1266
|
const [error, setOnlyError] = (0, import_react.useState)(null);
|
|
1255
1267
|
const [problems, setProblems] = (0, import_react.useState)({ list: [], round: 0 });
|
|
@@ -1266,9 +1278,32 @@ function useForm(props) {
|
|
|
1266
1278
|
const fieldError = (name) => problems.list.find((e) => e.field === name) ?? (error?.field === name ? error : null);
|
|
1267
1279
|
const firstWrong = (name) => problems.list[0]?.field === name ? problems.round : 0;
|
|
1268
1280
|
const [acting, setActing] = (0, import_react.useState)(null);
|
|
1269
|
-
const busy = acting !== null;
|
|
1281
|
+
const busy = acting !== null || opts.waiting === true;
|
|
1270
1282
|
const setBusy = (0, import_react.useCallback)((on) => setActing(on === false ? null : on === true ? SUBMIT : on), []);
|
|
1271
1283
|
const loading = (key = SUBMIT) => acting === key;
|
|
1284
|
+
const shown = (0, import_react.useRef)(true);
|
|
1285
|
+
(0, import_react.useEffect)(() => {
|
|
1286
|
+
shown.current = true;
|
|
1287
|
+
const back = (e) => {
|
|
1288
|
+
if (e.persisted) setActing(null);
|
|
1289
|
+
};
|
|
1290
|
+
window.addEventListener("pageshow", back);
|
|
1291
|
+
return () => {
|
|
1292
|
+
shown.current = false;
|
|
1293
|
+
window.removeEventListener("pageshow", back);
|
|
1294
|
+
};
|
|
1295
|
+
}, []);
|
|
1296
|
+
const hold = (after) => {
|
|
1297
|
+
if (!isPending(after)) return;
|
|
1298
|
+
const free = () => {
|
|
1299
|
+
if (shown.current) setActing(null);
|
|
1300
|
+
};
|
|
1301
|
+
after.then(free, free);
|
|
1302
|
+
};
|
|
1303
|
+
const fail = (e) => {
|
|
1304
|
+
setActing(null);
|
|
1305
|
+
setError(e);
|
|
1306
|
+
};
|
|
1272
1307
|
const [policy, setPolicy] = (0, import_react.useState)(void 0);
|
|
1273
1308
|
const locale = props.locale ?? "en";
|
|
1274
1309
|
const say = (e) => {
|
|
@@ -1277,15 +1312,18 @@ function useForm(props) {
|
|
|
1277
1312
|
};
|
|
1278
1313
|
const passwordRule = describePasswordPolicy(policy, locale);
|
|
1279
1314
|
const done = (to) => {
|
|
1280
|
-
|
|
1315
|
+
setActing((a) => a ?? SUBMIT);
|
|
1316
|
+
if (props.onSuccess) hold(props.onSuccess());
|
|
1281
1317
|
else window.location.assign(sitePath(to) ?? sameOrigin(props.returnTo) ?? "/");
|
|
1282
1318
|
};
|
|
1283
|
-
const root = useRoot(props);
|
|
1284
|
-
|
|
1319
|
+
const root = useRoot(opts.part ? { ...props, unstyled: true } : props);
|
|
1320
|
+
const plain = props.unstyled === true;
|
|
1321
|
+
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
1322
|
}
|
|
1286
1323
|
var SUBMIT = "submit";
|
|
1287
1324
|
function Wrap(p) {
|
|
1288
1325
|
const { Form: OwnForm } = p.f.C;
|
|
1326
|
+
const busy = p.busy ?? (p.f.busy || void 0);
|
|
1289
1327
|
if (OwnForm) {
|
|
1290
1328
|
const own = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1291
1329
|
OwnForm,
|
|
@@ -1293,7 +1331,7 @@ function Wrap(p) {
|
|
|
1293
1331
|
as: p.as === "form" ? "form" : "div",
|
|
1294
1332
|
onSubmit: p.onSubmit,
|
|
1295
1333
|
noValidate: p.noValidate,
|
|
1296
|
-
"aria-busy":
|
|
1334
|
+
"aria-busy": busy,
|
|
1297
1335
|
className: p.slot ? p.f.own(p.slot) : void 0,
|
|
1298
1336
|
section: p.section,
|
|
1299
1337
|
children: p.children
|
|
@@ -1302,7 +1340,16 @@ function Wrap(p) {
|
|
|
1302
1340
|
return p.root?.["data-cauthy"] !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ...p.root, children: own }) : own;
|
|
1303
1341
|
}
|
|
1304
1342
|
const Tag2 = p.as;
|
|
1305
|
-
|
|
1343
|
+
const children = p.busy && p.children === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Pending, { f: p.f }) : p.children;
|
|
1344
|
+
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 });
|
|
1345
|
+
}
|
|
1346
|
+
function Pending({ f }) {
|
|
1347
|
+
if (f.plain) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { role: "status", children: f.t.loading });
|
|
1348
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "status", className: f.cx("pending"), children: [
|
|
1349
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cauthy-spinner", "aria-hidden": "true" }),
|
|
1350
|
+
" ",
|
|
1351
|
+
f.t.loading
|
|
1352
|
+
] });
|
|
1306
1353
|
}
|
|
1307
1354
|
function Note({ f, children, footer }) {
|
|
1308
1355
|
const { Text } = f.C;
|
|
@@ -1343,10 +1390,10 @@ function Check(p) {
|
|
|
1343
1390
|
const { f } = p;
|
|
1344
1391
|
const { Checkbox } = f.C;
|
|
1345
1392
|
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 });
|
|
1393
|
+
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
1394
|
}
|
|
1348
1395
|
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) }),
|
|
1396
|
+
/* @__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
1397
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: p.children })
|
|
1351
1398
|
] });
|
|
1352
1399
|
}
|
|
@@ -1355,10 +1402,11 @@ function Pick(p) {
|
|
|
1355
1402
|
const id = p.id ?? made;
|
|
1356
1403
|
const { f } = p;
|
|
1357
1404
|
const { Select } = f.C;
|
|
1405
|
+
const disabled = p.disabled || f.busy;
|
|
1358
1406
|
if (Select) {
|
|
1359
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Select, { id, name: p.name, value: p.value, options: p.options, disabled
|
|
1407
|
+
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
1408
|
}
|
|
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
|
|
1409
|
+
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
1410
|
}
|
|
1363
1411
|
function Labelled({ f, id, label, children }) {
|
|
1364
1412
|
const { Field: OwnField } = f.C;
|
|
@@ -1412,6 +1460,7 @@ function Field(p) {
|
|
|
1412
1460
|
value: p.value,
|
|
1413
1461
|
autoComplete: p.autoComplete,
|
|
1414
1462
|
required: p.required,
|
|
1463
|
+
disabled: f.busy,
|
|
1415
1464
|
className: f.cx("input"),
|
|
1416
1465
|
"aria-invalid": bad ? true : void 0,
|
|
1417
1466
|
"aria-describedby": described || void 0,
|
|
@@ -1501,6 +1550,7 @@ function SecondStepForm(p) {
|
|
|
1501
1550
|
if (ctl.signal.aborted) return;
|
|
1502
1551
|
setPushNumber(0);
|
|
1503
1552
|
if (!done.ok) return f.setError(done.error);
|
|
1553
|
+
f.setBusy("push");
|
|
1504
1554
|
f.done();
|
|
1505
1555
|
};
|
|
1506
1556
|
const sendCode = async () => {
|
|
@@ -1516,8 +1566,7 @@ function SecondStepForm(p) {
|
|
|
1516
1566
|
f.setBusy(true);
|
|
1517
1567
|
f.setError(null);
|
|
1518
1568
|
const r = await auth.signIn.mfa({ code });
|
|
1519
|
-
f.
|
|
1520
|
-
if (!r.ok) return f.setError(r.error);
|
|
1569
|
+
if (!r.ok) return f.fail(r.error);
|
|
1521
1570
|
f.done();
|
|
1522
1571
|
};
|
|
1523
1572
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -1545,7 +1594,11 @@ function SecondStepForm(p) {
|
|
|
1545
1594
|
}
|
|
1546
1595
|
function ProviderButtons(p) {
|
|
1547
1596
|
const { f } = p;
|
|
1548
|
-
|
|
1597
|
+
const pick = (id) => {
|
|
1598
|
+
f.setBusy(`provider:${id}`);
|
|
1599
|
+
p.onPick(id);
|
|
1600
|
+
};
|
|
1601
|
+
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
1602
|
provider.iconUrl && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: provider.iconUrl, alt: "", width: 18, height: 18, className: f.cx("providerIcon") }),
|
|
1550
1603
|
f.t.continueWith,
|
|
1551
1604
|
" ",
|
|
@@ -1558,8 +1611,8 @@ function signInOffers(methods) {
|
|
|
1558
1611
|
}
|
|
1559
1612
|
function SignIn(props) {
|
|
1560
1613
|
const auth = useAuth();
|
|
1561
|
-
const f = useForm(props);
|
|
1562
1614
|
const [methods, setMethods] = (0, import_react.useState)(null);
|
|
1615
|
+
const f = useForm(props, { waiting: methods === null });
|
|
1563
1616
|
const [email, setEmail] = (0, import_react.useState)("");
|
|
1564
1617
|
const [password, setPassword] = (0, import_react.useState)("");
|
|
1565
1618
|
const [code, setCode] = (0, import_react.useState)("");
|
|
@@ -1631,14 +1684,16 @@ function SignIn(props) {
|
|
|
1631
1684
|
f.setBusy(true);
|
|
1632
1685
|
f.setError(null);
|
|
1633
1686
|
const r = step === "sent" ? await auth.signIn.emailCode({ code }) : await auth.signIn.email({ email, password, captchaToken: challenge.token });
|
|
1634
|
-
f.setBusy(false);
|
|
1635
1687
|
if (step === "password") challenge.renew();
|
|
1636
1688
|
if (!r.ok && r.error.code === "sso_required") return toCompany();
|
|
1637
1689
|
if (!r.ok) {
|
|
1638
1690
|
setOffer((n) => n + 1);
|
|
1639
|
-
return f.
|
|
1691
|
+
return f.fail(r.error);
|
|
1692
|
+
}
|
|
1693
|
+
if (r.status === "mfa_required") {
|
|
1694
|
+
f.setBusy(false);
|
|
1695
|
+
return setStep("code");
|
|
1640
1696
|
}
|
|
1641
|
-
if (r.status === "mfa_required") return setStep("code");
|
|
1642
1697
|
f.done("returnTo" in r && typeof r.returnTo === "string" ? r.returnTo : void 0);
|
|
1643
1698
|
};
|
|
1644
1699
|
const usePasskey = async () => {
|
|
@@ -1647,10 +1702,10 @@ function SignIn(props) {
|
|
|
1647
1702
|
f.setError(null);
|
|
1648
1703
|
const r = await auth.signIn.passkey({ returnTo: props.returnTo, locale: props.locale, email });
|
|
1649
1704
|
if (r.ok && r.status === "redirected") return;
|
|
1705
|
+
if (r.ok) return f.done();
|
|
1650
1706
|
f.setBusy(false);
|
|
1651
|
-
|
|
1652
|
-
if (
|
|
1653
|
-
f.done();
|
|
1707
|
+
setOffer((n) => n + 1);
|
|
1708
|
+
if (r.error.code !== "cancelled") f.setError(r.error);
|
|
1654
1709
|
};
|
|
1655
1710
|
const sendLink = async () => {
|
|
1656
1711
|
autofill.current?.abort();
|
|
@@ -1767,10 +1822,12 @@ function SignUp(props) {
|
|
|
1767
1822
|
...legal ? { acceptLegal, consents } : {},
|
|
1768
1823
|
captchaToken: challenge.token
|
|
1769
1824
|
});
|
|
1770
|
-
f.setBusy(false);
|
|
1771
1825
|
challenge.renew();
|
|
1772
|
-
if (!r.ok) return f.
|
|
1773
|
-
if (r.status === "verification_required")
|
|
1826
|
+
if (!r.ok) return f.fail(r.error);
|
|
1827
|
+
if (r.status === "verification_required") {
|
|
1828
|
+
f.setBusy(false);
|
|
1829
|
+
return setSent(true);
|
|
1830
|
+
}
|
|
1774
1831
|
f.done();
|
|
1775
1832
|
};
|
|
1776
1833
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -1933,13 +1990,16 @@ function AcceptInvite(props) {
|
|
|
1933
1990
|
f.setBusy(true);
|
|
1934
1991
|
f.setError(null);
|
|
1935
1992
|
const r = await auth.acceptInvitation(token, password);
|
|
1936
|
-
f.setBusy(false);
|
|
1937
1993
|
if (!r.ok) {
|
|
1994
|
+
f.setBusy(false);
|
|
1938
1995
|
if (r.error.code === "invalid_token") return setState("invalid");
|
|
1939
1996
|
return f.setError(r.error);
|
|
1940
1997
|
}
|
|
1941
1998
|
props.onSaved?.({ kind: "invitation.accepted", email: invitation?.email ?? "", ...invitation?.organization ? { organization: invitation.organization } : {} });
|
|
1942
|
-
if (r.status === "mfa_required")
|
|
1999
|
+
if (r.status === "mfa_required") {
|
|
2000
|
+
f.setBusy(false);
|
|
2001
|
+
return setState("code");
|
|
2002
|
+
}
|
|
1943
2003
|
f.done();
|
|
1944
2004
|
};
|
|
1945
2005
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -1977,10 +2037,11 @@ function PendingTerms(props) {
|
|
|
1977
2037
|
f.setBusy(true);
|
|
1978
2038
|
f.setError(null);
|
|
1979
2039
|
const r = await auth.account.acceptLegal({ kinds: owed.map((d) => d.kind) });
|
|
2040
|
+
if (!r.ok) return f.fail(r.error);
|
|
2041
|
+
const after = props.onSuccess?.();
|
|
2042
|
+
if (isPending(after)) await Promise.resolve(after).catch(() => void 0);
|
|
1980
2043
|
f.setBusy(false);
|
|
1981
|
-
if (!r.ok) return f.setError(r.error);
|
|
1982
2044
|
setOwed([]);
|
|
1983
|
-
props.onSuccess?.();
|
|
1984
2045
|
};
|
|
1985
2046
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Wrap, { f, as: "div", slot: "form", root: f.root, children: [
|
|
1986
2047
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Heading, { f, children: f.t.termsChanged }),
|
|
@@ -1995,15 +2056,15 @@ function PendingTerms(props) {
|
|
|
1995
2056
|
}
|
|
1996
2057
|
var ACCOUNT_SECTIONS = ["profile", "email", "password", "linked", "secondStep", "phone", "passkeys", "agents", "sessions"];
|
|
1997
2058
|
function useSection(props) {
|
|
1998
|
-
const f = useForm(
|
|
2059
|
+
const f = useForm(props, { part: true });
|
|
1999
2060
|
const [done, setDone] = (0, import_react.useState)(null);
|
|
2000
|
-
const run = async (action, key = SUBMIT) => {
|
|
2061
|
+
const run = async (action, key = SUBMIT, leaves = false) => {
|
|
2001
2062
|
f.setBusy(key);
|
|
2002
2063
|
f.setError(null);
|
|
2003
2064
|
setDone(null);
|
|
2004
2065
|
const r = await action();
|
|
2005
|
-
f.
|
|
2006
|
-
if (!
|
|
2066
|
+
if (!r.ok) f.fail(r.error);
|
|
2067
|
+
else if (!leaves) f.setBusy(false);
|
|
2007
2068
|
return r;
|
|
2008
2069
|
};
|
|
2009
2070
|
const general = f.error && !f.error.field ? f.error : null;
|
|
@@ -2416,11 +2477,26 @@ function LinkedPart({ props }) {
|
|
|
2416
2477
|
" ",
|
|
2417
2478
|
/* @__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
2479
|
] }, 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
|
-
|
|
2480
|
+
offered.filter((p) => !linked.some((x) => x.provider === p.id)).map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
2481
|
+
f.Button,
|
|
2482
|
+
{
|
|
2483
|
+
type: "button",
|
|
2484
|
+
variant: "secondary",
|
|
2485
|
+
loading: f.loading(`link:${p.id}`),
|
|
2486
|
+
disabled: f.busy,
|
|
2487
|
+
className: f.cx("providerButton"),
|
|
2488
|
+
onClick: () => {
|
|
2489
|
+
f.setBusy(`link:${p.id}`);
|
|
2490
|
+
auth.account.link(p.id, { returnTo: here });
|
|
2491
|
+
},
|
|
2492
|
+
children: [
|
|
2493
|
+
f.t.linkWith,
|
|
2494
|
+
" ",
|
|
2495
|
+
p.name
|
|
2496
|
+
]
|
|
2497
|
+
},
|
|
2498
|
+
p.id
|
|
2499
|
+
))
|
|
2424
2500
|
] });
|
|
2425
2501
|
}
|
|
2426
2502
|
function PasswordPart({ props }) {
|
|
@@ -2578,7 +2654,7 @@ function PhonePart({ props, account, onChanged }) {
|
|
|
2578
2654
|
!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
2655
|
!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
2656
|
!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) }),
|
|
2657
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { type: "radio", name: "channel", value: c, checked: channel === c, disabled: f.busy, onChange: () => setChannel(c) }),
|
|
2582
2658
|
" ",
|
|
2583
2659
|
c === "whatsapp" ? f.t.byWhatsapp : f.t.bySms
|
|
2584
2660
|
] }, c)) }),
|
|
@@ -2625,10 +2701,10 @@ function SessionsPart({ props, when }) {
|
|
|
2625
2701
|
void auth.account.sessions().then(setSessions);
|
|
2626
2702
|
}, []);
|
|
2627
2703
|
const end = async () => {
|
|
2628
|
-
const r = await run(() => auth.account.signOutEverywhere());
|
|
2704
|
+
const r = await run(() => auth.account.signOutEverywhere(), SUBMIT, true);
|
|
2629
2705
|
if (!r.ok) return;
|
|
2630
2706
|
props.onSaved?.({ kind: "sessions.ended" });
|
|
2631
|
-
if (props.onSignedOut) props.onSignedOut();
|
|
2707
|
+
if (props.onSignedOut) f.hold(props.onSignedOut());
|
|
2632
2708
|
else window.location.assign("/");
|
|
2633
2709
|
};
|
|
2634
2710
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Wrap, { f, as: "section", slot: "section", section: "sessions", children: [
|