@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,111 +0,0 @@
1
- "use client";
2
-
3
- import {
4
- AlertDialog,
5
- Callout,
6
- Flex,
7
- Text,
8
- VisuallyHidden,
9
- } from "@radix-ui/themes";
10
- import { type ReactNode, useRef } from "react";
11
- import { useRevokeUserInvite } from "./api/user";
12
- import {
13
- AlertDialogContent,
14
- DestructiveButton,
15
- SecondaryButton,
16
- } from "./elements";
17
- import { Member } from "../api";
18
-
19
- interface RevokeInviteDialogProps extends AlertDialog.RootProps {
20
- user: Member;
21
- children?: ReactNode;
22
- }
23
-
24
- export function RevokeInviteDialog({
25
- children,
26
- user,
27
- ...props
28
- }: RevokeInviteDialogProps) {
29
- const revokeInvite = useRevokeUserInvite();
30
- const cancelButtonRef = useRef<HTMLButtonElement>(null);
31
-
32
- const onSubmitForm = () => {
33
- revokeInvite.mutate(
34
- { userId: user.id },
35
- {
36
- onSuccess: () => {
37
- props.onOpenChange?.(false);
38
- },
39
- },
40
- );
41
- };
42
-
43
- return (
44
- <AlertDialog.Root {...props}>
45
- {children && <AlertDialog.Trigger>{children}</AlertDialog.Trigger>}
46
-
47
- <AlertDialogContent
48
- maxWidth="480px"
49
- onOpenAutoFocus={() => {
50
- requestAnimationFrame(() => {
51
- cancelButtonRef.current?.focus();
52
- });
53
- }}
54
- >
55
- <AlertDialog.Title>Revoke invite?</AlertDialog.Title>
56
- <Flex mb="4" direction="column" gap="3">
57
- <AlertDialog.Description>
58
- Are you sure you want to revoke the invite to{" "}
59
- <Text weight="bold">{user.email}</Text>? This action is immediate
60
- and cannot be undone.
61
- </AlertDialog.Description>
62
- </Flex>
63
-
64
- {revokeInvite.error ? (
65
- <Callout.Root color="red" mt="4" mb="-2">
66
- <Callout.Text>
67
- {getMutationErrorMessage(revokeInvite.error)}
68
- </Callout.Text>
69
- </Callout.Root>
70
- ) : null}
71
-
72
- <Flex gap="3" justify="end" mt="5" asChild>
73
- <form
74
- onSubmit={(event) => {
75
- event.preventDefault();
76
- onSubmitForm();
77
- }}
78
- >
79
- <AlertDialog.Cancel>
80
- <SecondaryButton
81
- ref={cancelButtonRef}
82
- disabled={revokeInvite.isPending}
83
- >
84
- Cancel
85
- </SecondaryButton>
86
- </AlertDialog.Cancel>
87
-
88
- <DestructiveButton type="submit" loading={revokeInvite.isPending}>
89
- Revoke
90
- </DestructiveButton>
91
- </form>
92
- </Flex>
93
- </AlertDialogContent>
94
-
95
- {/* mirror errors in a live region */}
96
- <VisuallyHidden asChild>
97
- <section aria-live="polite">
98
- {getMutationErrorMessage(revokeInvite.error)}
99
- </section>
100
- </VisuallyHidden>
101
- </AlertDialog.Root>
102
- );
103
- }
104
-
105
- function getMutationErrorMessage(error: unknown) {
106
- if (!error) {
107
- return null;
108
- }
109
- // TODO Handle server errors
110
- return "There was an error revoking the invite. Please try again.";
111
- }
@@ -1,113 +0,0 @@
1
- import {
2
- ButtonProps,
3
- Flex,
4
- Slot,
5
- Slottable,
6
- Spinner,
7
- Text,
8
- } from "@radix-ui/themes";
9
- import { CheckIcon } from "@radix-ui/react-icons";
10
- import { useEffect, useRef, useState } from "react";
11
- import { namespaceClassNames } from "./utils";
12
-
13
- const DONE_TIMEOUT_MS = 1500;
14
- const SAVING_TIMEOUT_MS = 600;
15
-
16
- interface SaveButtonProps extends ButtonProps {
17
- asChild?: boolean;
18
- children: React.ReactNode;
19
- loading?: boolean;
20
- done?: boolean;
21
- onDone?: () => void;
22
- }
23
-
24
- export function SaveButton({
25
- asChild = false,
26
- children,
27
- loading,
28
- done,
29
- onDone,
30
- ...props
31
- }: SaveButtonProps) {
32
- const [state, setState] = useState<"idle" | "loading" | "done">(
33
- loading ? "loading" : done ? "done" : "idle",
34
- );
35
- const loadingStartTime = useRef<number | null>(null);
36
- const Button = asChild ? Slot : "button";
37
-
38
- useEffect(() => {
39
- if (loading) {
40
- setState("loading");
41
- loadingStartTime.current = Date.now();
42
- } else if (done) {
43
- const currentTime = Date.now();
44
- const loadingDuration = loadingStartTime.current
45
- ? currentTime - loadingStartTime.current
46
- : 0;
47
-
48
- // If loading lasted less than 500 ms, wait for the remaining time
49
- const remainingDelay = Math.max(0, SAVING_TIMEOUT_MS - loadingDuration);
50
-
51
- let doneTimer: NodeJS.Timeout | null = null;
52
- const savedTimer = setTimeout(() => {
53
- setState("done");
54
-
55
- doneTimer = setTimeout(() => {
56
- onDone?.();
57
- }, DONE_TIMEOUT_MS);
58
- }, remainingDelay);
59
-
60
- return () => {
61
- clearTimeout(savedTimer);
62
- if (doneTimer) {
63
- clearTimeout(doneTimer);
64
- }
65
- };
66
- } else if (!loading && !done) {
67
- setState("idle");
68
- }
69
- }, [loading, done, onDone]);
70
-
71
- return (
72
- <Button
73
- {...props}
74
- className={namespaceClassNames("save-button")}
75
- data-save-state={state}
76
- >
77
- <Slottable>{children}</Slottable>
78
-
79
- {state === "loading" && (
80
- <Flex
81
- as="span"
82
- align="center"
83
- justify="center"
84
- position="absolute"
85
- inset="0"
86
- >
87
- <Spinner size="1" />
88
- </Flex>
89
- )}
90
-
91
- {state === "done" && (
92
- <Flex
93
- as="span"
94
- align="center"
95
- justify="center"
96
- position="absolute"
97
- inset="0"
98
- gap="1"
99
- >
100
- <CheckIcon
101
- width="18px"
102
- height="18px"
103
- style={{ marginLeft: "-4px" }}
104
- className={namespaceClassNames("save-button__done-icon")}
105
- />
106
- <Text className={namespaceClassNames("save-button__done-text")}>
107
- Done
108
- </Text>
109
- </Flex>
110
- )}
111
- </Button>
112
- );
113
- }
@@ -1,51 +0,0 @@
1
- import * as React from "react";
2
- import { flushSync } from "react-dom";
3
- import { useUsersManagementContext } from "./users-management-context";
4
-
5
- interface SearchContextType {
6
- inputRef: React.RefObject<HTMLInputElement>;
7
- clearSearch: () => void;
8
- searchValue: string;
9
- setSearchValue: React.Dispatch<React.SetStateAction<string>>;
10
- }
11
-
12
- const SearchContext = React.createContext<SearchContextType | undefined>(
13
- undefined,
14
- );
15
- SearchContext.displayName = "SearchContext";
16
-
17
- export const SearchProvider: React.FC<React.PropsWithChildren> = ({
18
- children,
19
- }) => {
20
- const inputRef = React.useRef<HTMLInputElement>(null);
21
- const {
22
- state: { searchQuery },
23
- dispatch,
24
- } = useUsersManagementContext();
25
-
26
- const [searchValue, setSearchValue] = React.useState(searchQuery || "");
27
-
28
- const clearSearch = React.useCallback(() => {
29
- flushSync(() => {
30
- setSearchValue("");
31
- dispatch({ type: "FILTER_BY_SEARCH", searchQuery: "" });
32
- });
33
- inputRef.current?.focus();
34
- }, [dispatch]);
35
-
36
- return (
37
- <SearchContext.Provider
38
- value={{ inputRef, clearSearch, searchValue, setSearchValue }}
39
- >
40
- {children}
41
- </SearchContext.Provider>
42
- );
43
- };
44
-
45
- export const useSearchContext = () => {
46
- const context = React.useContext(SearchContext);
47
- if (!context) {
48
- throw new Error("useSearchContext must be used within a SearchProvider");
49
- }
50
- return context;
51
- };
@@ -1,204 +0,0 @@
1
- "use client";
2
-
3
- import { Callout, Dialog, Flex, Text, VisuallyHidden } from "@radix-ui/themes";
4
- import * as React from "react";
5
- import { type ReactNode } from "react";
6
- import {
7
- DialogContent,
8
- Label,
9
- PasswordField,
10
- PrimaryButton,
11
- SecondaryButton,
12
- } from "./elements";
13
- import * as Form from "@radix-ui/react-form";
14
- import { useCreatePassword } from "../api";
15
- import { useSecuritySettings } from "./use-security-settings";
16
- import { ElevatedAccess } from "./elevated-access";
17
- import { SaveButton } from "./save-button";
18
- import { useDialogClose } from "./use-dialog-close";
19
-
20
- interface SetPasswordDialogProps extends Dialog.RootProps {
21
- children?: ReactNode;
22
- }
23
-
24
- export function SetPasswordDialog({
25
- children,
26
- ...props
27
- }: SetPasswordDialogProps) {
28
- const [open, setOpen] = React.useState(false);
29
-
30
- const handleClose = React.useCallback(() => {
31
- setOpen(false);
32
- }, []);
33
-
34
- return (
35
- <Dialog.Root {...props} open={open} onOpenChange={setOpen}>
36
- <Dialog.Trigger>{children}</Dialog.Trigger>
37
-
38
- <DialogContent maxWidth="480px">
39
- <ElevatedAccess>
40
- <Content onClose={handleClose} />
41
- </ElevatedAccess>
42
- </DialogContent>
43
- </Dialog.Root>
44
- );
45
- }
46
-
47
- interface ContentProps {
48
- onClose: () => void;
49
- }
50
-
51
- function Content({ onClose }: ContentProps) {
52
- const setPassword = useCreatePassword();
53
- const securitySettings = useSecuritySettings();
54
- const [disableSubmit, setDisableSubmit] = React.useState(true);
55
-
56
- const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
57
- event.preventDefault();
58
-
59
- const formData = new FormData(event.currentTarget);
60
- const password = formData.get("password")?.toString();
61
-
62
- if (!password) {
63
- return;
64
- }
65
-
66
- setPassword.mutate({ data: { password } });
67
- };
68
-
69
- useDialogClose(setPassword.isSuccess, () => {
70
- securitySettings.update("Password", true);
71
- });
72
-
73
- return (
74
- <>
75
- <Dialog.Title mb="5">Set New Password</Dialog.Title>
76
- <VisuallyHidden>
77
- <Dialog.Description>
78
- Set a new password for your account
79
- </Dialog.Description>
80
- </VisuallyHidden>
81
-
82
- {setPassword.error ? (
83
- <Callout.Root color="red" my="-2">
84
- <Callout.Text>
85
- {getMutationErrorMessage(setPassword.error)}
86
- </Callout.Text>
87
- </Callout.Root>
88
- ) : null}
89
-
90
- <Form.Root
91
- onSubmit={handleSubmit}
92
- onChange={(event) => {
93
- const formData = new FormData(event.currentTarget);
94
- const password = formData.get("password")?.toString();
95
- const confirmPassword = formData.get("confirmPassword")?.toString();
96
- setDisableSubmit(password === "" || confirmPassword === "");
97
- }}
98
- >
99
- <Flex mt="5" direction="column" gap="4">
100
- <Form.Field name="password" asChild>
101
- <Flex direction="column" gap="2">
102
- <Form.Label asChild>
103
- <Label>Enter a new password</Label>
104
- </Form.Label>
105
- <Form.Control asChild>
106
- <PasswordField
107
- autoFocus
108
- required
109
- autoComplete="new-password"
110
- placeholder="New password"
111
- disabled={setPassword.isPending || setPassword.isSuccess}
112
- />
113
- </Form.Control>
114
- <Form.Message match="valueMissing" asChild>
115
- <Text size="2" color="red">
116
- Please enter a new password
117
- </Text>
118
- </Form.Message>
119
- <Form.Message match={(value) => value.length < 8} asChild>
120
- <Text size="2" color="red">
121
- Password must be at least 8 characters
122
- </Text>
123
- </Form.Message>
124
- </Flex>
125
- </Form.Field>
126
-
127
- <Form.Field name="confirmPassword" asChild>
128
- <Flex direction="column" gap="2">
129
- <Form.Label asChild>
130
- <Label>Confirm your new password</Label>
131
- </Form.Label>
132
- <Form.Control asChild>
133
- <PasswordField
134
- required
135
- autoComplete="new-password"
136
- placeholder="Confirm new password"
137
- disabled={setPassword.isPending || setPassword.isSuccess}
138
- />
139
- </Form.Control>
140
- <Form.Message match="valueMissing" asChild>
141
- <Text size="2" color="red">
142
- Please confirm your new password
143
- </Text>
144
- </Form.Message>
145
- <Form.Message
146
- match={(value, formData) => value !== formData.get("password")}
147
- asChild
148
- >
149
- <Text size="2" color="red">
150
- The passwords you entered don’t match.
151
- </Text>
152
- </Form.Message>
153
- </Flex>
154
- </Form.Field>
155
- </Flex>
156
-
157
- <Flex mt="5" gap="3" justify="end">
158
- <Dialog.Close>
159
- <SecondaryButton
160
- disabled={setPassword.isPending || setPassword.isSuccess}
161
- >
162
- Cancel
163
- </SecondaryButton>
164
- </Dialog.Close>
165
-
166
- <SaveButton
167
- asChild
168
- loading={setPassword.isPending}
169
- done={setPassword.isSuccess}
170
- onDone={onClose}
171
- >
172
- <PrimaryButton type="submit" disabled={disableSubmit}>
173
- Set password
174
- </PrimaryButton>
175
- </SaveButton>
176
- </Flex>
177
-
178
- {/* mirror errors in a live region */}
179
- <VisuallyHidden asChild>
180
- <section aria-live="polite">
181
- {getMutationErrorMessage(setPassword.error)}
182
- </section>
183
- </VisuallyHidden>
184
- </Form.Root>
185
- </>
186
- );
187
- }
188
-
189
- function getMutationErrorMessage(error: unknown) {
190
- if (error instanceof Error) {
191
- return error.message;
192
- }
193
-
194
- if (
195
- typeof error === "object" &&
196
- error !== null &&
197
- "message" in error &&
198
- typeof error.message === "string"
199
- ) {
200
- return error.message;
201
- }
202
-
203
- return "Something went wrong. Please try again.";
204
- }
@@ -1,19 +0,0 @@
1
- import * as React from "react";
2
-
3
- export function useDialogClose(enabled: boolean, callback: () => void) {
4
- const callbackRef = React.useRef(callback);
5
- const firstRenderRef = React.useRef(true);
6
-
7
- React.useEffect(() => {
8
- if (firstRenderRef.current) {
9
- firstRenderRef.current = false;
10
- return;
11
- }
12
-
13
- const cb = callbackRef.current;
14
-
15
- return () => {
16
- if (enabled) cb();
17
- };
18
- }, [enabled]);
19
- }
@@ -1,13 +0,0 @@
1
- import * as React from "react";
2
-
3
- function subscribe() {
4
- return () => {};
5
- }
6
-
7
- export function useIsHydrated() {
8
- return React.useSyncExternalStore(
9
- subscribe,
10
- () => true,
11
- () => false,
12
- );
13
- }
@@ -1,6 +0,0 @@
1
- import { useLayoutEffect } from "react";
2
- import { canUseDOM } from "./utils";
3
-
4
- const useIsoLayoutEffect = canUseDOM ? useLayoutEffect : () => void 0;
5
-
6
- export { useIsoLayoutEffect as useLayoutEffect };
@@ -1,49 +0,0 @@
1
- import {
2
- AuthenticationInformationResponse,
3
- getAuthenticationInformationQueryKey,
4
- } from "../api";
5
- import { useQueryClient } from "@tanstack/react-query";
6
-
7
- type Methods = "Password" | "Mfa" | "Passkey";
8
-
9
- /**
10
- * A hook that provides utilities for managing security settings.
11
- * Allows optimistically updating verification method status (password, MFA, passkey)
12
- * while also triggering a server revalidation.
13
- */
14
- export function useSecuritySettings() {
15
- const client = useQueryClient();
16
-
17
- const update = (method: Methods, isSetUp: boolean) => {
18
- const queryKey = getAuthenticationInformationQueryKey();
19
-
20
- // Optimistic update
21
- client.setQueriesData<AuthenticationInformationResponse>(
22
- { queryKey, exact: false },
23
- (data) => {
24
- if (!data) {
25
- return data;
26
- }
27
-
28
- return {
29
- ...data,
30
- data: {
31
- ...data.data,
32
- verificationMethods: {
33
- ...data.data.verificationMethods,
34
- [method]: { ...data.data.verificationMethods[method], isSetUp },
35
- },
36
- },
37
- };
38
- },
39
- );
40
-
41
- // Also validate it with the server
42
- client.invalidateQueries({
43
- queryKey: getAuthenticationInformationQueryKey(),
44
- exact: false,
45
- });
46
- };
47
-
48
- return { update };
49
- }