@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.
- package/dist/cjs/lib/organization-switcher.d.ts +10 -1
- package/dist/cjs/lib/organization-switcher.d.ts.map +1 -1
- package/dist/cjs/lib/organization-switcher.js +31 -3
- package/dist/cjs/lib/organization-switcher.js.map +1 -1
- package/dist/cjs/workos-widgets.client.d.ts +6 -0
- package/dist/cjs/workos-widgets.client.d.ts.map +1 -1
- package/dist/cjs/workos-widgets.client.js +23 -3
- package/dist/cjs/workos-widgets.client.js.map +1 -1
- package/dist/esm/lib/organization-switcher.d.ts +10 -1
- package/dist/esm/lib/organization-switcher.d.ts.map +1 -1
- package/dist/esm/lib/organization-switcher.js +31 -3
- package/dist/esm/lib/organization-switcher.js.map +1 -1
- package/dist/esm/workos-widgets.client.d.ts +6 -0
- package/dist/esm/workos-widgets.client.d.ts.map +1 -1
- package/dist/esm/workos-widgets.client.js +25 -5
- package/dist/esm/workos-widgets.client.js.map +1 -1
- package/package.json +8 -9
- package/src/api/api-provider.tsx +0 -158
- package/src/api/constants.ts +0 -1
- package/src/api/endpoint.ts +0 -3097
- package/src/api/errors.ts +0 -48
- package/src/api/index.ts +0 -2
- package/src/api/utils.ts +0 -42
- package/src/api/widgets-api-client.ts +0 -87
- package/src/card-list.tsx +0 -26
- package/src/index.ts +0 -9
- package/src/lib/add-mfa-dialog.tsx +0 -379
- package/src/lib/api/config.ts +0 -9
- package/src/lib/api/user.ts +0 -98
- package/src/lib/change-password-dialog.tsx +0 -290
- package/src/lib/constants.ts +0 -3
- package/src/lib/copy-button.tsx +0 -53
- package/src/lib/delete-user-dialog.tsx +0 -110
- package/src/lib/edit-user-profile-dialog.tsx +0 -181
- package/src/lib/edit-user-role-dialog.tsx +0 -178
- package/src/lib/elements.tsx +0 -428
- package/src/lib/elevated-access.tsx +0 -261
- package/src/lib/error-boundary.tsx +0 -166
- package/src/lib/errors.ts +0 -49
- package/src/lib/generic-error.tsx +0 -70
- package/src/lib/icon-panel.tsx +0 -26
- package/src/lib/icons.tsx +0 -21
- package/src/lib/invite-user-dialog.tsx +0 -327
- package/src/lib/logout-all-sessions-dialog.tsx +0 -82
- package/src/lib/logout-dialog.tsx +0 -85
- package/src/lib/marker.tsx +0 -39
- package/src/lib/oauth-icons.tsx +0 -138
- package/src/lib/organization-switcher.tsx +0 -156
- package/src/lib/otp-input.tsx +0 -276
- package/src/lib/resend-invite-dialog.tsx +0 -145
- package/src/lib/reset-mfa-dialog.tsx +0 -104
- package/src/lib/revoke-invite-dialog.tsx +0 -111
- package/src/lib/save-button.tsx +0 -113
- package/src/lib/search-provider.tsx +0 -51
- package/src/lib/set-password-dialog.tsx +0 -204
- package/src/lib/use-dialog-close.tsx +0 -19
- package/src/lib/use-is-hydrated.ts +0 -13
- package/src/lib/use-layout-effect.ts +0 -6
- package/src/lib/use-security-settings.tsx +0 -49
- package/src/lib/user-actions-dropdown.tsx +0 -157
- package/src/lib/user-profile.tsx +0 -227
- package/src/lib/user-security.tsx +0 -187
- package/src/lib/user-sessions.tsx +0 -204
- package/src/lib/users-filter.tsx +0 -62
- package/src/lib/users-management-context.tsx +0 -74
- package/src/lib/users-management-state.ts +0 -165
- package/src/lib/users-management.tsx +0 -594
- package/src/lib/users-search.tsx +0 -73
- package/src/lib/utils.ts +0 -131
- package/src/lib/widgets-context.ts +0 -29
- package/src/organization-switcher.client.tsx +0 -81
- package/src/user-profile.client.tsx +0 -55
- package/src/user-security.client.tsx +0 -55
- package/src/user-sessions.client.tsx +0 -100
- package/src/users-management.client.tsx +0 -73
- package/src/workos-widgets.client.tsx +0 -75
- /package/{src → dist/css}/base.css +0 -0
- /package/{src → dist/css}/lib/card-list.css +0 -0
- /package/{src → dist/css}/lib/marker.css +0 -0
- /package/{src → dist/css}/lib/save-button.css +0 -0
- /package/{src → dist/css}/styles.css +0 -0
- /package/{src → dist/css}/users-management.css +0 -0
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Callout, Dialog, Flex, Text, VisuallyHidden } from "@radix-ui/themes";
|
|
4
|
-
import * as Form from "@radix-ui/react-form";
|
|
5
|
-
import * as React from "react";
|
|
6
|
-
import { type ReactNode } from "react";
|
|
7
|
-
import {
|
|
8
|
-
DialogContent,
|
|
9
|
-
Label,
|
|
10
|
-
PasswordField,
|
|
11
|
-
PrimaryButton,
|
|
12
|
-
SecondaryButton,
|
|
13
|
-
} from "./elements";
|
|
14
|
-
import { useUpdatePassword } from "../api";
|
|
15
|
-
import { SaveButton } from "./save-button";
|
|
16
|
-
|
|
17
|
-
interface ChangePasswordDialogProps extends Dialog.RootProps {
|
|
18
|
-
children?: ReactNode;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function ChangePasswordDialog({
|
|
22
|
-
children,
|
|
23
|
-
...props
|
|
24
|
-
}: ChangePasswordDialogProps) {
|
|
25
|
-
const [open, setOpen] = React.useState(false);
|
|
26
|
-
|
|
27
|
-
const handleClose = React.useCallback(() => {
|
|
28
|
-
setOpen(false);
|
|
29
|
-
}, []);
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<Dialog.Root
|
|
33
|
-
{...props}
|
|
34
|
-
open={props.open || open}
|
|
35
|
-
onOpenChange={props.onOpenChange || setOpen}
|
|
36
|
-
>
|
|
37
|
-
<Dialog.Trigger>{children}</Dialog.Trigger>
|
|
38
|
-
|
|
39
|
-
<DialogContent maxWidth="480px">
|
|
40
|
-
<Content onClose={handleClose} />
|
|
41
|
-
</DialogContent>
|
|
42
|
-
</Dialog.Root>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface ContentProps {
|
|
47
|
-
onClose: () => void;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function Content({ onClose }: ContentProps) {
|
|
51
|
-
const changePassword = useUpdatePassword({
|
|
52
|
-
mutation: {
|
|
53
|
-
onError: (error) => {
|
|
54
|
-
const errorMsg = getMutationErrorMessage(error);
|
|
55
|
-
if (errorMsg === "Invalid credentials") {
|
|
56
|
-
setServerErrors({
|
|
57
|
-
currentPassword:
|
|
58
|
-
"Your current password is incorrect. Please, try again.",
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
const [disableSubmit, setDisableSubmit] = React.useState(true);
|
|
65
|
-
const [serverErrors, setServerErrors] = React.useState<{
|
|
66
|
-
currentPassword?: string;
|
|
67
|
-
password?: string;
|
|
68
|
-
confirmPassword?: string;
|
|
69
|
-
}>({});
|
|
70
|
-
|
|
71
|
-
const errorMessage = React.useMemo(() => {
|
|
72
|
-
if (changePassword.error) {
|
|
73
|
-
return getMutationErrorMessage(changePassword.error);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
}, [changePassword.error]);
|
|
78
|
-
|
|
79
|
-
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
80
|
-
event.preventDefault();
|
|
81
|
-
|
|
82
|
-
const formData = new FormData(event.currentTarget);
|
|
83
|
-
const currentPassword = formData.get("oldPassword")?.toString() ?? "";
|
|
84
|
-
const newPassword = formData.get("password")?.toString() ?? "";
|
|
85
|
-
|
|
86
|
-
changePassword.mutate({
|
|
87
|
-
data: { currentPassword, newPassword },
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<>
|
|
93
|
-
<Dialog.Title mb="5">Change Password</Dialog.Title>
|
|
94
|
-
<VisuallyHidden>
|
|
95
|
-
<Dialog.Description>Change your account password</Dialog.Description>
|
|
96
|
-
</VisuallyHidden>
|
|
97
|
-
|
|
98
|
-
{errorMessage && errorMessage !== "Invalid credentials" ? (
|
|
99
|
-
<Callout.Root color="red" my="-2">
|
|
100
|
-
<Callout.Text>{errorMessage}</Callout.Text>
|
|
101
|
-
</Callout.Root>
|
|
102
|
-
) : null}
|
|
103
|
-
|
|
104
|
-
<Form.Root
|
|
105
|
-
onSubmit={handleSubmit}
|
|
106
|
-
onChange={(event) => {
|
|
107
|
-
const formData = new FormData(event.currentTarget);
|
|
108
|
-
const currentPassword = formData.get("oldPassword")?.toString() ?? "";
|
|
109
|
-
const newPassword = formData.get("password")?.toString() ?? "";
|
|
110
|
-
const confirmPassword =
|
|
111
|
-
formData.get("confirmPassword")?.toString() ?? "";
|
|
112
|
-
|
|
113
|
-
setDisableSubmit(
|
|
114
|
-
currentPassword === "" ||
|
|
115
|
-
newPassword === "" ||
|
|
116
|
-
confirmPassword === "",
|
|
117
|
-
);
|
|
118
|
-
setServerErrors({});
|
|
119
|
-
}}
|
|
120
|
-
onClearServerErrors={() => {
|
|
121
|
-
setServerErrors({});
|
|
122
|
-
}}
|
|
123
|
-
>
|
|
124
|
-
<Flex mt="5" direction="column" gap="4">
|
|
125
|
-
<Form.Field
|
|
126
|
-
name="oldPassword"
|
|
127
|
-
asChild
|
|
128
|
-
serverInvalid={!serverErrors.currentPassword}
|
|
129
|
-
>
|
|
130
|
-
<Flex direction="column" gap="2">
|
|
131
|
-
<Form.Label asChild>
|
|
132
|
-
<Label>Enter your current password</Label>
|
|
133
|
-
</Form.Label>
|
|
134
|
-
|
|
135
|
-
<Form.Control asChild>
|
|
136
|
-
<PasswordField
|
|
137
|
-
autoFocus
|
|
138
|
-
required
|
|
139
|
-
disabled={
|
|
140
|
-
changePassword.isPending || changePassword.isSuccess
|
|
141
|
-
}
|
|
142
|
-
autoComplete="current-password"
|
|
143
|
-
placeholder="Current password"
|
|
144
|
-
/>
|
|
145
|
-
</Form.Control>
|
|
146
|
-
|
|
147
|
-
<Form.Message match="valueMissing" asChild>
|
|
148
|
-
<Text size="2" color="red">
|
|
149
|
-
Please enter your current password
|
|
150
|
-
</Text>
|
|
151
|
-
</Form.Message>
|
|
152
|
-
|
|
153
|
-
{serverErrors.currentPassword && (
|
|
154
|
-
<Form.Message asChild>
|
|
155
|
-
<Text size="2" color="red">
|
|
156
|
-
{serverErrors.currentPassword}
|
|
157
|
-
</Text>
|
|
158
|
-
</Form.Message>
|
|
159
|
-
)}
|
|
160
|
-
</Flex>
|
|
161
|
-
</Form.Field>
|
|
162
|
-
|
|
163
|
-
<Form.Field
|
|
164
|
-
name="password"
|
|
165
|
-
asChild
|
|
166
|
-
serverInvalid={!serverErrors.password}
|
|
167
|
-
>
|
|
168
|
-
<Flex direction="column" gap="2">
|
|
169
|
-
<Form.Label asChild>
|
|
170
|
-
<Label>Enter your new password</Label>
|
|
171
|
-
</Form.Label>
|
|
172
|
-
|
|
173
|
-
<Form.Control asChild>
|
|
174
|
-
<PasswordField
|
|
175
|
-
autoComplete="new-password"
|
|
176
|
-
placeholder="New password"
|
|
177
|
-
required
|
|
178
|
-
disabled={
|
|
179
|
-
changePassword.isPending || changePassword.isSuccess
|
|
180
|
-
}
|
|
181
|
-
/>
|
|
182
|
-
</Form.Control>
|
|
183
|
-
|
|
184
|
-
<Form.Message match="valueMissing" asChild>
|
|
185
|
-
<Text size="2" color="red">
|
|
186
|
-
Please enter your new password
|
|
187
|
-
</Text>
|
|
188
|
-
</Form.Message>
|
|
189
|
-
<Form.Message match={(value) => value.length < 8} asChild>
|
|
190
|
-
<Text size="2" color="red">
|
|
191
|
-
Password must be at least 8 characters
|
|
192
|
-
</Text>
|
|
193
|
-
</Form.Message>
|
|
194
|
-
<Form.Message
|
|
195
|
-
match={(value, formData) =>
|
|
196
|
-
value === formData.get("oldPassword")
|
|
197
|
-
}
|
|
198
|
-
asChild
|
|
199
|
-
>
|
|
200
|
-
<Text size="2" color="red">
|
|
201
|
-
You cannot reuse your current password
|
|
202
|
-
</Text>
|
|
203
|
-
</Form.Message>
|
|
204
|
-
</Flex>
|
|
205
|
-
</Form.Field>
|
|
206
|
-
|
|
207
|
-
<Form.Field
|
|
208
|
-
name="confirmPassword"
|
|
209
|
-
asChild
|
|
210
|
-
serverInvalid={!serverErrors.confirmPassword}
|
|
211
|
-
>
|
|
212
|
-
<Flex direction="column" gap="2">
|
|
213
|
-
<Form.Label asChild>
|
|
214
|
-
<Label>Confirm your new password</Label>
|
|
215
|
-
</Form.Label>
|
|
216
|
-
|
|
217
|
-
<Form.Control asChild>
|
|
218
|
-
<PasswordField
|
|
219
|
-
autoComplete="new-password"
|
|
220
|
-
placeholder="Confirm new password"
|
|
221
|
-
required
|
|
222
|
-
disabled={
|
|
223
|
-
changePassword.isPending || changePassword.isSuccess
|
|
224
|
-
}
|
|
225
|
-
/>
|
|
226
|
-
</Form.Control>
|
|
227
|
-
<Form.Message match="valueMissing" asChild>
|
|
228
|
-
<Text size="2" color="red">
|
|
229
|
-
Please confirm your new password
|
|
230
|
-
</Text>
|
|
231
|
-
</Form.Message>
|
|
232
|
-
<Form.Message
|
|
233
|
-
match={(value, formData) => value !== formData.get("password")}
|
|
234
|
-
asChild
|
|
235
|
-
>
|
|
236
|
-
<Text size="2" color="red">
|
|
237
|
-
The new passwords you entered don’t match.
|
|
238
|
-
</Text>
|
|
239
|
-
</Form.Message>
|
|
240
|
-
</Flex>
|
|
241
|
-
</Form.Field>
|
|
242
|
-
</Flex>
|
|
243
|
-
|
|
244
|
-
<Flex mt="5" gap="3" justify="end">
|
|
245
|
-
<Dialog.Close>
|
|
246
|
-
<SecondaryButton
|
|
247
|
-
type="button"
|
|
248
|
-
disabled={changePassword.isPending || changePassword.isSuccess}
|
|
249
|
-
>
|
|
250
|
-
Cancel
|
|
251
|
-
</SecondaryButton>
|
|
252
|
-
</Dialog.Close>
|
|
253
|
-
|
|
254
|
-
<SaveButton
|
|
255
|
-
asChild
|
|
256
|
-
loading={changePassword.isPending}
|
|
257
|
-
done={changePassword.isSuccess}
|
|
258
|
-
onDone={onClose}
|
|
259
|
-
>
|
|
260
|
-
<PrimaryButton type="submit" disabled={disableSubmit}>
|
|
261
|
-
Change password
|
|
262
|
-
</PrimaryButton>
|
|
263
|
-
</SaveButton>
|
|
264
|
-
</Flex>
|
|
265
|
-
</Form.Root>
|
|
266
|
-
|
|
267
|
-
{/* mirror errors in a live region */}
|
|
268
|
-
<VisuallyHidden asChild>
|
|
269
|
-
<section aria-live="polite">{errorMessage}</section>
|
|
270
|
-
</VisuallyHidden>
|
|
271
|
-
</>
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function getMutationErrorMessage(error: unknown) {
|
|
276
|
-
if (error instanceof Error) {
|
|
277
|
-
return error.message;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
typeof error === "object" &&
|
|
282
|
-
error !== null &&
|
|
283
|
-
"message" in error &&
|
|
284
|
-
typeof error.message === "string"
|
|
285
|
-
) {
|
|
286
|
-
return error.message;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return "Something went wrong. Please try again.";
|
|
290
|
-
}
|
package/src/lib/constants.ts
DELETED
package/src/lib/copy-button.tsx
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { CheckIcon, CopyIcon } from "@radix-ui/react-icons";
|
|
3
|
-
import { Slot, Slottable } from "@radix-ui/themes";
|
|
4
|
-
|
|
5
|
-
interface CopyButtonProps
|
|
6
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
7
|
-
value: string;
|
|
8
|
-
asChild?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const CopyButton = React.forwardRef<HTMLButtonElement, CopyButtonProps>(
|
|
12
|
-
function CopyButton({ value, asChild, children, style, ...props }, ref) {
|
|
13
|
-
const [hasCopied, setHasCopied] = React.useState(false);
|
|
14
|
-
const Comp = asChild ? Slot : "button";
|
|
15
|
-
const timeoutRef = React.useRef<NodeJS.Timeout>();
|
|
16
|
-
|
|
17
|
-
const handleClick = async () => {
|
|
18
|
-
try {
|
|
19
|
-
console.log("Copying text:", value);
|
|
20
|
-
await navigator.clipboard.writeText(value);
|
|
21
|
-
setHasCopied(true);
|
|
22
|
-
|
|
23
|
-
// Clear any existing timeout
|
|
24
|
-
if (timeoutRef.current) {
|
|
25
|
-
clearTimeout(timeoutRef.current);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Set new timeout
|
|
29
|
-
timeoutRef.current = setTimeout(() => {
|
|
30
|
-
setHasCopied(false);
|
|
31
|
-
}, 3000);
|
|
32
|
-
} catch (err) {
|
|
33
|
-
console.error("Failed to copy text:", err);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Cleanup timeout on unmount
|
|
38
|
-
React.useEffect(() => {
|
|
39
|
-
return () => {
|
|
40
|
-
if (timeoutRef.current) {
|
|
41
|
-
clearTimeout(timeoutRef.current);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
}, []);
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<Comp onPointerUp={handleClick} ref={ref} {...props}>
|
|
48
|
-
{hasCopied ? <CheckIcon /> : <CopyIcon />}
|
|
49
|
-
<Slottable>{children}</Slottable>
|
|
50
|
-
</Comp>
|
|
51
|
-
);
|
|
52
|
-
},
|
|
53
|
-
);
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import {
|
|
5
|
-
AlertDialog,
|
|
6
|
-
Callout,
|
|
7
|
-
Flex,
|
|
8
|
-
Text,
|
|
9
|
-
VisuallyHidden,
|
|
10
|
-
} from "@radix-ui/themes";
|
|
11
|
-
import { type ReactNode, useRef } from "react";
|
|
12
|
-
import { useDeleteUser } from "./api/user";
|
|
13
|
-
import {
|
|
14
|
-
AlertDialogContent,
|
|
15
|
-
DestructiveButton,
|
|
16
|
-
SecondaryButton,
|
|
17
|
-
} from "./elements";
|
|
18
|
-
import { Member } from "../api";
|
|
19
|
-
|
|
20
|
-
interface DeleteUserDialogProps extends AlertDialog.RootProps {
|
|
21
|
-
open: boolean;
|
|
22
|
-
onOpenChange: (open: boolean) => void;
|
|
23
|
-
user: Member;
|
|
24
|
-
children?: ReactNode;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function DeleteUserDialog({
|
|
28
|
-
children,
|
|
29
|
-
user,
|
|
30
|
-
...props
|
|
31
|
-
}: DeleteUserDialogProps) {
|
|
32
|
-
const deleteUser = useDeleteUser();
|
|
33
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
34
|
-
|
|
35
|
-
const onSubmitForm = () => {
|
|
36
|
-
deleteUser.mutate(
|
|
37
|
-
{ userId: user.id },
|
|
38
|
-
{
|
|
39
|
-
onSuccess: () => {
|
|
40
|
-
props.onOpenChange(false);
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
return (
|
|
47
|
-
<AlertDialog.Root {...props}>
|
|
48
|
-
{children && <AlertDialog.Trigger>{children}</AlertDialog.Trigger>}
|
|
49
|
-
|
|
50
|
-
<AlertDialogContent
|
|
51
|
-
maxWidth="480px"
|
|
52
|
-
onOpenAutoFocus={() => {
|
|
53
|
-
requestAnimationFrame(() => {
|
|
54
|
-
inputRef.current?.focus();
|
|
55
|
-
});
|
|
56
|
-
}}
|
|
57
|
-
>
|
|
58
|
-
<AlertDialog.Title>Remove user</AlertDialog.Title>
|
|
59
|
-
<Flex direction="column" gap="3">
|
|
60
|
-
<AlertDialog.Description>
|
|
61
|
-
Are you sure you want to remove{" "}
|
|
62
|
-
<Text weight="bold">{user.email}</Text>? This action is immediate
|
|
63
|
-
and cannot be undone.
|
|
64
|
-
</AlertDialog.Description>
|
|
65
|
-
</Flex>
|
|
66
|
-
|
|
67
|
-
{deleteUser.error ? (
|
|
68
|
-
<Callout.Root color="red" mt="4" mb="-2">
|
|
69
|
-
<Callout.Text>
|
|
70
|
-
{getMutationErrorMessage(deleteUser.error)}
|
|
71
|
-
</Callout.Text>
|
|
72
|
-
</Callout.Root>
|
|
73
|
-
) : null}
|
|
74
|
-
|
|
75
|
-
<Flex gap="3" justify="end" mt="5" asChild>
|
|
76
|
-
<form
|
|
77
|
-
onSubmit={(event) => {
|
|
78
|
-
event.preventDefault();
|
|
79
|
-
onSubmitForm();
|
|
80
|
-
}}
|
|
81
|
-
>
|
|
82
|
-
<AlertDialog.Cancel>
|
|
83
|
-
<SecondaryButton disabled={deleteUser.isPending}>
|
|
84
|
-
Cancel
|
|
85
|
-
</SecondaryButton>
|
|
86
|
-
</AlertDialog.Cancel>
|
|
87
|
-
|
|
88
|
-
<DestructiveButton type="submit" loading={deleteUser.isPending}>
|
|
89
|
-
Remove
|
|
90
|
-
</DestructiveButton>
|
|
91
|
-
</form>
|
|
92
|
-
</Flex>
|
|
93
|
-
{/* mirror errors in a live region */}
|
|
94
|
-
<VisuallyHidden asChild>
|
|
95
|
-
<section aria-live="polite">
|
|
96
|
-
{getMutationErrorMessage(deleteUser.error)}
|
|
97
|
-
</section>
|
|
98
|
-
</VisuallyHidden>
|
|
99
|
-
</AlertDialogContent>
|
|
100
|
-
</AlertDialog.Root>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function getMutationErrorMessage(error: unknown) {
|
|
105
|
-
if (!error) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
// TODO Handle server errors
|
|
109
|
-
return "There was an error removing the user. Please try again.";
|
|
110
|
-
}
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Callout, Dialog, Flex, Text, VisuallyHidden } from "@radix-ui/themes";
|
|
4
|
-
import { getMeQueryKey, Me, useUpdateMe } from "../api";
|
|
5
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
6
|
-
import * as React from "react";
|
|
7
|
-
import { type ReactNode } from "react";
|
|
8
|
-
import * as Form from "@radix-ui/react-form";
|
|
9
|
-
import {
|
|
10
|
-
DialogContent,
|
|
11
|
-
Label,
|
|
12
|
-
PrimaryButton,
|
|
13
|
-
SecondaryButton,
|
|
14
|
-
TextField,
|
|
15
|
-
} from "./elements";
|
|
16
|
-
import { SaveButton } from "./save-button";
|
|
17
|
-
|
|
18
|
-
interface EditUserProfileDialogProps extends Dialog.RootProps {
|
|
19
|
-
user: Me;
|
|
20
|
-
children?: ReactNode;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function EditUserProfileDialog({
|
|
24
|
-
children,
|
|
25
|
-
user,
|
|
26
|
-
...props
|
|
27
|
-
}: EditUserProfileDialogProps) {
|
|
28
|
-
const [open, setOpen] = React.useState(false);
|
|
29
|
-
|
|
30
|
-
const handleClose = React.useCallback(() => {
|
|
31
|
-
setOpen(false);
|
|
32
|
-
}, []);
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<Dialog.Root
|
|
36
|
-
{...props}
|
|
37
|
-
open={props.open ?? open}
|
|
38
|
-
onOpenChange={props.onOpenChange ?? setOpen}
|
|
39
|
-
>
|
|
40
|
-
<Dialog.Trigger>{children}</Dialog.Trigger>
|
|
41
|
-
|
|
42
|
-
<DialogContent maxWidth="480px">
|
|
43
|
-
<Content user={user} onClose={handleClose} />
|
|
44
|
-
</DialogContent>
|
|
45
|
-
</Dialog.Root>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface ContentProps extends Pick<EditUserProfileDialogProps, "user"> {
|
|
50
|
-
onClose: () => void;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function Content({ user, onClose }: ContentProps) {
|
|
54
|
-
const client = useQueryClient();
|
|
55
|
-
const updateMe = useUpdateMe({
|
|
56
|
-
mutation: {
|
|
57
|
-
onSettled: () => {
|
|
58
|
-
client.invalidateQueries({ queryKey: getMeQueryKey() });
|
|
59
|
-
},
|
|
60
|
-
onSuccess: (newProfile) => {
|
|
61
|
-
client.setQueryData(getMeQueryKey(), {
|
|
62
|
-
...user,
|
|
63
|
-
firstName: newProfile.firstName || user.firstName,
|
|
64
|
-
lastName: newProfile.lastName || user.lastName,
|
|
65
|
-
});
|
|
66
|
-
},
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<>
|
|
72
|
-
<Dialog.Title mb="5">Edit name</Dialog.Title>
|
|
73
|
-
<VisuallyHidden>
|
|
74
|
-
<Dialog.Description>
|
|
75
|
-
Edit the details of <Text weight="bold">{user.email}</Text>
|
|
76
|
-
</Dialog.Description>
|
|
77
|
-
</VisuallyHidden>
|
|
78
|
-
|
|
79
|
-
{updateMe.error ? (
|
|
80
|
-
<Callout.Root color="red" my="-2">
|
|
81
|
-
<Callout.Text>{getMutationErrorMessage(updateMe.error)}</Callout.Text>
|
|
82
|
-
</Callout.Root>
|
|
83
|
-
) : null}
|
|
84
|
-
|
|
85
|
-
<Form.Root
|
|
86
|
-
onSubmit={async (event) => {
|
|
87
|
-
event.preventDefault();
|
|
88
|
-
const formData = new FormData(event.currentTarget);
|
|
89
|
-
const firstName = formData.get("firstName")?.toString();
|
|
90
|
-
const lastName = formData.get("lastName")?.toString();
|
|
91
|
-
updateMe.mutate({
|
|
92
|
-
data: {
|
|
93
|
-
firstName: firstName ?? undefined,
|
|
94
|
-
lastName: lastName ?? undefined,
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
}}
|
|
98
|
-
>
|
|
99
|
-
<Flex my="5" direction="column" gap="4">
|
|
100
|
-
<Form.Field name="firstName" asChild>
|
|
101
|
-
<Flex direction="column" gap="1">
|
|
102
|
-
<Form.Label asChild>
|
|
103
|
-
<Label>First name</Label>
|
|
104
|
-
</Form.Label>
|
|
105
|
-
<Form.Control asChild>
|
|
106
|
-
<TextField
|
|
107
|
-
data-1p-ignore
|
|
108
|
-
autoComplete="given-name"
|
|
109
|
-
defaultValue={user.firstName ?? ""}
|
|
110
|
-
placeholder="Your first name"
|
|
111
|
-
disabled={updateMe.isPending || updateMe.isSuccess}
|
|
112
|
-
/>
|
|
113
|
-
</Form.Control>
|
|
114
|
-
</Flex>
|
|
115
|
-
</Form.Field>
|
|
116
|
-
|
|
117
|
-
<Form.Field name="lastName" asChild>
|
|
118
|
-
<Flex direction="column" gap="1">
|
|
119
|
-
<Form.Label asChild>
|
|
120
|
-
<Label>Last name</Label>
|
|
121
|
-
</Form.Label>
|
|
122
|
-
<Form.Control asChild>
|
|
123
|
-
<TextField
|
|
124
|
-
data-1p-ignore
|
|
125
|
-
autoComplete="family-name"
|
|
126
|
-
defaultValue={user.lastName ?? ""}
|
|
127
|
-
placeholder="Your last name"
|
|
128
|
-
disabled={updateMe.isPending || updateMe.isSuccess}
|
|
129
|
-
/>
|
|
130
|
-
</Form.Control>
|
|
131
|
-
</Flex>
|
|
132
|
-
</Form.Field>
|
|
133
|
-
</Flex>
|
|
134
|
-
|
|
135
|
-
<Flex mt="5" gap="3" justify="end">
|
|
136
|
-
<Dialog.Close>
|
|
137
|
-
<SecondaryButton
|
|
138
|
-
disabled={updateMe.isPending || updateMe.isSuccess}
|
|
139
|
-
>
|
|
140
|
-
Cancel
|
|
141
|
-
</SecondaryButton>
|
|
142
|
-
</Dialog.Close>
|
|
143
|
-
|
|
144
|
-
<SaveButton
|
|
145
|
-
asChild
|
|
146
|
-
loading={updateMe.isPending}
|
|
147
|
-
done={updateMe.isSuccess}
|
|
148
|
-
onDone={onClose}
|
|
149
|
-
>
|
|
150
|
-
<PrimaryButton>Save</PrimaryButton>
|
|
151
|
-
</SaveButton>
|
|
152
|
-
</Flex>
|
|
153
|
-
|
|
154
|
-
{/* mirror errors in a live region */}
|
|
155
|
-
<VisuallyHidden asChild>
|
|
156
|
-
<section aria-live="polite">
|
|
157
|
-
{getMutationErrorMessage(updateMe.error)}
|
|
158
|
-
</section>
|
|
159
|
-
</VisuallyHidden>
|
|
160
|
-
</Form.Root>
|
|
161
|
-
</>
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function getMutationErrorMessage(error: unknown) {
|
|
166
|
-
if (error instanceof Error) {
|
|
167
|
-
return error.message;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
typeof error === "object" &&
|
|
172
|
-
error !== null &&
|
|
173
|
-
"message" in error &&
|
|
174
|
-
typeof error.message === "string"
|
|
175
|
-
) {
|
|
176
|
-
return error.message;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// TODO Handle server errors
|
|
180
|
-
return "Something went wrong. Please try again.";
|
|
181
|
-
}
|