@workos-inc/widgets 0.0.0-pre.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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +8 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lib/api/config.d.ts +9 -0
- package/dist/cjs/lib/api/config.d.ts.map +1 -0
- package/dist/cjs/lib/api/config.js +12 -0
- package/dist/cjs/lib/api/config.js.map +1 -0
- package/dist/cjs/lib/api/role.d.ts +9 -0
- package/dist/cjs/lib/api/role.d.ts.map +1 -0
- package/dist/cjs/lib/api/role.js +94 -0
- package/dist/cjs/lib/api/role.js.map +1 -0
- package/dist/cjs/lib/api/user.d.ts +61 -0
- package/dist/cjs/lib/api/user.d.ts.map +1 -0
- package/dist/cjs/lib/api/user.js +312 -0
- package/dist/cjs/lib/api/user.js.map +1 -0
- package/dist/cjs/lib/constants.d.ts +3 -0
- package/dist/cjs/lib/constants.d.ts.map +1 -0
- package/dist/cjs/lib/constants.js +6 -0
- package/dist/cjs/lib/constants.js.map +1 -0
- package/dist/cjs/lib/delete-user-dialog.d.ts +12 -0
- package/dist/cjs/lib/delete-user-dialog.d.ts.map +1 -0
- package/dist/cjs/lib/delete-user-dialog.js +37 -0
- package/dist/cjs/lib/delete-user-dialog.js.map +1 -0
- package/dist/cjs/lib/edit-user-details-dialog.d.ts +12 -0
- package/dist/cjs/lib/edit-user-details-dialog.d.ts.map +1 -0
- package/dist/cjs/lib/edit-user-details-dialog.js +81 -0
- package/dist/cjs/lib/edit-user-details-dialog.js.map +1 -0
- package/dist/cjs/lib/elements.d.ts +32 -0
- package/dist/cjs/lib/elements.d.ts.map +1 -0
- package/dist/cjs/lib/elements.js +57 -0
- package/dist/cjs/lib/elements.js.map +1 -0
- package/dist/cjs/lib/invite-user-dialog.d.ts +7 -0
- package/dist/cjs/lib/invite-user-dialog.d.ts.map +1 -0
- package/dist/cjs/lib/invite-user-dialog.js +167 -0
- package/dist/cjs/lib/invite-user-dialog.js.map +1 -0
- package/dist/cjs/lib/label.d.ts +7 -0
- package/dist/cjs/lib/label.d.ts.map +1 -0
- package/dist/cjs/lib/label.js +9 -0
- package/dist/cjs/lib/label.js.map +1 -0
- package/dist/cjs/lib/pagination.d.ts +8 -0
- package/dist/cjs/lib/pagination.d.ts.map +1 -0
- package/dist/cjs/lib/pagination.js +67 -0
- package/dist/cjs/lib/pagination.js.map +1 -0
- package/dist/cjs/lib/resend-invite-dialog.d.ts +10 -0
- package/dist/cjs/lib/resend-invite-dialog.d.ts.map +1 -0
- package/dist/cjs/lib/resend-invite-dialog.js +71 -0
- package/dist/cjs/lib/resend-invite-dialog.js.map +1 -0
- package/dist/cjs/lib/revoke-invite-dialog.d.ts +10 -0
- package/dist/cjs/lib/revoke-invite-dialog.d.ts.map +1 -0
- package/dist/cjs/lib/revoke-invite-dialog.js +37 -0
- package/dist/cjs/lib/revoke-invite-dialog.js.map +1 -0
- package/dist/cjs/lib/search-provider.d.ts +11 -0
- package/dist/cjs/lib/search-provider.d.ts.map +1 -0
- package/dist/cjs/lib/search-provider.js +55 -0
- package/dist/cjs/lib/search-provider.js.map +1 -0
- package/dist/cjs/lib/use-is-hydrated.d.ts +2 -0
- package/dist/cjs/lib/use-is-hydrated.d.ts.map +1 -0
- package/dist/cjs/lib/use-is-hydrated.js +34 -0
- package/dist/cjs/lib/use-is-hydrated.js.map +1 -0
- package/dist/cjs/lib/user-actions-dropdown.d.ts +9 -0
- package/dist/cjs/lib/user-actions-dropdown.d.ts.map +1 -0
- package/dist/cjs/lib/user-actions-dropdown.js +83 -0
- package/dist/cjs/lib/user-actions-dropdown.js.map +1 -0
- package/dist/cjs/lib/users-filter.d.ts +9 -0
- package/dist/cjs/lib/users-filter.d.ts.map +1 -0
- package/dist/cjs/lib/users-filter.js +63 -0
- package/dist/cjs/lib/users-filter.js.map +1 -0
- package/dist/cjs/lib/users-management-context.d.ts +23 -0
- package/dist/cjs/lib/users-management-context.d.ts.map +1 -0
- package/dist/cjs/lib/users-management-context.js +83 -0
- package/dist/cjs/lib/users-management-context.js.map +1 -0
- package/dist/cjs/lib/users-management-state.d.ts +22 -0
- package/dist/cjs/lib/users-management-state.d.ts.map +1 -0
- package/dist/cjs/lib/users-management-state.js +143 -0
- package/dist/cjs/lib/users-management-state.js.map +1 -0
- package/dist/cjs/lib/users-management.d.ts +12 -0
- package/dist/cjs/lib/users-management.d.ts.map +1 -0
- package/dist/cjs/lib/users-management.js +141 -0
- package/dist/cjs/lib/users-management.js.map +1 -0
- package/dist/cjs/lib/users-search.d.ts +3 -0
- package/dist/cjs/lib/users-search.d.ts.map +1 -0
- package/dist/cjs/lib/users-search.js +65 -0
- package/dist/cjs/lib/users-search.js.map +1 -0
- package/dist/cjs/lib/utils.d.ts +15 -0
- package/dist/cjs/lib/utils.d.ts.map +1 -0
- package/dist/cjs/lib/utils.js +78 -0
- package/dist/cjs/lib/utils.js.map +1 -0
- package/dist/cjs/lib/widgets-context.d.ts +11 -0
- package/dist/cjs/lib/widgets-context.d.ts.map +1 -0
- package/dist/cjs/lib/widgets-context.js +45 -0
- package/dist/cjs/lib/widgets-context.js.map +1 -0
- package/dist/cjs/users-management.client.d.ts +6 -0
- package/dist/cjs/users-management.client.d.ts.map +1 -0
- package/dist/cjs/users-management.client.js +57 -0
- package/dist/cjs/users-management.client.js.map +1 -0
- package/dist/cjs/workos-widgets.client.d.ts +17 -0
- package/dist/cjs/workos-widgets.client.d.ts.map +1 -0
- package/dist/cjs/workos-widgets.client.js +55 -0
- package/dist/cjs/workos-widgets.client.js.map +1 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lib/api/config.d.ts +9 -0
- package/dist/esm/lib/api/config.d.ts.map +1 -0
- package/dist/esm/lib/api/config.js +9 -0
- package/dist/esm/lib/api/config.js.map +1 -0
- package/dist/esm/lib/api/role.d.ts +9 -0
- package/dist/esm/lib/api/role.d.ts.map +1 -0
- package/dist/esm/lib/api/role.js +89 -0
- package/dist/esm/lib/api/role.js.map +1 -0
- package/dist/esm/lib/api/user.d.ts +61 -0
- package/dist/esm/lib/api/user.d.ts.map +1 -0
- package/dist/esm/lib/api/user.js +302 -0
- package/dist/esm/lib/api/user.js.map +1 -0
- package/dist/esm/lib/constants.d.ts +3 -0
- package/dist/esm/lib/constants.d.ts.map +1 -0
- package/dist/esm/lib/constants.js +3 -0
- package/dist/esm/lib/constants.js.map +1 -0
- package/dist/esm/lib/delete-user-dialog.d.ts +12 -0
- package/dist/esm/lib/delete-user-dialog.d.ts.map +1 -0
- package/dist/esm/lib/delete-user-dialog.js +33 -0
- package/dist/esm/lib/delete-user-dialog.js.map +1 -0
- package/dist/esm/lib/edit-user-details-dialog.d.ts +12 -0
- package/dist/esm/lib/edit-user-details-dialog.d.ts.map +1 -0
- package/dist/esm/lib/edit-user-details-dialog.js +54 -0
- package/dist/esm/lib/edit-user-details-dialog.js.map +1 -0
- package/dist/esm/lib/elements.d.ts +32 -0
- package/dist/esm/lib/elements.d.ts.map +1 -0
- package/dist/esm/lib/elements.js +54 -0
- package/dist/esm/lib/elements.js.map +1 -0
- package/dist/esm/lib/invite-user-dialog.d.ts +7 -0
- package/dist/esm/lib/invite-user-dialog.d.ts.map +1 -0
- package/dist/esm/lib/invite-user-dialog.js +140 -0
- package/dist/esm/lib/invite-user-dialog.js.map +1 -0
- package/dist/esm/lib/label.d.ts +7 -0
- package/dist/esm/lib/label.d.ts.map +1 -0
- package/dist/esm/lib/label.js +6 -0
- package/dist/esm/lib/label.js.map +1 -0
- package/dist/esm/lib/pagination.d.ts +8 -0
- package/dist/esm/lib/pagination.d.ts.map +1 -0
- package/dist/esm/lib/pagination.js +40 -0
- package/dist/esm/lib/pagination.js.map +1 -0
- package/dist/esm/lib/resend-invite-dialog.d.ts +10 -0
- package/dist/esm/lib/resend-invite-dialog.d.ts.map +1 -0
- package/dist/esm/lib/resend-invite-dialog.js +44 -0
- package/dist/esm/lib/resend-invite-dialog.js.map +1 -0
- package/dist/esm/lib/revoke-invite-dialog.d.ts +10 -0
- package/dist/esm/lib/revoke-invite-dialog.d.ts.map +1 -0
- package/dist/esm/lib/revoke-invite-dialog.js +33 -0
- package/dist/esm/lib/revoke-invite-dialog.js.map +1 -0
- package/dist/esm/lib/search-provider.d.ts +11 -0
- package/dist/esm/lib/search-provider.d.ts.map +1 -0
- package/dist/esm/lib/search-provider.js +27 -0
- package/dist/esm/lib/search-provider.js.map +1 -0
- package/dist/esm/lib/use-is-hydrated.d.ts +2 -0
- package/dist/esm/lib/use-is-hydrated.d.ts.map +1 -0
- package/dist/esm/lib/use-is-hydrated.js +8 -0
- package/dist/esm/lib/use-is-hydrated.js.map +1 -0
- package/dist/esm/lib/user-actions-dropdown.d.ts +9 -0
- package/dist/esm/lib/user-actions-dropdown.d.ts.map +1 -0
- package/dist/esm/lib/user-actions-dropdown.js +56 -0
- package/dist/esm/lib/user-actions-dropdown.js.map +1 -0
- package/dist/esm/lib/users-filter.d.ts +9 -0
- package/dist/esm/lib/users-filter.d.ts.map +1 -0
- package/dist/esm/lib/users-filter.js +36 -0
- package/dist/esm/lib/users-filter.js.map +1 -0
- package/dist/esm/lib/users-management-context.d.ts +23 -0
- package/dist/esm/lib/users-management-context.d.ts.map +1 -0
- package/dist/esm/lib/users-management-context.js +54 -0
- package/dist/esm/lib/users-management-context.js.map +1 -0
- package/dist/esm/lib/users-management-state.d.ts +22 -0
- package/dist/esm/lib/users-management-state.d.ts.map +1 -0
- package/dist/esm/lib/users-management-state.js +117 -0
- package/dist/esm/lib/users-management-state.js.map +1 -0
- package/dist/esm/lib/users-management.d.ts +12 -0
- package/dist/esm/lib/users-management.d.ts.map +1 -0
- package/dist/esm/lib/users-management.js +114 -0
- package/dist/esm/lib/users-management.js.map +1 -0
- package/dist/esm/lib/users-search.d.ts +3 -0
- package/dist/esm/lib/users-search.d.ts.map +1 -0
- package/dist/esm/lib/users-search.js +39 -0
- package/dist/esm/lib/users-search.js.map +1 -0
- package/dist/esm/lib/utils.d.ts +15 -0
- package/dist/esm/lib/utils.d.ts.map +1 -0
- package/dist/esm/lib/utils.js +70 -0
- package/dist/esm/lib/utils.js.map +1 -0
- package/dist/esm/lib/widgets-context.d.ts +11 -0
- package/dist/esm/lib/widgets-context.d.ts.map +1 -0
- package/dist/esm/lib/widgets-context.js +17 -0
- package/dist/esm/lib/widgets-context.js.map +1 -0
- package/dist/esm/users-management.client.d.ts +6 -0
- package/dist/esm/users-management.client.d.ts.map +1 -0
- package/dist/esm/users-management.client.js +30 -0
- package/dist/esm/users-management.client.js.map +1 -0
- package/dist/esm/workos-widgets.client.d.ts +17 -0
- package/dist/esm/workos-widgets.client.d.ts.map +1 -0
- package/dist/esm/workos-widgets.client.js +28 -0
- package/dist/esm/workos-widgets.client.js.map +1 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +69 -0
- package/src/index.ts +5 -0
- package/src/lib/api/config.ts +9 -0
- package/src/lib/api/role.ts +124 -0
- package/src/lib/api/user.ts +458 -0
- package/src/lib/constants.ts +2 -0
- package/src/lib/delete-user-dialog.tsx +103 -0
- package/src/lib/edit-user-details-dialog.tsx +170 -0
- package/src/lib/elements.tsx +175 -0
- package/src/lib/invite-user-dialog.tsx +319 -0
- package/src/lib/label.tsx +14 -0
- package/src/lib/pagination.tsx +69 -0
- package/src/lib/resend-invite-dialog.tsx +136 -0
- package/src/lib/revoke-invite-dialog.tsx +104 -0
- package/src/lib/search-provider.tsx +51 -0
- package/src/lib/use-is-hydrated.ts +13 -0
- package/src/lib/user-actions-dropdown.tsx +161 -0
- package/src/lib/users-filter.tsx +122 -0
- package/src/lib/users-management-context.tsx +89 -0
- package/src/lib/users-management-state.ts +165 -0
- package/src/lib/users-management.tsx +461 -0
- package/src/lib/users-search.tsx +130 -0
- package/src/lib/utils.ts +94 -0
- package/src/lib/widgets-context.ts +29 -0
- package/src/users-management.client.tsx +59 -0
- package/src/workos-widgets.client.tsx +73 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import {
|
|
2
|
+
keepPreviousData,
|
|
3
|
+
useMutation,
|
|
4
|
+
useQuery,
|
|
5
|
+
useQueryClient,
|
|
6
|
+
} from "@tanstack/react-query";
|
|
7
|
+
import {
|
|
8
|
+
useUsersManagementContext,
|
|
9
|
+
type UsersManagementContextType,
|
|
10
|
+
} from "../users-management-context";
|
|
11
|
+
import { parseErrorResponse } from "../utils";
|
|
12
|
+
import { useWorkOsApiUrl } from "../widgets-context";
|
|
13
|
+
import { API_ENDPOINTS } from "./config";
|
|
14
|
+
import { USER_ROW_LIMIT, WIDGETS_API_VERSION } from "../constants";
|
|
15
|
+
|
|
16
|
+
export interface PaginationData {
|
|
17
|
+
before?: string;
|
|
18
|
+
after?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type UserAction =
|
|
22
|
+
| "edit-role"
|
|
23
|
+
| "revoke-membership"
|
|
24
|
+
| "resend-invite"
|
|
25
|
+
| "revoke-invite";
|
|
26
|
+
|
|
27
|
+
export type UserStatus =
|
|
28
|
+
| "Active"
|
|
29
|
+
| "Invited"
|
|
30
|
+
| "InviteExpired"
|
|
31
|
+
| "InviteRevoked";
|
|
32
|
+
|
|
33
|
+
export interface UserRole {
|
|
34
|
+
slug: string;
|
|
35
|
+
name: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface User {
|
|
39
|
+
id: string;
|
|
40
|
+
email: string;
|
|
41
|
+
profilePictureUrl?: string | null;
|
|
42
|
+
firstName?: string | null;
|
|
43
|
+
lastName?: string | null;
|
|
44
|
+
emailVerified: boolean;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
lastActivityAt?: string | null;
|
|
47
|
+
isLoggedInUser?: boolean;
|
|
48
|
+
roles: UserRole[];
|
|
49
|
+
actions: UserAction[];
|
|
50
|
+
status: UserStatus;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export type Paginated<T> = { data: T; pagination: PaginationData };
|
|
54
|
+
|
|
55
|
+
type PaginatedUsers = Paginated<User[]>;
|
|
56
|
+
|
|
57
|
+
class InviteUserError extends Error {
|
|
58
|
+
status: number;
|
|
59
|
+
constructor(args: { message: string; status: number }) {
|
|
60
|
+
super(args.message);
|
|
61
|
+
this.name = "InviteUserError";
|
|
62
|
+
this.status = args.status;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const useUsers = (context?: UsersManagementContextType) => {
|
|
67
|
+
const {
|
|
68
|
+
authToken,
|
|
69
|
+
state: { pagination, role, searchQuery },
|
|
70
|
+
} = useUsersManagementContext(context);
|
|
71
|
+
const baseUrl = useWorkOsApiUrl();
|
|
72
|
+
const limit = USER_ROW_LIMIT;
|
|
73
|
+
const { before, after } = pagination || {};
|
|
74
|
+
|
|
75
|
+
return useQuery<PaginatedUsers>({
|
|
76
|
+
queryKey: [
|
|
77
|
+
"users",
|
|
78
|
+
{ authToken, baseUrl, limit, before, after, role, searchQuery },
|
|
79
|
+
],
|
|
80
|
+
queryFn: () =>
|
|
81
|
+
fetchUsers({
|
|
82
|
+
authToken,
|
|
83
|
+
baseUrl,
|
|
84
|
+
limit,
|
|
85
|
+
before,
|
|
86
|
+
after,
|
|
87
|
+
role,
|
|
88
|
+
searchQuery,
|
|
89
|
+
}),
|
|
90
|
+
enabled: !!authToken,
|
|
91
|
+
placeholderData: keepPreviousData,
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const useUser = (
|
|
96
|
+
userId: string,
|
|
97
|
+
initialContext?: UsersManagementContextType,
|
|
98
|
+
) => {
|
|
99
|
+
const { authToken } = useUsersManagementContext(initialContext);
|
|
100
|
+
const baseUrl = useWorkOsApiUrl();
|
|
101
|
+
return useQuery<User>({
|
|
102
|
+
queryKey: ["user", userId],
|
|
103
|
+
queryFn: () => fetchUser(userId, { baseUrl, authToken }),
|
|
104
|
+
enabled: !!authToken,
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const useUpdateUserRole = (args?: {
|
|
109
|
+
onError?: (error: unknown) => void;
|
|
110
|
+
}) => {
|
|
111
|
+
const { authToken } = useUsersManagementContext();
|
|
112
|
+
const queryClient = useQueryClient();
|
|
113
|
+
const baseUrl = useWorkOsApiUrl();
|
|
114
|
+
const { onError } = args || {};
|
|
115
|
+
|
|
116
|
+
return useMutation({
|
|
117
|
+
mutationFn: ({
|
|
118
|
+
id,
|
|
119
|
+
data: { roles },
|
|
120
|
+
}: {
|
|
121
|
+
id: string;
|
|
122
|
+
data: { roles: string[] };
|
|
123
|
+
}) => updateUserRole(id, { authToken, baseUrl, data: { roles } }),
|
|
124
|
+
onSuccess: (updatedUser) => {
|
|
125
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
126
|
+
queryClient.invalidateQueries({ queryKey: ["user", updatedUser.id] });
|
|
127
|
+
},
|
|
128
|
+
onError,
|
|
129
|
+
});
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export interface InviteUserPayload {
|
|
133
|
+
email: string;
|
|
134
|
+
role: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const useInviteUser = (args?: {
|
|
138
|
+
onError?: (error: unknown) => void;
|
|
139
|
+
}) => {
|
|
140
|
+
const { authToken } = useUsersManagementContext();
|
|
141
|
+
const queryClient = useQueryClient();
|
|
142
|
+
const baseUrl = useWorkOsApiUrl();
|
|
143
|
+
const { onError } = args || {};
|
|
144
|
+
|
|
145
|
+
return useMutation({
|
|
146
|
+
mutationFn: (data: InviteUserPayload) =>
|
|
147
|
+
inviteUser({ authToken, baseUrl, ...data }),
|
|
148
|
+
onSuccess: (_newUser) => {
|
|
149
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
150
|
+
},
|
|
151
|
+
onError,
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const useDeleteUser = (args?: {
|
|
156
|
+
onError?: (error: unknown) => void;
|
|
157
|
+
}) => {
|
|
158
|
+
const { authToken } = useUsersManagementContext();
|
|
159
|
+
const queryClient = useQueryClient();
|
|
160
|
+
const baseUrl = useWorkOsApiUrl();
|
|
161
|
+
const { onError } = args || {};
|
|
162
|
+
|
|
163
|
+
return useMutation({
|
|
164
|
+
mutationFn: (id: string) => deleteUser(id, { authToken, baseUrl }),
|
|
165
|
+
onSuccess: (_, userId) => {
|
|
166
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
167
|
+
queryClient.removeQueries({ queryKey: ["user", userId] });
|
|
168
|
+
},
|
|
169
|
+
onError,
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export const useRevokeUserInvite = (args?: {
|
|
174
|
+
onError?: (error: unknown) => void;
|
|
175
|
+
}) => {
|
|
176
|
+
const { authToken } = useUsersManagementContext();
|
|
177
|
+
const queryClient = useQueryClient();
|
|
178
|
+
const baseUrl = useWorkOsApiUrl();
|
|
179
|
+
const { onError } = args || {};
|
|
180
|
+
|
|
181
|
+
return useMutation({
|
|
182
|
+
mutationFn: (id: string) => revokeUserInvite(id, { authToken, baseUrl }),
|
|
183
|
+
onSuccess: (_, userId) => {
|
|
184
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
185
|
+
queryClient.removeQueries({ queryKey: ["user", userId] });
|
|
186
|
+
},
|
|
187
|
+
onError,
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export const useResendUserInvite = (args?: {
|
|
192
|
+
onError?: (error: unknown) => void;
|
|
193
|
+
}) => {
|
|
194
|
+
const { authToken } = useUsersManagementContext();
|
|
195
|
+
const queryClient = useQueryClient();
|
|
196
|
+
const baseUrl = useWorkOsApiUrl();
|
|
197
|
+
const { onError } = args || {};
|
|
198
|
+
|
|
199
|
+
return useMutation({
|
|
200
|
+
mutationFn: (id: string) => resendUserInvite(id, { authToken, baseUrl }),
|
|
201
|
+
onSuccess: (_, userId) => {
|
|
202
|
+
queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
203
|
+
queryClient.removeQueries({ queryKey: ["user", userId] });
|
|
204
|
+
},
|
|
205
|
+
onError,
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Fetch functions
|
|
211
|
+
*/
|
|
212
|
+
async function fetchUsers({
|
|
213
|
+
baseUrl,
|
|
214
|
+
authToken,
|
|
215
|
+
limit,
|
|
216
|
+
role,
|
|
217
|
+
before,
|
|
218
|
+
after,
|
|
219
|
+
searchQuery,
|
|
220
|
+
}: {
|
|
221
|
+
baseUrl: string;
|
|
222
|
+
authToken: string | null;
|
|
223
|
+
limit: number;
|
|
224
|
+
before: string | undefined;
|
|
225
|
+
after: string | undefined;
|
|
226
|
+
searchQuery: string | null;
|
|
227
|
+
role: string | null;
|
|
228
|
+
}) {
|
|
229
|
+
if (!authToken) {
|
|
230
|
+
throw new Error("No auth token provided");
|
|
231
|
+
}
|
|
232
|
+
const url = new URL(`${baseUrl}/${API_ENDPOINTS.USERS}`);
|
|
233
|
+
url.searchParams.set("limit", limit.toString());
|
|
234
|
+
if (before) {
|
|
235
|
+
url.searchParams.set("before", before);
|
|
236
|
+
}
|
|
237
|
+
if (after) {
|
|
238
|
+
url.searchParams.set("after", after);
|
|
239
|
+
}
|
|
240
|
+
if (role) {
|
|
241
|
+
url.searchParams.set("role", role);
|
|
242
|
+
}
|
|
243
|
+
if (searchQuery) {
|
|
244
|
+
url.searchParams.set("search", searchQuery);
|
|
245
|
+
}
|
|
246
|
+
const response = await fetch(url, {
|
|
247
|
+
cache: "no-store",
|
|
248
|
+
headers: getAuthHeaders(authToken),
|
|
249
|
+
}).catch((error) => {
|
|
250
|
+
console.error(error);
|
|
251
|
+
// TODO: handle fetch errors
|
|
252
|
+
throw new Error("Failed to fetch users");
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
if (!response.ok) {
|
|
256
|
+
// TODO: handle this case
|
|
257
|
+
throw new Error("Failed to fetch users");
|
|
258
|
+
}
|
|
259
|
+
// TODO validate the response
|
|
260
|
+
const json = await response.json();
|
|
261
|
+
const { data, listMetadata: pagination } = json as {
|
|
262
|
+
data: User[];
|
|
263
|
+
listMetadata: PaginationData;
|
|
264
|
+
};
|
|
265
|
+
return { data, pagination };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const fetchUser = async (
|
|
269
|
+
userId: string,
|
|
270
|
+
{ authToken, baseUrl }: { authToken: string | null; baseUrl: string },
|
|
271
|
+
) => {
|
|
272
|
+
if (!authToken) {
|
|
273
|
+
throw new Error("No auth token provided");
|
|
274
|
+
}
|
|
275
|
+
const response = await fetch(`${baseUrl}/${API_ENDPOINTS.USERS}/${userId}`, {
|
|
276
|
+
headers: getAuthHeaders(authToken),
|
|
277
|
+
cache: "no-store",
|
|
278
|
+
}).catch((error) => {
|
|
279
|
+
console.error(error);
|
|
280
|
+
// TODO: handle fetch errors
|
|
281
|
+
throw new Error("Failed to fetch user");
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (!response.ok) {
|
|
285
|
+
// TODO: handle this case
|
|
286
|
+
throw new Error("Failed to fetch user");
|
|
287
|
+
}
|
|
288
|
+
// TODO validate the response
|
|
289
|
+
const json = await response.json();
|
|
290
|
+
return json.data as User;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const updateUserRole = async (
|
|
294
|
+
userId: string,
|
|
295
|
+
{
|
|
296
|
+
authToken,
|
|
297
|
+
baseUrl,
|
|
298
|
+
data,
|
|
299
|
+
}: { authToken: string | null; baseUrl: string; data: { roles: string[] } },
|
|
300
|
+
) => {
|
|
301
|
+
if (!authToken) {
|
|
302
|
+
// TODO handle this case
|
|
303
|
+
throw new Error("No auth token");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const response = await fetch(`${baseUrl}/${API_ENDPOINTS.USERS}/${userId}`, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: {
|
|
309
|
+
"Content-Type": "application/json",
|
|
310
|
+
...getAuthHeaders(authToken),
|
|
311
|
+
},
|
|
312
|
+
body: JSON.stringify(data),
|
|
313
|
+
}).catch((error) => {
|
|
314
|
+
console.error(error);
|
|
315
|
+
// TODO: handle fetch errors
|
|
316
|
+
throw new Error("Failed to update user");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
if (!response.ok) {
|
|
320
|
+
throw new Error("Failed to update user");
|
|
321
|
+
}
|
|
322
|
+
// TODO validate the response
|
|
323
|
+
const json = await response.json();
|
|
324
|
+
return json as { id: string; success: boolean };
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const inviteUser = async ({
|
|
328
|
+
authToken,
|
|
329
|
+
baseUrl,
|
|
330
|
+
...data
|
|
331
|
+
}: InviteUserPayload & { authToken: string | null; baseUrl: string }) => {
|
|
332
|
+
if (!authToken) {
|
|
333
|
+
// TODO handle this case
|
|
334
|
+
throw new Error("No auth token");
|
|
335
|
+
}
|
|
336
|
+
if (!data.role) {
|
|
337
|
+
throw new Error("Role required to invite user");
|
|
338
|
+
}
|
|
339
|
+
const response = await fetch(`${baseUrl}/${API_ENDPOINTS.USER_INVITE}`, {
|
|
340
|
+
method: "POST",
|
|
341
|
+
headers: {
|
|
342
|
+
"Content-Type": "application/json",
|
|
343
|
+
...getAuthHeaders(authToken),
|
|
344
|
+
},
|
|
345
|
+
body: JSON.stringify({
|
|
346
|
+
email: data.email,
|
|
347
|
+
roles: [data.role],
|
|
348
|
+
}),
|
|
349
|
+
}).catch((error) => {
|
|
350
|
+
console.error(error);
|
|
351
|
+
// TODO: handle fetch errors
|
|
352
|
+
throw new Error("Failed to invite user");
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
const { message, status } = await parseErrorResponse(response);
|
|
357
|
+
throw new InviteUserError({
|
|
358
|
+
message,
|
|
359
|
+
status,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// TODO validate the response
|
|
363
|
+
const json = await response.json();
|
|
364
|
+
return json as unknown;
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
const deleteUser = async (
|
|
368
|
+
userId: string,
|
|
369
|
+
{ authToken, baseUrl }: { authToken: string | null; baseUrl: string },
|
|
370
|
+
) => {
|
|
371
|
+
if (!authToken) {
|
|
372
|
+
// TODO handle this case
|
|
373
|
+
throw new Error("No auth token");
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const response = await fetch(`${baseUrl}/${API_ENDPOINTS.USERS}/${userId}`, {
|
|
377
|
+
method: "DELETE",
|
|
378
|
+
headers: getAuthHeaders(authToken),
|
|
379
|
+
}).catch((error) => {
|
|
380
|
+
console.error(error);
|
|
381
|
+
// TODO: handle fetch errors
|
|
382
|
+
throw new Error("Failed to delete user. Please try again.");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
console.error(response);
|
|
387
|
+
// TODO: handle server errors
|
|
388
|
+
throw new Error("Failed to delete user. Please try again.");
|
|
389
|
+
}
|
|
390
|
+
// TODO validate the response
|
|
391
|
+
const json = await response.json();
|
|
392
|
+
return json as unknown;
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
const revokeUserInvite = async (
|
|
396
|
+
userId: string,
|
|
397
|
+
{ authToken, baseUrl }: { authToken: string | null; baseUrl: string },
|
|
398
|
+
) => {
|
|
399
|
+
if (!authToken) {
|
|
400
|
+
// TODO handle this case
|
|
401
|
+
throw new Error("No auth token");
|
|
402
|
+
}
|
|
403
|
+
const response = await fetch(
|
|
404
|
+
`${baseUrl}/${API_ENDPOINTS.USER_INVITES}/${userId}`,
|
|
405
|
+
{
|
|
406
|
+
method: "DELETE",
|
|
407
|
+
headers: getAuthHeaders(authToken),
|
|
408
|
+
},
|
|
409
|
+
).catch((error) => {
|
|
410
|
+
console.error(error);
|
|
411
|
+
// TODO: handle fetch errors
|
|
412
|
+
throw new Error("Failed to resend user invite. Please try again.");
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
if (!response.ok) {
|
|
416
|
+
// TODO: handle server errors
|
|
417
|
+
throw new Error("Failed to resend user invite. Please try again.");
|
|
418
|
+
}
|
|
419
|
+
// TODO validate the response
|
|
420
|
+
const json = await response.json();
|
|
421
|
+
return json as unknown;
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
const resendUserInvite = async (
|
|
425
|
+
userId: string,
|
|
426
|
+
{ authToken, baseUrl }: { authToken: string | null; baseUrl: string },
|
|
427
|
+
) => {
|
|
428
|
+
if (!authToken) {
|
|
429
|
+
// TODO handle this case
|
|
430
|
+
throw new Error("No auth token");
|
|
431
|
+
}
|
|
432
|
+
const response = await fetch(
|
|
433
|
+
`${baseUrl}/${API_ENDPOINTS.USER_INVITES_RESEND(userId)}`,
|
|
434
|
+
{
|
|
435
|
+
method: "POST",
|
|
436
|
+
headers: getAuthHeaders(authToken),
|
|
437
|
+
},
|
|
438
|
+
).catch((error) => {
|
|
439
|
+
console.error(error);
|
|
440
|
+
// TODO: handle fetch errors
|
|
441
|
+
throw new Error("Failed to resend user invite. Please try again.");
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
if (!response.ok) {
|
|
445
|
+
// TODO: handle server errors
|
|
446
|
+
throw new Error("Failed to resend user invite. Please try again.");
|
|
447
|
+
}
|
|
448
|
+
// TODO validate the response
|
|
449
|
+
const json = await response.json();
|
|
450
|
+
return json as unknown;
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
function getAuthHeaders(authToken: string): HeadersInit {
|
|
454
|
+
return {
|
|
455
|
+
Authorization: `Bearer ${authToken}`,
|
|
456
|
+
"WorkOS-Widgets-Version": WIDGETS_API_VERSION,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
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 type { User } from "./api/user";
|
|
14
|
+
import { DestructiveButton, SecondaryButton } from "./elements";
|
|
15
|
+
|
|
16
|
+
interface DeleteUserDialogProps extends AlertDialog.RootProps {
|
|
17
|
+
open: boolean;
|
|
18
|
+
onOpenChange: (open: boolean) => void;
|
|
19
|
+
user: User;
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const DeleteUserDialog = ({
|
|
24
|
+
children,
|
|
25
|
+
user,
|
|
26
|
+
...props
|
|
27
|
+
}: DeleteUserDialogProps) => {
|
|
28
|
+
const deleteUser = useDeleteUser();
|
|
29
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
30
|
+
|
|
31
|
+
const onSubmitForm = () => {
|
|
32
|
+
deleteUser.mutate(user.id, {
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
props.onOpenChange(false);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<AlertDialog.Root {...props}>
|
|
41
|
+
{children && <AlertDialog.Trigger>{children}</AlertDialog.Trigger>}
|
|
42
|
+
|
|
43
|
+
<AlertDialog.Content
|
|
44
|
+
maxWidth="480px"
|
|
45
|
+
onOpenAutoFocus={() => {
|
|
46
|
+
requestAnimationFrame(() => {
|
|
47
|
+
inputRef.current?.focus();
|
|
48
|
+
});
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
<AlertDialog.Title>Remove user</AlertDialog.Title>
|
|
52
|
+
<Flex direction="column" gap="3">
|
|
53
|
+
<AlertDialog.Description>
|
|
54
|
+
Are you sure you want to remove{" "}
|
|
55
|
+
<Text weight="bold">{user.email}</Text>? This action is immediate
|
|
56
|
+
and cannot be undone.
|
|
57
|
+
</AlertDialog.Description>
|
|
58
|
+
</Flex>
|
|
59
|
+
|
|
60
|
+
{deleteUser.error ? (
|
|
61
|
+
<Callout.Root color="red" mt="4" mb="-2">
|
|
62
|
+
<Callout.Text>
|
|
63
|
+
{getMutationErrorMessage(deleteUser.error)}
|
|
64
|
+
</Callout.Text>
|
|
65
|
+
</Callout.Root>
|
|
66
|
+
) : null}
|
|
67
|
+
|
|
68
|
+
<Flex gap="3" justify="end" mt="5" asChild>
|
|
69
|
+
<form
|
|
70
|
+
onSubmit={(event) => {
|
|
71
|
+
event.preventDefault();
|
|
72
|
+
onSubmitForm();
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<AlertDialog.Cancel>
|
|
76
|
+
<SecondaryButton disabled={deleteUser.isPending}>
|
|
77
|
+
Cancel
|
|
78
|
+
</SecondaryButton>
|
|
79
|
+
</AlertDialog.Cancel>
|
|
80
|
+
|
|
81
|
+
<DestructiveButton type="submit" loading={deleteUser.isPending}>
|
|
82
|
+
Remove
|
|
83
|
+
</DestructiveButton>
|
|
84
|
+
</form>
|
|
85
|
+
</Flex>
|
|
86
|
+
{/* mirror errors in a live region */}
|
|
87
|
+
<VisuallyHidden asChild>
|
|
88
|
+
<section aria-live="polite">
|
|
89
|
+
{getMutationErrorMessage(deleteUser.error)}
|
|
90
|
+
</section>
|
|
91
|
+
</VisuallyHidden>
|
|
92
|
+
</AlertDialog.Content>
|
|
93
|
+
</AlertDialog.Root>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
function getMutationErrorMessage(error: unknown) {
|
|
98
|
+
if (!error) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
// TODO Handle server errors
|
|
102
|
+
return "There was an error removing the user. Please try again.";
|
|
103
|
+
}
|