@workos-inc/widgets 1.1.4 → 1.2.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cjs/lib/organization-switcher.d.ts +10 -1
  3. package/dist/cjs/lib/organization-switcher.d.ts.map +1 -1
  4. package/dist/cjs/lib/organization-switcher.js +31 -3
  5. package/dist/cjs/lib/organization-switcher.js.map +1 -1
  6. package/dist/cjs/workos-widgets.client.d.ts +6 -0
  7. package/dist/cjs/workos-widgets.client.d.ts.map +1 -1
  8. package/dist/cjs/workos-widgets.client.js +23 -3
  9. package/dist/cjs/workos-widgets.client.js.map +1 -1
  10. package/dist/esm/lib/organization-switcher.d.ts +10 -1
  11. package/dist/esm/lib/organization-switcher.d.ts.map +1 -1
  12. package/dist/esm/lib/organization-switcher.js +31 -3
  13. package/dist/esm/lib/organization-switcher.js.map +1 -1
  14. package/dist/esm/workos-widgets.client.d.ts +6 -0
  15. package/dist/esm/workos-widgets.client.d.ts.map +1 -1
  16. package/dist/esm/workos-widgets.client.js +25 -5
  17. package/dist/esm/workos-widgets.client.js.map +1 -1
  18. package/package.json +40 -47
  19. package/src/api/api-provider.tsx +0 -158
  20. package/src/api/constants.ts +0 -1
  21. package/src/api/endpoint.ts +0 -3097
  22. package/src/api/errors.ts +0 -48
  23. package/src/api/index.ts +0 -2
  24. package/src/api/utils.ts +0 -42
  25. package/src/api/widgets-api-client.ts +0 -87
  26. package/src/card-list.tsx +0 -26
  27. package/src/index.ts +0 -9
  28. package/src/lib/add-mfa-dialog.tsx +0 -379
  29. package/src/lib/api/config.ts +0 -9
  30. package/src/lib/api/user.ts +0 -98
  31. package/src/lib/change-password-dialog.tsx +0 -290
  32. package/src/lib/constants.ts +0 -3
  33. package/src/lib/copy-button.tsx +0 -53
  34. package/src/lib/delete-user-dialog.tsx +0 -110
  35. package/src/lib/edit-user-profile-dialog.tsx +0 -181
  36. package/src/lib/edit-user-role-dialog.tsx +0 -178
  37. package/src/lib/elements.tsx +0 -428
  38. package/src/lib/elevated-access.tsx +0 -261
  39. package/src/lib/error-boundary.tsx +0 -166
  40. package/src/lib/errors.ts +0 -49
  41. package/src/lib/generic-error.tsx +0 -70
  42. package/src/lib/icon-panel.tsx +0 -26
  43. package/src/lib/icons.tsx +0 -21
  44. package/src/lib/invite-user-dialog.tsx +0 -327
  45. package/src/lib/logout-all-sessions-dialog.tsx +0 -82
  46. package/src/lib/logout-dialog.tsx +0 -85
  47. package/src/lib/marker.tsx +0 -39
  48. package/src/lib/oauth-icons.tsx +0 -138
  49. package/src/lib/organization-switcher.tsx +0 -156
  50. package/src/lib/otp-input.tsx +0 -276
  51. package/src/lib/resend-invite-dialog.tsx +0 -145
  52. package/src/lib/reset-mfa-dialog.tsx +0 -104
  53. package/src/lib/revoke-invite-dialog.tsx +0 -111
  54. package/src/lib/save-button.tsx +0 -113
  55. package/src/lib/search-provider.tsx +0 -51
  56. package/src/lib/set-password-dialog.tsx +0 -204
  57. package/src/lib/use-dialog-close.tsx +0 -19
  58. package/src/lib/use-is-hydrated.ts +0 -13
  59. package/src/lib/use-layout-effect.ts +0 -6
  60. package/src/lib/use-security-settings.tsx +0 -49
  61. package/src/lib/user-actions-dropdown.tsx +0 -157
  62. package/src/lib/user-profile.tsx +0 -227
  63. package/src/lib/user-security.tsx +0 -187
  64. package/src/lib/user-sessions.tsx +0 -204
  65. package/src/lib/users-filter.tsx +0 -62
  66. package/src/lib/users-management-context.tsx +0 -74
  67. package/src/lib/users-management-state.ts +0 -165
  68. package/src/lib/users-management.tsx +0 -594
  69. package/src/lib/users-search.tsx +0 -73
  70. package/src/lib/utils.ts +0 -131
  71. package/src/lib/widgets-context.ts +0 -29
  72. package/src/organization-switcher.client.tsx +0 -81
  73. package/src/user-profile.client.tsx +0 -55
  74. package/src/user-security.client.tsx +0 -55
  75. package/src/user-sessions.client.tsx +0 -100
  76. package/src/users-management.client.tsx +0 -73
  77. package/src/workos-widgets.client.tsx +0 -75
  78. /package/{src → dist/css}/base.css +0 -0
  79. /package/{src → dist/css}/lib/card-list.css +0 -0
  80. /package/{src → dist/css}/lib/marker.css +0 -0
  81. /package/{src → dist/css}/lib/save-button.css +0 -0
  82. /package/{src → dist/css}/styles.css +0 -0
  83. /package/{src → dist/css}/users-management.css +0 -0
@@ -1,156 +0,0 @@
1
- "use client";
2
-
3
- import { CheckIcon } from "@radix-ui/react-icons";
4
- import {
5
- Box,
6
- Button,
7
- ChevronDownIcon,
8
- DropdownMenu,
9
- Flex,
10
- Skeleton,
11
- Text,
12
- VisuallyHidden,
13
- } from "@radix-ui/themes";
14
- import { OrganizationInfo, Organizations403, Organizations404 } from "../api";
15
-
16
- type OrganizationSwitcherVariant = "ghost" | "outline";
17
-
18
- // Rename all uses of `org` to `organization`
19
- export type OrganizationSwitcherPassthroughProps = {
20
- switchToOrganization: ({
21
- organizationId,
22
- }: {
23
- organizationId: string;
24
- }) => void;
25
- // Simple props to affect the overall style
26
- variant?: OrganizationSwitcherVariant;
27
- organizationLabel?: string | null;
28
- children?: React.ReactNode;
29
- };
30
-
31
- export interface OrganizationSwitcherProps
32
- extends OrganizationSwitcherPassthroughProps {
33
- organizations: OrganizationInfo[];
34
- }
35
-
36
- export const OrganizationSwitcher = ({
37
- organizations,
38
- switchToOrganization,
39
- variant = "outline",
40
- organizationLabel = "Organizations",
41
- children,
42
- }: OrganizationSwitcherProps) => {
43
- const currentOrganization = organizations.find(
44
- (organization) => organization.current,
45
- );
46
-
47
- // Possible if the user has no organizations - we should figure out what to do in this case
48
- if (!currentOrganization) {
49
- return null;
50
- }
51
-
52
- return (
53
- <DropdownMenu.Root>
54
- <DropdownMenu.Trigger>
55
- <Button
56
- color="gray"
57
- variant={variant}
58
- className="OrganizationSwitcherTrigger"
59
- >
60
- <Flex align="center" justify="between" gap="2" flexGrow="1">
61
- <Text>{currentOrganization.name}</Text>
62
- <DropdownMenu.TriggerIcon />
63
- </Flex>
64
- </Button>
65
- </DropdownMenu.Trigger>
66
- <DropdownMenu.Content>
67
- <DropdownMenu.Group>
68
- {organizationLabel ? (
69
- <DropdownMenu.Label>
70
- <Text>{organizationLabel}</Text>
71
- </DropdownMenu.Label>
72
- ) : null}
73
- {organizations.map((organization) => (
74
- <Flex
75
- key={organization.id}
76
- asChild
77
- pr="2"
78
- maxWidth="280px"
79
- minWidth="180px"
80
- >
81
- <DropdownMenu.Item
82
- onClick={() => {
83
- if (organization.id !== currentOrganization.id) {
84
- switchToOrganization({ organizationId: organization.id });
85
- }
86
- }}
87
- >
88
- <Flex
89
- justify="between"
90
- align="center"
91
- gap="4"
92
- flexGrow="1"
93
- overflow="hidden"
94
- >
95
- <Text truncate>
96
- {organization.name}
97
- {organization.current && (
98
- <VisuallyHidden> (current)</VisuallyHidden>
99
- )}
100
- </Text>
101
- <Flex
102
- aria-hidden
103
- align="center"
104
- justify="center"
105
- flexShrink="0"
106
- >
107
- {organization.current ? (
108
- <CheckIcon width="18px" height="18px" />
109
- ) : (
110
- // make the extra space for
111
- <Box width="18px" height="18px" />
112
- )}
113
- </Flex>
114
- </Flex>
115
- </DropdownMenu.Item>
116
- </Flex>
117
- ))}
118
- </DropdownMenu.Group>
119
- {children}
120
- </DropdownMenu.Content>
121
- </DropdownMenu.Root>
122
- );
123
- };
124
-
125
- export function OrganizationSwitcherLoading({
126
- variant = "outline",
127
- organizations,
128
- }: {
129
- variant?: OrganizationSwitcherVariant;
130
- organizations: OrganizationInfo[];
131
- }) {
132
- const currentOrganization = organizations.find(
133
- (organization) => organization.current,
134
- );
135
-
136
- return (
137
- // Always need DropdownMenu.Root to wrap children than may include
138
- <Button color="gray" variant={variant} disabled>
139
- <Flex align="center" gap="2">
140
- <Skeleton loading={!currentOrganization}>
141
- <Text>{currentOrganization?.name ?? "Loading..."}</Text>
142
- </Skeleton>
143
- <ChevronDownIcon />
144
- </Flex>
145
- </Button>
146
- );
147
- }
148
-
149
- interface OrganizationSwitcherErrorProps {
150
- error: Organizations403 | Organizations404;
151
- }
152
-
153
- export function OrganizationSwitcherError(_: OrganizationSwitcherErrorProps) {
154
- // TODO: consider other error state options
155
- return null;
156
- }
@@ -1,276 +0,0 @@
1
- "use client";
2
-
3
- import { composeRefs, useComposedRefs } from "@radix-ui/react-compose-refs";
4
- import { useControllableState } from "@radix-ui/react-use-controllable-state";
5
- import * as Form from "@radix-ui/react-form";
6
- import { Grid } from "@radix-ui/themes";
7
- import * as React from "react";
8
- import { TextField } from "./elements";
9
-
10
- interface OptContextType {
11
- value: string[];
12
- readOnly?: boolean;
13
- state?: "valid" | "invalid";
14
- onEnterPressed: () => void;
15
- onChildAdd: (input: HTMLInputElement) => void;
16
- onCharChange: (char: string, index: number) => void;
17
- allChildrenAdded: boolean;
18
- }
19
-
20
- const OtpContext = React.createContext<OptContextType | undefined>(undefined);
21
-
22
- type OtpRootProps = React.ComponentPropsWithoutRef<typeof Grid> & {
23
- onValueChange?: (value: string) => void;
24
- id?: string;
25
- name?: string;
26
- readOnly?: boolean;
27
- state?: "valid" | "invalid";
28
- value?: string;
29
- defaultValue?: string;
30
- autoSubmit?: boolean;
31
- };
32
-
33
- export const Root = React.forwardRef<HTMLInputElement, OtpRootProps>(
34
- function Root(
35
- {
36
- name,
37
- id,
38
- defaultValue,
39
- value: valueProp,
40
- onValueChange,
41
- autoSubmit,
42
- children,
43
- readOnly,
44
- state,
45
- ...gridProps
46
- },
47
- forwardedRef,
48
- ) {
49
- const [lastCharIndex, setLastCharIndex] = React.useState<number>(0);
50
- const [allChildrenAdded, setAllChildrenAdded] =
51
- React.useState<boolean>(false);
52
- const childCount = React.Children.count(children);
53
-
54
- const [value, setValue] = useControllableState({
55
- prop: getValueAsArray(valueProp, childCount),
56
- defaultProp: getValueAsArray(defaultValue, childCount),
57
- onChange: (value) => onValueChange?.(value.join("")),
58
- });
59
-
60
- const hiddenInputRef = React.useRef<HTMLInputElement>(null);
61
- const childrenRefs = React.useRef<HTMLInputElement[]>([]);
62
-
63
- const attemptAutoSubmit = React.useCallback(
64
- (enterPressed = false) => {
65
- if (
66
- autoSubmit &&
67
- value &&
68
- value.every((char) => char !== "") &&
69
- (enterPressed || lastCharIndex + 1 === childCount)
70
- ) {
71
- hiddenInputRef.current?.form?.requestSubmit();
72
- }
73
- },
74
- [value, childCount, lastCharIndex, autoSubmit],
75
- );
76
-
77
- const handleEnterPressed = React.useCallback(
78
- () => attemptAutoSubmit(true),
79
- [attemptAutoSubmit],
80
- );
81
-
82
- const handleChildAdd = React.useCallback(
83
- (input: HTMLInputElement) => {
84
- if (input) {
85
- input.dataset.index = `${childrenRefs.current.length}`;
86
- childrenRefs.current.push(input);
87
- } else {
88
- childrenRefs.current.pop();
89
- }
90
-
91
- if (childrenRefs.current.length === childCount) {
92
- setAllChildrenAdded(true);
93
- }
94
- },
95
- [childCount],
96
- );
97
-
98
- const handleCharChange = React.useCallback(
99
- (char: string, index: number) => {
100
- setValue((previousValue) => {
101
- const arrayToCopy = previousValue ?? createEmptyArray(childCount);
102
- const newValue = [...arrayToCopy];
103
- newValue[index] = char;
104
- return newValue;
105
- });
106
- setLastCharIndex(index);
107
- },
108
- [childCount, setValue],
109
- );
110
-
111
- const otpContext = React.useMemo(
112
- () => ({
113
- value: value ?? createEmptyArray(childCount),
114
- readOnly,
115
- state,
116
- allChildrenAdded,
117
- onEnterPressed: handleEnterPressed,
118
- onChildAdd: handleChildAdd,
119
- onCharChange: handleCharChange,
120
- }),
121
- [
122
- value,
123
- allChildrenAdded,
124
- readOnly,
125
- state,
126
- childCount,
127
- handleEnterPressed,
128
- handleChildAdd,
129
- handleCharChange,
130
- ],
131
- );
132
-
133
- React.useEffect(attemptAutoSubmit, [attemptAutoSubmit]);
134
-
135
- return (
136
- <OtpContext.Provider value={otpContext}>
137
- <Grid
138
- columns={`repeat(${childCount}, 1fr)`}
139
- {...gridProps}
140
- onPaste={(event: React.ClipboardEvent<HTMLDivElement>) => {
141
- event.preventDefault();
142
- const pastedValue = event.clipboardData.getData("Text");
143
- const sanitizedValue = pastedValue
144
- .replace(/[^\d]/g, "")
145
- .slice(0, childCount);
146
- const value = sanitizedValue
147
- .padEnd(childCount, "#")
148
- .split("")
149
- .map((char) => (char === "#" ? "" : char));
150
-
151
- setValue(value);
152
- setLastCharIndex(sanitizedValue.length - 1);
153
-
154
- const index = Math.min(sanitizedValue.length, childCount - 1);
155
- childrenRefs.current?.[index]?.focus();
156
- }}
157
- >
158
- {children}
159
- <input
160
- ref={composeRefs(forwardedRef, hiddenInputRef)}
161
- defaultValue={value?.join("")}
162
- minLength={childCount}
163
- name={name}
164
- type="hidden"
165
- />
166
- </Grid>
167
- </OtpContext.Provider>
168
- );
169
- },
170
- );
171
-
172
- interface InputProps extends React.ComponentProps<typeof TextField> {
173
- autoComplete?: "one-time-code" | "off";
174
- }
175
-
176
- export const Input = React.forwardRef<HTMLInputElement, InputProps>(
177
- function Input(
178
- { style, readOnly, autoComplete = "off", ...props },
179
- forwardedRef,
180
- ) {
181
- const otpContext = useOptContext();
182
- const inputRef = React.useRef<HTMLInputElement>(null);
183
- const composedInputRef = useComposedRefs(
184
- forwardedRef,
185
- inputRef,
186
- otpContext.onChildAdd,
187
- );
188
-
189
- const index = Number(inputRef.current?.dataset.index ?? -1);
190
- const char = otpContext.value[index] ?? "";
191
-
192
- return (
193
- <Form.Field name={`otp-${index}`} asChild>
194
- <Form.Control asChild>
195
- <TextField
196
- ref={composedInputRef}
197
- autoComplete={index === 0 ? autoComplete : "off"}
198
- color={otpContext.state === "invalid" ? "red" : undefined}
199
- inputMode="numeric"
200
- maxLength={1}
201
- pattern="\d{1}"
202
- readOnly={readOnly ?? otpContext.readOnly}
203
- size="3"
204
- value={char}
205
- variant={otpContext.state === "invalid" ? "soft" : undefined}
206
- style={{
207
- ...style,
208
- height: "auto",
209
- "--text-field-padding": 0,
210
- textAlign: "center",
211
- }}
212
- onChange={(event) => {
213
- // Only update the value if it matches the input pattern (number only)
214
- if (event.target.validity.valid) {
215
- const char = event.target.value;
216
- const index = Number(event.target.dataset.index ?? -1);
217
- otpContext.onCharChange(char, index);
218
- if (char !== "") {
219
- focusSibling(event.currentTarget, { back: char === "" });
220
- }
221
- }
222
- }}
223
- onKeyDown={(event) => {
224
- if (event.key === "ArrowLeft") {
225
- focusSibling(event.currentTarget, { back: true });
226
- event.preventDefault();
227
- } else if (event.key === "ArrowRight") {
228
- focusSibling(event.currentTarget);
229
- event.preventDefault();
230
- } else if (event.key === "Backspace" && char === "") {
231
- focusSibling(event.currentTarget, { back: true });
232
- } else if (event.key === "Enter" && char !== "") {
233
- otpContext.onEnterPressed();
234
- }
235
- }}
236
- {...props}
237
- />
238
- </Form.Control>
239
- </Form.Field>
240
- );
241
- },
242
- );
243
-
244
- const useOptContext = () => {
245
- const optContext = React.useContext(OtpContext);
246
-
247
- if (!optContext) {
248
- throw new Error(
249
- "OtpInput compound components cannot be rendered outside the OtpRoot component",
250
- );
251
- }
252
-
253
- return optContext;
254
- };
255
-
256
- function focusSibling(input: HTMLInputElement, { back = false } = {}) {
257
- const sibling = back
258
- ? input.parentElement?.previousSibling
259
- : input.parentElement?.nextSibling;
260
- const siblingInput = sibling?.firstChild;
261
- if (siblingInput && siblingInput instanceof HTMLInputElement) {
262
- siblingInput?.focus();
263
- siblingInput?.select();
264
- }
265
- }
266
-
267
- const getValueAsArray = (value: string | undefined, length: number) => {
268
- if (!value) {
269
- return undefined;
270
- }
271
-
272
- return createEmptyArray(length).map((_, index) => value?.[index] ?? "");
273
- };
274
-
275
- const createEmptyArray = (length: number): string[] =>
276
- Array.from<string>({ length }).fill("");
@@ -1,145 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- AlertDialog,
5
- Callout,
6
- Dialog,
7
- Flex,
8
- Text,
9
- VisuallyHidden,
10
- } from "@radix-ui/themes";
11
- import * as React from "react";
12
- import { useResendUserInvite } from "./api/user";
13
- import {
14
- AlertDialogContent,
15
- DestructiveButton,
16
- DialogContent,
17
- PrimaryButton,
18
- SecondaryButton,
19
- } from "./elements";
20
- import { Member } from "../api";
21
-
22
- interface ResendInviteDialogProps extends AlertDialog.RootProps {
23
- user: Member;
24
- children?: React.ReactNode;
25
- }
26
-
27
- export function ResendInviteDialog({
28
- children,
29
- user,
30
- ...props
31
- }: ResendInviteDialogProps) {
32
- const resendInvite = useResendUserInvite();
33
- const cancelButtonRef = React.useRef<HTMLButtonElement>(null);
34
- const successButtonRef = React.useRef<HTMLButtonElement>(null);
35
- const [successDialogIsOpen, setSuccessDialogIsOpen] = React.useState(false);
36
-
37
- const onSubmitForm = () => {
38
- resendInvite.mutate(
39
- { userId: user.id },
40
- {
41
- onSuccess: () => {
42
- setSuccessDialogIsOpen(true);
43
- },
44
- },
45
- );
46
- };
47
-
48
- return (
49
- <>
50
- <AlertDialog.Root {...props}>
51
- {children && <AlertDialog.Trigger>{children}</AlertDialog.Trigger>}
52
-
53
- <AlertDialogContent
54
- maxWidth="480px"
55
- onOpenAutoFocus={() => {
56
- requestAnimationFrame(() => {
57
- cancelButtonRef.current?.focus();
58
- });
59
- }}
60
- >
61
- <AlertDialog.Title>Resend invite?</AlertDialog.Title>
62
- <Flex mb="4" direction="column" gap="3">
63
- <AlertDialog.Description>
64
- Are you sure you want to resend the invite to{" "}
65
- <Text weight="bold">{user.email}</Text>?
66
- </AlertDialog.Description>
67
- </Flex>
68
-
69
- {resendInvite.error ? (
70
- <Callout.Root color="red" mt="4" mb="-2">
71
- <Callout.Text>
72
- {getMutationErrorMessage(resendInvite.error)}
73
- </Callout.Text>
74
- </Callout.Root>
75
- ) : null}
76
-
77
- <Flex gap="3" justify="end" mt="5" asChild>
78
- <form
79
- onSubmit={(event) => {
80
- event.preventDefault();
81
- onSubmitForm();
82
- }}
83
- >
84
- <AlertDialog.Cancel>
85
- <SecondaryButton
86
- ref={cancelButtonRef}
87
- disabled={resendInvite.isPending}
88
- >
89
- Cancel
90
- </SecondaryButton>
91
- </AlertDialog.Cancel>
92
- <DestructiveButton type="submit" loading={resendInvite.isPending}>
93
- Resend
94
- </DestructiveButton>
95
- </form>
96
- </Flex>
97
- </AlertDialogContent>
98
-
99
- {/* mirror errors in a live region */}
100
- <VisuallyHidden asChild>
101
- <section aria-live="polite">
102
- {getMutationErrorMessage(resendInvite.error)}
103
- </section>
104
- </VisuallyHidden>
105
- </AlertDialog.Root>
106
- <Dialog.Root
107
- open={successDialogIsOpen}
108
- onOpenChange={(isOpen) => {
109
- if (!isOpen) {
110
- props.onOpenChange?.(false);
111
- }
112
- setSuccessDialogIsOpen(isOpen);
113
- }}
114
- >
115
- <DialogContent
116
- maxWidth="360px"
117
- onOpenAutoFocus={() => {
118
- requestAnimationFrame(() => {
119
- successButtonRef.current?.focus();
120
- });
121
- }}
122
- >
123
- <Dialog.Title>Invite sent</Dialog.Title>
124
- <Dialog.Description>
125
- The invite email has been resent to{" "}
126
- <Text weight="bold">{user.email}</Text>
127
- </Dialog.Description>
128
- <Flex gap="3" justify="end" mt="5">
129
- <Dialog.Close>
130
- <PrimaryButton ref={successButtonRef}>Close</PrimaryButton>
131
- </Dialog.Close>
132
- </Flex>
133
- </DialogContent>
134
- </Dialog.Root>
135
- </>
136
- );
137
- }
138
-
139
- function getMutationErrorMessage(error: unknown) {
140
- if (!error) {
141
- return "";
142
- }
143
- // TODO Handle server errors
144
- return "There was an error sending the invite. Please try again.";
145
- }
@@ -1,104 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import { AlertDialog, Flex } from "@radix-ui/themes";
5
- import { type ReactNode } from "react";
6
- import {
7
- AlertDialogContent,
8
- DestructiveButton,
9
- SecondaryButton,
10
- } from "./elements";
11
- import { useDeleteTotpFactors } from "../api";
12
- import { useSecuritySettings } from "./use-security-settings";
13
- import { ElevatedAccess } from "./elevated-access";
14
- import { SaveButton } from "./save-button";
15
- import { useDialogClose } from "./use-dialog-close";
16
-
17
- interface ResetMfaDialogProps extends AlertDialog.RootProps {
18
- children?: ReactNode;
19
- isPasswordSet: boolean;
20
- }
21
-
22
- export function ResetMfaDialog({
23
- children,
24
- isPasswordSet,
25
- ...props
26
- }: ResetMfaDialogProps) {
27
- const [open, setOpen] = React.useState(false);
28
-
29
- const handleClose = React.useCallback(() => {
30
- setOpen(false);
31
- }, []);
32
-
33
- return (
34
- <AlertDialog.Root {...props} open={open} onOpenChange={setOpen}>
35
- <AlertDialog.Trigger>{children}</AlertDialog.Trigger>
36
-
37
- <AlertDialogContent maxWidth="480px">
38
- <ElevatedAccess type="alert">
39
- <Content onClose={handleClose} isPasswordSet={isPasswordSet} />
40
- </ElevatedAccess>
41
- </AlertDialogContent>
42
- </AlertDialog.Root>
43
- );
44
- }
45
-
46
- function Content({
47
- onClose,
48
- isPasswordSet,
49
- }: {
50
- onClose: () => void;
51
- isPasswordSet: boolean;
52
- }) {
53
- const securitySettings = useSecuritySettings();
54
- const resetMfa = useDeleteTotpFactors();
55
-
56
- const onSubmitForm = () => {
57
- resetMfa.mutate();
58
- };
59
-
60
- useDialogClose(resetMfa.isSuccess, () => {
61
- securitySettings.update("Mfa", false);
62
- });
63
-
64
- return (
65
- <>
66
- <AlertDialog.Title>
67
- Disable multi-factor authentication?
68
- </AlertDialog.Title>
69
- <AlertDialog.Description>
70
- Turning off MFA will remove the additional layer of security on your
71
- account.{" "}
72
- {isPasswordSet
73
- ? "We will only ask for your password during sign-in."
74
- : "We will not ask for additional verification during sign-in."}
75
- </AlertDialog.Description>
76
-
77
- <Flex gap="3" justify="end" mt="5" asChild>
78
- <form
79
- onSubmit={(event) => {
80
- event.preventDefault();
81
- onSubmitForm();
82
- }}
83
- >
84
- <AlertDialog.Cancel>
85
- <SecondaryButton
86
- disabled={resetMfa.isPending || resetMfa.isSuccess}
87
- >
88
- Cancel
89
- </SecondaryButton>
90
- </AlertDialog.Cancel>
91
-
92
- <SaveButton
93
- asChild
94
- loading={resetMfa.isPending}
95
- done={resetMfa.isSuccess}
96
- onDone={onClose}
97
- >
98
- <DestructiveButton type="submit">Disable</DestructiveButton>
99
- </SaveButton>
100
- </form>
101
- </Flex>
102
- </>
103
- );
104
- }