@workos-inc/widgets 1.1.5 → 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 (82) hide show
  1. package/dist/cjs/lib/organization-switcher.d.ts +10 -1
  2. package/dist/cjs/lib/organization-switcher.d.ts.map +1 -1
  3. package/dist/cjs/lib/organization-switcher.js +31 -3
  4. package/dist/cjs/lib/organization-switcher.js.map +1 -1
  5. package/dist/cjs/workos-widgets.client.d.ts +6 -0
  6. package/dist/cjs/workos-widgets.client.d.ts.map +1 -1
  7. package/dist/cjs/workos-widgets.client.js +23 -3
  8. package/dist/cjs/workos-widgets.client.js.map +1 -1
  9. package/dist/esm/lib/organization-switcher.d.ts +10 -1
  10. package/dist/esm/lib/organization-switcher.d.ts.map +1 -1
  11. package/dist/esm/lib/organization-switcher.js +31 -3
  12. package/dist/esm/lib/organization-switcher.js.map +1 -1
  13. package/dist/esm/workos-widgets.client.d.ts +6 -0
  14. package/dist/esm/workos-widgets.client.d.ts.map +1 -1
  15. package/dist/esm/workos-widgets.client.js +25 -5
  16. package/dist/esm/workos-widgets.client.js.map +1 -1
  17. package/package.json +8 -9
  18. package/src/api/api-provider.tsx +0 -158
  19. package/src/api/constants.ts +0 -1
  20. package/src/api/endpoint.ts +0 -3097
  21. package/src/api/errors.ts +0 -48
  22. package/src/api/index.ts +0 -2
  23. package/src/api/utils.ts +0 -42
  24. package/src/api/widgets-api-client.ts +0 -87
  25. package/src/card-list.tsx +0 -26
  26. package/src/index.ts +0 -9
  27. package/src/lib/add-mfa-dialog.tsx +0 -379
  28. package/src/lib/api/config.ts +0 -9
  29. package/src/lib/api/user.ts +0 -98
  30. package/src/lib/change-password-dialog.tsx +0 -290
  31. package/src/lib/constants.ts +0 -3
  32. package/src/lib/copy-button.tsx +0 -53
  33. package/src/lib/delete-user-dialog.tsx +0 -110
  34. package/src/lib/edit-user-profile-dialog.tsx +0 -181
  35. package/src/lib/edit-user-role-dialog.tsx +0 -178
  36. package/src/lib/elements.tsx +0 -428
  37. package/src/lib/elevated-access.tsx +0 -261
  38. package/src/lib/error-boundary.tsx +0 -166
  39. package/src/lib/errors.ts +0 -49
  40. package/src/lib/generic-error.tsx +0 -70
  41. package/src/lib/icon-panel.tsx +0 -26
  42. package/src/lib/icons.tsx +0 -21
  43. package/src/lib/invite-user-dialog.tsx +0 -327
  44. package/src/lib/logout-all-sessions-dialog.tsx +0 -82
  45. package/src/lib/logout-dialog.tsx +0 -85
  46. package/src/lib/marker.tsx +0 -39
  47. package/src/lib/oauth-icons.tsx +0 -138
  48. package/src/lib/organization-switcher.tsx +0 -156
  49. package/src/lib/otp-input.tsx +0 -276
  50. package/src/lib/resend-invite-dialog.tsx +0 -145
  51. package/src/lib/reset-mfa-dialog.tsx +0 -104
  52. package/src/lib/revoke-invite-dialog.tsx +0 -111
  53. package/src/lib/save-button.tsx +0 -113
  54. package/src/lib/search-provider.tsx +0 -51
  55. package/src/lib/set-password-dialog.tsx +0 -204
  56. package/src/lib/use-dialog-close.tsx +0 -19
  57. package/src/lib/use-is-hydrated.ts +0 -13
  58. package/src/lib/use-layout-effect.ts +0 -6
  59. package/src/lib/use-security-settings.tsx +0 -49
  60. package/src/lib/user-actions-dropdown.tsx +0 -157
  61. package/src/lib/user-profile.tsx +0 -227
  62. package/src/lib/user-security.tsx +0 -187
  63. package/src/lib/user-sessions.tsx +0 -204
  64. package/src/lib/users-filter.tsx +0 -62
  65. package/src/lib/users-management-context.tsx +0 -74
  66. package/src/lib/users-management-state.ts +0 -165
  67. package/src/lib/users-management.tsx +0 -594
  68. package/src/lib/users-search.tsx +0 -73
  69. package/src/lib/utils.ts +0 -131
  70. package/src/lib/widgets-context.ts +0 -29
  71. package/src/organization-switcher.client.tsx +0 -81
  72. package/src/user-profile.client.tsx +0 -55
  73. package/src/user-security.client.tsx +0 -55
  74. package/src/user-sessions.client.tsx +0 -100
  75. package/src/users-management.client.tsx +0 -73
  76. package/src/workos-widgets.client.tsx +0 -75
  77. /package/{src → dist/css}/base.css +0 -0
  78. /package/{src → dist/css}/lib/card-list.css +0 -0
  79. /package/{src → dist/css}/lib/marker.css +0 -0
  80. /package/{src → dist/css}/lib/save-button.css +0 -0
  81. /package/{src → dist/css}/styles.css +0 -0
  82. /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
- }