@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,327 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Callout,
|
|
5
|
-
Dialog,
|
|
6
|
-
Flex,
|
|
7
|
-
Select,
|
|
8
|
-
Text,
|
|
9
|
-
VisuallyHidden,
|
|
10
|
-
} from "@radix-ui/themes";
|
|
11
|
-
import * as React from "react";
|
|
12
|
-
import {
|
|
13
|
-
DialogContent,
|
|
14
|
-
PrimaryButton,
|
|
15
|
-
SecondaryButton,
|
|
16
|
-
SelectContent,
|
|
17
|
-
SelectItem,
|
|
18
|
-
SelectTrigger,
|
|
19
|
-
TextField,
|
|
20
|
-
} from "./elements";
|
|
21
|
-
import { Label } from "./elements";
|
|
22
|
-
import { isErrorLike } from "./utils";
|
|
23
|
-
import { useInviteUser } from "./api/user";
|
|
24
|
-
import { InviteMemberInput, MemberRole, useRoles } from "../api";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Used to stub a fake value for the role select. It will be selected by default
|
|
28
|
-
* before the role query resolves, or if the query results in an error. We do
|
|
29
|
-
* this because we need to provide _any_ value to the select to avoid
|
|
30
|
-
* controlled/uncontrolled bugs.
|
|
31
|
-
*/
|
|
32
|
-
const PLACEHOLDER_ROLE = "_rolePlaceholder";
|
|
33
|
-
|
|
34
|
-
interface InviteUserDialogProps {
|
|
35
|
-
children?: React.ReactNode;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function InviteUserDialog({ children }: InviteUserDialogProps) {
|
|
39
|
-
const [open, setOpen] = React.useState(false);
|
|
40
|
-
const dialogId = toId("invite-user", React.useId());
|
|
41
|
-
const formId = toId(dialogId, "form");
|
|
42
|
-
|
|
43
|
-
const inviteUser = useInviteUser();
|
|
44
|
-
const rolesQuery = useRoles({
|
|
45
|
-
query: { initialData: [] },
|
|
46
|
-
});
|
|
47
|
-
const roles = rolesQuery.data;
|
|
48
|
-
const [selectedRole, setSelectedRole] = React.useState(
|
|
49
|
-
() => getDefaultRole(roles)?.slug || PLACEHOLDER_ROLE,
|
|
50
|
-
);
|
|
51
|
-
React.useEffect(() => {
|
|
52
|
-
// Update the selected role if it's not in the list (eg if the list was
|
|
53
|
-
// previously empty and the query resolved)
|
|
54
|
-
setSelectedRole((selectedRole) => {
|
|
55
|
-
if (roles.find((role) => role.slug === selectedRole)) {
|
|
56
|
-
// if current selected role is in the new list, don't change it
|
|
57
|
-
return selectedRole;
|
|
58
|
-
}
|
|
59
|
-
return getDefaultRole(roles)?.slug || PLACEHOLDER_ROLE;
|
|
60
|
-
});
|
|
61
|
-
}, [roles]);
|
|
62
|
-
|
|
63
|
-
const onSubmitForm = (data: InviteMemberInput) => {
|
|
64
|
-
if (inviteUser.isPending || rolesQuery.status !== "success") {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
inviteUser.mutate(
|
|
68
|
-
{ data },
|
|
69
|
-
{
|
|
70
|
-
onSuccess: () => {
|
|
71
|
-
setOpen(false);
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
);
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const formErrors = getFormErrors(inviteUser.error);
|
|
78
|
-
useFormFieldFocusOnError(dialogId, inviteUser.error);
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<Dialog.Root open={open} onOpenChange={setOpen}>
|
|
82
|
-
{children && <Dialog.Trigger>{children}</Dialog.Trigger>}
|
|
83
|
-
<DialogContent maxWidth="480px" key={String(open)}>
|
|
84
|
-
<Dialog.Title>Invite user</Dialog.Title>
|
|
85
|
-
<Dialog.Description>
|
|
86
|
-
An invitation will be sent to this email address with a link to
|
|
87
|
-
complete their account.
|
|
88
|
-
</Dialog.Description>
|
|
89
|
-
<Flex direction="column" gap="4" mt="5" asChild>
|
|
90
|
-
<form
|
|
91
|
-
id={formId}
|
|
92
|
-
onSubmit={async (event) => {
|
|
93
|
-
event.preventDefault();
|
|
94
|
-
onSubmitForm({
|
|
95
|
-
email: event.currentTarget.email.value,
|
|
96
|
-
roles: [selectedRole],
|
|
97
|
-
});
|
|
98
|
-
}}
|
|
99
|
-
>
|
|
100
|
-
<FormField
|
|
101
|
-
rootId={dialogId}
|
|
102
|
-
name="email"
|
|
103
|
-
label="Email address"
|
|
104
|
-
error={formErrors.fields.email}
|
|
105
|
-
required
|
|
106
|
-
control={(props) => (
|
|
107
|
-
<TextField
|
|
108
|
-
{...props}
|
|
109
|
-
data-1p-ignore="true"
|
|
110
|
-
data-lpignore="true"
|
|
111
|
-
type="email"
|
|
112
|
-
autoComplete="off"
|
|
113
|
-
placeholder="Enter an email address"
|
|
114
|
-
/>
|
|
115
|
-
)}
|
|
116
|
-
/>
|
|
117
|
-
|
|
118
|
-
<FormField
|
|
119
|
-
rootId={dialogId}
|
|
120
|
-
name="role"
|
|
121
|
-
label="Role"
|
|
122
|
-
error={formErrors.fields.role}
|
|
123
|
-
disabled={rolesQuery.isPending || roles.length <= 1}
|
|
124
|
-
info={
|
|
125
|
-
roles.length === 1 ? (
|
|
126
|
-
<>
|
|
127
|
-
New users will be invited with the{" "}
|
|
128
|
-
<Text weight="bold">{roles[0].name}</Text> role, as it is
|
|
129
|
-
the only one available.
|
|
130
|
-
</>
|
|
131
|
-
) : undefined
|
|
132
|
-
}
|
|
133
|
-
control={({
|
|
134
|
-
id,
|
|
135
|
-
"aria-invalid": ariaInvalid,
|
|
136
|
-
"aria-describedby": ariaDescribedBy,
|
|
137
|
-
...props
|
|
138
|
-
}) => (
|
|
139
|
-
<Select.Root
|
|
140
|
-
{...props}
|
|
141
|
-
value={selectedRole}
|
|
142
|
-
onValueChange={setSelectedRole}
|
|
143
|
-
>
|
|
144
|
-
<SelectTrigger
|
|
145
|
-
id={id}
|
|
146
|
-
aria-invalid={ariaInvalid}
|
|
147
|
-
aria-describedby={ariaDescribedBy}
|
|
148
|
-
/>
|
|
149
|
-
<SelectContent>
|
|
150
|
-
<SelectItem value={PLACEHOLDER_ROLE} disabled>
|
|
151
|
-
Select a role
|
|
152
|
-
</SelectItem>
|
|
153
|
-
{roles.map((role) => (
|
|
154
|
-
<SelectItem key={role.slug} value={role.slug}>
|
|
155
|
-
{role.name}
|
|
156
|
-
</SelectItem>
|
|
157
|
-
))}
|
|
158
|
-
</SelectContent>
|
|
159
|
-
</Select.Root>
|
|
160
|
-
)}
|
|
161
|
-
/>
|
|
162
|
-
</form>
|
|
163
|
-
</Flex>
|
|
164
|
-
|
|
165
|
-
{formErrors.form ? (
|
|
166
|
-
<Callout.Root color="red" mt="4" mb="-2">
|
|
167
|
-
<Callout.Text>{formErrors.form}</Callout.Text>
|
|
168
|
-
</Callout.Root>
|
|
169
|
-
) : null}
|
|
170
|
-
|
|
171
|
-
<Flex mt="5" gap="3" justify="end">
|
|
172
|
-
<Dialog.Close>
|
|
173
|
-
<SecondaryButton disabled={inviteUser.isPending}>
|
|
174
|
-
Cancel
|
|
175
|
-
</SecondaryButton>
|
|
176
|
-
</Dialog.Close>
|
|
177
|
-
<PrimaryButton
|
|
178
|
-
form={formId}
|
|
179
|
-
loading={inviteUser.isPending}
|
|
180
|
-
disabled={rolesQuery.isPending || undefined}
|
|
181
|
-
>
|
|
182
|
-
Invite
|
|
183
|
-
</PrimaryButton>
|
|
184
|
-
</Flex>
|
|
185
|
-
{/* mirror errors in a live region */}
|
|
186
|
-
<VisuallyHidden asChild>
|
|
187
|
-
<section aria-live="polite">{formErrors.form}</section>
|
|
188
|
-
</VisuallyHidden>
|
|
189
|
-
</DialogContent>
|
|
190
|
-
</Dialog.Root>
|
|
191
|
-
);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
interface FormControlRenderProps {
|
|
195
|
-
id: string;
|
|
196
|
-
name: string;
|
|
197
|
-
"aria-describedby": string | undefined;
|
|
198
|
-
"aria-invalid"?: boolean;
|
|
199
|
-
required: boolean | undefined;
|
|
200
|
-
disabled: boolean | undefined;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function FormField({
|
|
204
|
-
rootId,
|
|
205
|
-
name,
|
|
206
|
-
label,
|
|
207
|
-
error,
|
|
208
|
-
info,
|
|
209
|
-
control,
|
|
210
|
-
required,
|
|
211
|
-
disabled,
|
|
212
|
-
}: {
|
|
213
|
-
rootId: string;
|
|
214
|
-
name: string;
|
|
215
|
-
label: string;
|
|
216
|
-
error?: React.ReactNode;
|
|
217
|
-
info?: React.ReactNode;
|
|
218
|
-
control: (props: FormControlRenderProps) => React.ReactNode;
|
|
219
|
-
required?: boolean;
|
|
220
|
-
disabled?: boolean;
|
|
221
|
-
}) {
|
|
222
|
-
const fieldId = toId(rootId, name);
|
|
223
|
-
const errorId = toId(rootId, name, "error");
|
|
224
|
-
const infoId = toId(rootId, name, "info");
|
|
225
|
-
return (
|
|
226
|
-
<Flex direction="column" gap="1">
|
|
227
|
-
<Label htmlFor={fieldId}>{label}</Label>
|
|
228
|
-
{control({
|
|
229
|
-
id: fieldId,
|
|
230
|
-
name,
|
|
231
|
-
"aria-describedby": (() => {
|
|
232
|
-
const tags: string[] = [];
|
|
233
|
-
if (error) {
|
|
234
|
-
tags.push(errorId);
|
|
235
|
-
}
|
|
236
|
-
if (info) {
|
|
237
|
-
tags.push(infoId);
|
|
238
|
-
}
|
|
239
|
-
if (tags.length === 0) {
|
|
240
|
-
return undefined;
|
|
241
|
-
}
|
|
242
|
-
return tags.join(" ");
|
|
243
|
-
})(),
|
|
244
|
-
"aria-invalid": !!error || undefined,
|
|
245
|
-
required: required || undefined,
|
|
246
|
-
disabled: disabled || undefined,
|
|
247
|
-
})}
|
|
248
|
-
|
|
249
|
-
{error ? (
|
|
250
|
-
<Text color="red" size="2" id={errorId}>
|
|
251
|
-
{error}
|
|
252
|
-
</Text>
|
|
253
|
-
) : null}
|
|
254
|
-
{info ? (
|
|
255
|
-
<Text color="gray" size="2" id={infoId} mt="1">
|
|
256
|
-
{info}
|
|
257
|
-
</Text>
|
|
258
|
-
) : null}
|
|
259
|
-
</Flex>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function toId(...parts: string[]) {
|
|
264
|
-
return parts.join("-");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function getFormErrors(queryError: unknown) {
|
|
268
|
-
const formErrors = {
|
|
269
|
-
form: null as string | null,
|
|
270
|
-
fields: {
|
|
271
|
-
email: null as string | null,
|
|
272
|
-
role: null as string | null,
|
|
273
|
-
},
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
if (queryError) {
|
|
277
|
-
if (!isErrorLike(queryError)) {
|
|
278
|
-
return {
|
|
279
|
-
...formErrors,
|
|
280
|
-
form: "An unexpected error occurred. Please try again.",
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
switch (queryError.message.toLowerCase()) {
|
|
285
|
-
case "user already exists":
|
|
286
|
-
case "user already invited":
|
|
287
|
-
case "invalid email":
|
|
288
|
-
formErrors.fields.email = queryError.message;
|
|
289
|
-
break;
|
|
290
|
-
case "invalid role":
|
|
291
|
-
formErrors.fields.role = queryError.message;
|
|
292
|
-
break;
|
|
293
|
-
default:
|
|
294
|
-
// TODO handle more cases for various server errors
|
|
295
|
-
formErrors.form =
|
|
296
|
-
"There was an error inviting this user. Please refresh the page and try again.";
|
|
297
|
-
break;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return formErrors;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function useFormFieldFocusOnError(dialogId: string, queryError: unknown) {
|
|
305
|
-
React.useEffect(() => {
|
|
306
|
-
const fieldErrors = getFormErrors(queryError).fields;
|
|
307
|
-
for (const [name, error] of Object.entries(fieldErrors)) {
|
|
308
|
-
if (error) {
|
|
309
|
-
const fieldElement = document.getElementById(toId(dialogId, name)) as
|
|
310
|
-
| HTMLInputElement
|
|
311
|
-
| HTMLButtonElement
|
|
312
|
-
| null;
|
|
313
|
-
if (fieldElement) {
|
|
314
|
-
fieldElement?.focus();
|
|
315
|
-
if ("select" in fieldElement) {
|
|
316
|
-
fieldElement.select();
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}, [dialogId, queryError]);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function getDefaultRole(roles: MemberRole[]) {
|
|
326
|
-
return roles.find((role) => role.default) || roles[0];
|
|
327
|
-
}
|
|
@@ -1,82 +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 { getSessionsQueryKey, useRevokeAllSessions } from "../api";
|
|
12
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
13
|
-
import { SaveButton } from "./save-button";
|
|
14
|
-
|
|
15
|
-
interface LogoutAllSessionsDialogProps extends AlertDialog.RootProps {
|
|
16
|
-
children?: ReactNode;
|
|
17
|
-
currentSessionId: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function LogoutAllSessionsDialog({
|
|
21
|
-
open,
|
|
22
|
-
onOpenChange,
|
|
23
|
-
children,
|
|
24
|
-
currentSessionId,
|
|
25
|
-
...props
|
|
26
|
-
}: LogoutAllSessionsDialogProps) {
|
|
27
|
-
const client = useQueryClient();
|
|
28
|
-
|
|
29
|
-
const revokeAllSessions = useRevokeAllSessions();
|
|
30
|
-
|
|
31
|
-
const onSubmitForm = () => {
|
|
32
|
-
revokeAllSessions.mutate({ data: { currentSessionId } });
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const handleDone = React.useCallback(() => {
|
|
36
|
-
onOpenChange?.(false);
|
|
37
|
-
|
|
38
|
-
client.invalidateQueries({
|
|
39
|
-
queryKey: getSessionsQueryKey(),
|
|
40
|
-
});
|
|
41
|
-
}, [client, onOpenChange]);
|
|
42
|
-
|
|
43
|
-
return (
|
|
44
|
-
<AlertDialog.Root open={open} onOpenChange={onOpenChange} {...props}>
|
|
45
|
-
<AlertDialogContent maxWidth="480px">
|
|
46
|
-
<AlertDialog.Title>Sign out of all other devices?</AlertDialog.Title>
|
|
47
|
-
<AlertDialog.Description>
|
|
48
|
-
You will be logged out of all other active sessions on other devices,
|
|
49
|
-
except this one.
|
|
50
|
-
</AlertDialog.Description>
|
|
51
|
-
|
|
52
|
-
<Flex gap="3" justify="end" mt="5" asChild>
|
|
53
|
-
<form
|
|
54
|
-
onSubmit={(event) => {
|
|
55
|
-
event.preventDefault();
|
|
56
|
-
onSubmitForm();
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
<AlertDialog.Cancel>
|
|
60
|
-
<SecondaryButton
|
|
61
|
-
disabled={
|
|
62
|
-
revokeAllSessions.isPending || revokeAllSessions.isSuccess
|
|
63
|
-
}
|
|
64
|
-
>
|
|
65
|
-
Cancel
|
|
66
|
-
</SecondaryButton>
|
|
67
|
-
</AlertDialog.Cancel>
|
|
68
|
-
|
|
69
|
-
<SaveButton
|
|
70
|
-
asChild
|
|
71
|
-
loading={revokeAllSessions.isPending}
|
|
72
|
-
done={revokeAllSessions.isSuccess}
|
|
73
|
-
onDone={handleDone}
|
|
74
|
-
>
|
|
75
|
-
<DestructiveButton type="submit">Sign out</DestructiveButton>
|
|
76
|
-
</SaveButton>
|
|
77
|
-
</form>
|
|
78
|
-
</Flex>
|
|
79
|
-
</AlertDialogContent>
|
|
80
|
-
</AlertDialog.Root>
|
|
81
|
-
);
|
|
82
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { AlertDialog, Flex, Strong } from "@radix-ui/themes";
|
|
5
|
-
import { type ReactNode } from "react";
|
|
6
|
-
import {
|
|
7
|
-
AlertDialogContent,
|
|
8
|
-
DestructiveButton,
|
|
9
|
-
SecondaryButton,
|
|
10
|
-
} from "./elements";
|
|
11
|
-
import { ActiveSession, getSessionsQueryKey, useRevokeSession } from "../api";
|
|
12
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
13
|
-
import { SaveButton } from "./save-button";
|
|
14
|
-
import { parseUserAgent } from "./utils";
|
|
15
|
-
|
|
16
|
-
interface LogoutDialogProps extends AlertDialog.RootProps {
|
|
17
|
-
children?: ReactNode;
|
|
18
|
-
session: ActiveSession;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function LogoutDialog({
|
|
22
|
-
children,
|
|
23
|
-
session,
|
|
24
|
-
open,
|
|
25
|
-
onOpenChange,
|
|
26
|
-
...props
|
|
27
|
-
}: LogoutDialogProps) {
|
|
28
|
-
const client = useQueryClient();
|
|
29
|
-
const userAgent = parseUserAgent(session.userAgent);
|
|
30
|
-
const device = userAgent.pretty;
|
|
31
|
-
|
|
32
|
-
const revokeSession = useRevokeSession();
|
|
33
|
-
|
|
34
|
-
const handleDone = React.useCallback(() => {
|
|
35
|
-
onOpenChange?.(false);
|
|
36
|
-
|
|
37
|
-
client.invalidateQueries({
|
|
38
|
-
queryKey: getSessionsQueryKey(),
|
|
39
|
-
exact: false,
|
|
40
|
-
});
|
|
41
|
-
}, [onOpenChange, client]);
|
|
42
|
-
|
|
43
|
-
const onSubmitForm = () => {
|
|
44
|
-
revokeSession.mutate({
|
|
45
|
-
sessionId: session.id,
|
|
46
|
-
});
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<AlertDialog.Root open={open} onOpenChange={onOpenChange} {...props}>
|
|
51
|
-
<AlertDialogContent maxWidth="480px">
|
|
52
|
-
<AlertDialog.Title>Sign out of device?</AlertDialog.Title>
|
|
53
|
-
<AlertDialog.Description>
|
|
54
|
-
You will be signed out of <Strong>{device}.</Strong>
|
|
55
|
-
</AlertDialog.Description>
|
|
56
|
-
|
|
57
|
-
<Flex gap="3" justify="end" mt="5" asChild>
|
|
58
|
-
<form
|
|
59
|
-
onSubmit={(event) => {
|
|
60
|
-
event.preventDefault();
|
|
61
|
-
onSubmitForm();
|
|
62
|
-
}}
|
|
63
|
-
>
|
|
64
|
-
<AlertDialog.Cancel>
|
|
65
|
-
<SecondaryButton
|
|
66
|
-
disabled={revokeSession.isPending || revokeSession.isSuccess}
|
|
67
|
-
>
|
|
68
|
-
Cancel
|
|
69
|
-
</SecondaryButton>
|
|
70
|
-
</AlertDialog.Cancel>
|
|
71
|
-
|
|
72
|
-
<SaveButton
|
|
73
|
-
asChild
|
|
74
|
-
loading={revokeSession.isPending}
|
|
75
|
-
done={revokeSession.isSuccess}
|
|
76
|
-
onDone={handleDone}
|
|
77
|
-
>
|
|
78
|
-
<DestructiveButton type="submit">Sign out</DestructiveButton>
|
|
79
|
-
</SaveButton>
|
|
80
|
-
</form>
|
|
81
|
-
</Flex>
|
|
82
|
-
</AlertDialogContent>
|
|
83
|
-
</AlertDialog.Root>
|
|
84
|
-
);
|
|
85
|
-
}
|
package/src/lib/marker.tsx
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { Text } from "@radix-ui/themes";
|
|
3
|
-
import { MarginProps } from "@radix-ui/themes/props";
|
|
4
|
-
import clsx from "clsx";
|
|
5
|
-
import { namespaceClassNames } from "./utils";
|
|
6
|
-
|
|
7
|
-
type TextProps = React.ComponentPropsWithoutRef<typeof Text>;
|
|
8
|
-
|
|
9
|
-
type MarkerOwnProps = {
|
|
10
|
-
color?: "gray" | "purple" | "blue" | "green" | "yellow" | "red";
|
|
11
|
-
highContrast?: boolean;
|
|
12
|
-
size?: TextProps["size"];
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
interface MarkerProps
|
|
16
|
-
extends Omit<React.ComponentPropsWithRef<"span">, "color">,
|
|
17
|
-
MarkerOwnProps,
|
|
18
|
-
MarginProps {}
|
|
19
|
-
|
|
20
|
-
export const Marker = React.forwardRef<HTMLSpanElement, MarkerProps>(
|
|
21
|
-
function Marker(
|
|
22
|
-
{ children, className, highContrast, ...props },
|
|
23
|
-
forwardedRef,
|
|
24
|
-
) {
|
|
25
|
-
return (
|
|
26
|
-
<Text
|
|
27
|
-
ref={forwardedRef}
|
|
28
|
-
className={clsx(className, namespaceClassNames("marker"))}
|
|
29
|
-
{...props}
|
|
30
|
-
>
|
|
31
|
-
<span className={namespaceClassNames("marker-circle")}>
|
|
32
|
-
<span className={namespaceClassNames("marker-content")}>
|
|
33
|
-
{children}
|
|
34
|
-
</span>
|
|
35
|
-
</span>
|
|
36
|
-
</Text>
|
|
37
|
-
);
|
|
38
|
-
},
|
|
39
|
-
);
|
package/src/lib/oauth-icons.tsx
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
const GitHub = React.forwardRef<
|
|
4
|
-
SVGSVGElement,
|
|
5
|
-
React.ComponentPropsWithoutRef<"svg">
|
|
6
|
-
>((props, forwardedRef) => (
|
|
7
|
-
<svg
|
|
8
|
-
ref={forwardedRef}
|
|
9
|
-
fill="none"
|
|
10
|
-
height="16"
|
|
11
|
-
viewBox="0 0 15 15"
|
|
12
|
-
width="16"
|
|
13
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
14
|
-
{...props}
|
|
15
|
-
>
|
|
16
|
-
<path
|
|
17
|
-
clipRule="evenodd"
|
|
18
|
-
d="M7.49933 0.25C3.49635 0.25 0.25 3.49593 0.25 7.50024C0.25 10.703 2.32715 13.4206 5.2081 14.3797C5.57084 14.446 5.70302 14.2222 5.70302 14.0299C5.70302 13.8576 5.69679 13.4019 5.69323 12.797C3.67661 13.235 3.25112 11.825 3.25112 11.825C2.92132 10.9874 2.44599 10.7644 2.44599 10.7644C1.78773 10.3149 2.49584 10.3238 2.49584 10.3238C3.22353 10.375 3.60629 11.0711 3.60629 11.0711C4.25298 12.1788 5.30335 11.8588 5.71638 11.6732C5.78225 11.205 5.96962 10.8854 6.17658 10.7043C4.56675 10.5209 2.87415 9.89918 2.87415 7.12104C2.87415 6.32925 3.15677 5.68257 3.62053 5.17563C3.54576 4.99226 3.29697 4.25521 3.69174 3.25691C3.69174 3.25691 4.30015 3.06196 5.68522 3.99973C6.26337 3.83906 6.8838 3.75895 7.50022 3.75583C8.1162 3.75895 8.73619 3.83906 9.31523 3.99973C10.6994 3.06196 11.3069 3.25691 11.3069 3.25691C11.7026 4.25521 11.4538 4.99226 11.3795 5.17563C11.8441 5.68257 12.1245 6.32925 12.1245 7.12104C12.1245 9.9063 10.4292 10.5192 8.81452 10.6985C9.07444 10.9224 9.30633 11.3648 9.30633 12.0413C9.30633 13.0102 9.29742 13.7922 9.29742 14.0299C9.29742 14.2239 9.42828 14.4496 9.79591 14.3788C12.6746 13.4179 14.75 10.7025 14.75 7.50024C14.75 3.49593 11.5036 0.25 7.49933 0.25Z"
|
|
19
|
-
fill="currentColor"
|
|
20
|
-
fillRule="evenodd"
|
|
21
|
-
></path>
|
|
22
|
-
</svg>
|
|
23
|
-
));
|
|
24
|
-
|
|
25
|
-
GitHub.displayName = "GitHub";
|
|
26
|
-
|
|
27
|
-
const Google = React.forwardRef<
|
|
28
|
-
SVGSVGElement,
|
|
29
|
-
React.ComponentPropsWithoutRef<"svg">
|
|
30
|
-
>((props, forwardedRef) => (
|
|
31
|
-
<svg
|
|
32
|
-
ref={forwardedRef}
|
|
33
|
-
fill="none"
|
|
34
|
-
height="15"
|
|
35
|
-
viewBox="0 0 16 16"
|
|
36
|
-
width="15"
|
|
37
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
38
|
-
{...props}
|
|
39
|
-
>
|
|
40
|
-
<g>
|
|
41
|
-
<path
|
|
42
|
-
d="M15.83 8.18C15.83 7.65333 15.7833 7.15333 15.7033 6.66667H8.17V9.67333H12.4833C12.29 10.66 11.7233 11.4933 10.8833 12.06V14.06H13.4567C14.9633 12.6667 15.83 10.6133 15.83 8.18Z"
|
|
43
|
-
fill="#4285F4"
|
|
44
|
-
/>
|
|
45
|
-
<path
|
|
46
|
-
d="M8.17 16C10.33 16 12.1367 15.28 13.4567 14.06L10.8833 12.06C10.1633 12.54 9.25 12.8333 8.17 12.8333C6.08334 12.8333 4.31667 11.4267 3.68334 9.52667H1.03V11.5867C2.34334 14.2 5.04334 16 8.17 16Z"
|
|
47
|
-
fill="#34A853"
|
|
48
|
-
/>
|
|
49
|
-
<path
|
|
50
|
-
d="M3.68334 9.52667C3.51667 9.04667 3.43 8.53333 3.43 8C3.43 7.46667 3.52334 6.95334 3.68334 6.47334V4.41334H1.03C0.483335 5.49334 0.170002 6.70667 0.170002 8C0.170002 9.29333 0.483335 10.5067 1.03 11.5867L3.68334 9.52667Z"
|
|
51
|
-
fill="#FBBC05"
|
|
52
|
-
/>
|
|
53
|
-
<path
|
|
54
|
-
d="M8.17 3.16667C9.35 3.16667 10.4033 3.57334 11.2367 4.36667L13.5167 2.08667C12.1367 0.793334 10.33 0 8.17 0C5.04334 0 2.34334 1.8 1.03 4.41334L3.68334 6.47334C4.31667 4.57334 6.08334 3.16667 8.17 3.16667Z"
|
|
55
|
-
fill="#EA4335"
|
|
56
|
-
/>
|
|
57
|
-
</g>
|
|
58
|
-
</svg>
|
|
59
|
-
));
|
|
60
|
-
|
|
61
|
-
Google.displayName = "Google";
|
|
62
|
-
|
|
63
|
-
const Microsoft = React.forwardRef<
|
|
64
|
-
SVGSVGElement,
|
|
65
|
-
React.ComponentPropsWithoutRef<"svg">
|
|
66
|
-
>((props, forwardedRef) => (
|
|
67
|
-
<svg
|
|
68
|
-
ref={forwardedRef}
|
|
69
|
-
fill="none"
|
|
70
|
-
height="15"
|
|
71
|
-
viewBox="0 0 15 15"
|
|
72
|
-
width="15"
|
|
73
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
74
|
-
{...props}
|
|
75
|
-
>
|
|
76
|
-
<g>
|
|
77
|
-
<path d="M0 0H7L7 7H-4.76837e-07L0 0Z" fill="#F35325" />
|
|
78
|
-
<path d="M8 0H15V7H8L8 0Z" fill="#81BC06" />
|
|
79
|
-
<path d="M0 8H7V15H0V8Z" fill="#05A6F0" />
|
|
80
|
-
<path d="M8 8L15 8V15L8 15V8Z" fill="#FFBA08" />
|
|
81
|
-
</g>
|
|
82
|
-
</svg>
|
|
83
|
-
));
|
|
84
|
-
|
|
85
|
-
Microsoft.displayName = "Microsoft";
|
|
86
|
-
|
|
87
|
-
const Apple = React.forwardRef<
|
|
88
|
-
SVGSVGElement,
|
|
89
|
-
React.ComponentPropsWithoutRef<"svg">
|
|
90
|
-
>(({ style, ...props }, forwardedRef) => (
|
|
91
|
-
<svg
|
|
92
|
-
ref={forwardedRef}
|
|
93
|
-
fill="none"
|
|
94
|
-
height="15"
|
|
95
|
-
style={{ overflow: "visible", ...style }}
|
|
96
|
-
viewBox="0 0 15 15"
|
|
97
|
-
width="15"
|
|
98
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
99
|
-
{...props}
|
|
100
|
-
>
|
|
101
|
-
<path
|
|
102
|
-
d="M14.219 3.33667C14.1169 3.41674 12.3137 4.44312 12.3137 6.72534C12.3137 9.3651 14.6082 10.299 14.6769 10.3221C14.6663 10.379 14.3124 11.601 13.4671 12.8462C12.7134 13.942 11.9263 15.0359 10.7288 15.0359C9.53134 15.0359 9.22317 14.3333 7.84081 14.3333C6.49366 14.3333 6.01469 15.0591 4.91935 15.0591C3.82401 15.0591 3.05978 14.0451 2.18104 12.8C1.1632 11.3378 0.34082 9.06625 0.34082 6.91034C0.34082 3.45232 2.56668 1.61835 4.75732 1.61835C5.92133 1.61835 6.89164 2.39036 7.62246 2.39036C8.31802 2.39036 9.40277 1.5721 10.7271 1.5721C11.2289 1.5721 13.0321 1.61835 14.219 3.33667ZM10.0984 0.108136C10.646 -0.548247 11.0334 -1.459 11.0334 -2.36975C11.0334 -2.49605 11.0229 -2.62412 11 -2.72729C10.1089 -2.6935 9.04883 -2.12783 8.40959 -1.37895C7.90772 -0.802617 7.43931 0.108136 7.43931 1.03134C7.43931 1.1701 7.46218 1.30883 7.47277 1.35331C7.52909 1.36398 7.62067 1.37643 7.71224 1.37643C8.51172 1.37643 9.51724 0.835672 10.0984 0.108136Z"
|
|
103
|
-
fill="currentColor"
|
|
104
|
-
/>
|
|
105
|
-
</svg>
|
|
106
|
-
));
|
|
107
|
-
|
|
108
|
-
Apple.displayName = "Apple";
|
|
109
|
-
|
|
110
|
-
export const getOAuthIcon = (account: string) => {
|
|
111
|
-
switch (account) {
|
|
112
|
-
case "GithubOAuth":
|
|
113
|
-
return GitHub;
|
|
114
|
-
case "GoogleOAuth":
|
|
115
|
-
return Google;
|
|
116
|
-
case "MicrosoftOAuth":
|
|
117
|
-
return Microsoft;
|
|
118
|
-
case "AppleOAuth":
|
|
119
|
-
return Apple;
|
|
120
|
-
default:
|
|
121
|
-
throw new Error(`Unknown OAuth account type: ${account}`);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
export const getOAuthName = (account: string) => {
|
|
126
|
-
switch (account) {
|
|
127
|
-
case "GithubOAuth":
|
|
128
|
-
return "GitHub";
|
|
129
|
-
case "GoogleOAuth":
|
|
130
|
-
return "Google";
|
|
131
|
-
case "MicrosoftOAuth":
|
|
132
|
-
return "Microsoft";
|
|
133
|
-
case "AppleOAuth":
|
|
134
|
-
return "Apple";
|
|
135
|
-
default:
|
|
136
|
-
throw new Error(`Unknown OAuth account type: ${account}`);
|
|
137
|
-
}
|
|
138
|
-
};
|