@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.
Files changed (230) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -0
  3. package/dist/cjs/index.d.ts +3 -0
  4. package/dist/cjs/index.d.ts.map +1 -0
  5. package/dist/cjs/index.js +8 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/lib/api/config.d.ts +9 -0
  8. package/dist/cjs/lib/api/config.d.ts.map +1 -0
  9. package/dist/cjs/lib/api/config.js +12 -0
  10. package/dist/cjs/lib/api/config.js.map +1 -0
  11. package/dist/cjs/lib/api/role.d.ts +9 -0
  12. package/dist/cjs/lib/api/role.d.ts.map +1 -0
  13. package/dist/cjs/lib/api/role.js +94 -0
  14. package/dist/cjs/lib/api/role.js.map +1 -0
  15. package/dist/cjs/lib/api/user.d.ts +61 -0
  16. package/dist/cjs/lib/api/user.d.ts.map +1 -0
  17. package/dist/cjs/lib/api/user.js +312 -0
  18. package/dist/cjs/lib/api/user.js.map +1 -0
  19. package/dist/cjs/lib/constants.d.ts +3 -0
  20. package/dist/cjs/lib/constants.d.ts.map +1 -0
  21. package/dist/cjs/lib/constants.js +6 -0
  22. package/dist/cjs/lib/constants.js.map +1 -0
  23. package/dist/cjs/lib/delete-user-dialog.d.ts +12 -0
  24. package/dist/cjs/lib/delete-user-dialog.d.ts.map +1 -0
  25. package/dist/cjs/lib/delete-user-dialog.js +37 -0
  26. package/dist/cjs/lib/delete-user-dialog.js.map +1 -0
  27. package/dist/cjs/lib/edit-user-details-dialog.d.ts +12 -0
  28. package/dist/cjs/lib/edit-user-details-dialog.d.ts.map +1 -0
  29. package/dist/cjs/lib/edit-user-details-dialog.js +81 -0
  30. package/dist/cjs/lib/edit-user-details-dialog.js.map +1 -0
  31. package/dist/cjs/lib/elements.d.ts +32 -0
  32. package/dist/cjs/lib/elements.d.ts.map +1 -0
  33. package/dist/cjs/lib/elements.js +57 -0
  34. package/dist/cjs/lib/elements.js.map +1 -0
  35. package/dist/cjs/lib/invite-user-dialog.d.ts +7 -0
  36. package/dist/cjs/lib/invite-user-dialog.d.ts.map +1 -0
  37. package/dist/cjs/lib/invite-user-dialog.js +167 -0
  38. package/dist/cjs/lib/invite-user-dialog.js.map +1 -0
  39. package/dist/cjs/lib/label.d.ts +7 -0
  40. package/dist/cjs/lib/label.d.ts.map +1 -0
  41. package/dist/cjs/lib/label.js +9 -0
  42. package/dist/cjs/lib/label.js.map +1 -0
  43. package/dist/cjs/lib/pagination.d.ts +8 -0
  44. package/dist/cjs/lib/pagination.d.ts.map +1 -0
  45. package/dist/cjs/lib/pagination.js +67 -0
  46. package/dist/cjs/lib/pagination.js.map +1 -0
  47. package/dist/cjs/lib/resend-invite-dialog.d.ts +10 -0
  48. package/dist/cjs/lib/resend-invite-dialog.d.ts.map +1 -0
  49. package/dist/cjs/lib/resend-invite-dialog.js +71 -0
  50. package/dist/cjs/lib/resend-invite-dialog.js.map +1 -0
  51. package/dist/cjs/lib/revoke-invite-dialog.d.ts +10 -0
  52. package/dist/cjs/lib/revoke-invite-dialog.d.ts.map +1 -0
  53. package/dist/cjs/lib/revoke-invite-dialog.js +37 -0
  54. package/dist/cjs/lib/revoke-invite-dialog.js.map +1 -0
  55. package/dist/cjs/lib/search-provider.d.ts +11 -0
  56. package/dist/cjs/lib/search-provider.d.ts.map +1 -0
  57. package/dist/cjs/lib/search-provider.js +55 -0
  58. package/dist/cjs/lib/search-provider.js.map +1 -0
  59. package/dist/cjs/lib/use-is-hydrated.d.ts +2 -0
  60. package/dist/cjs/lib/use-is-hydrated.d.ts.map +1 -0
  61. package/dist/cjs/lib/use-is-hydrated.js +34 -0
  62. package/dist/cjs/lib/use-is-hydrated.js.map +1 -0
  63. package/dist/cjs/lib/user-actions-dropdown.d.ts +9 -0
  64. package/dist/cjs/lib/user-actions-dropdown.d.ts.map +1 -0
  65. package/dist/cjs/lib/user-actions-dropdown.js +83 -0
  66. package/dist/cjs/lib/user-actions-dropdown.js.map +1 -0
  67. package/dist/cjs/lib/users-filter.d.ts +9 -0
  68. package/dist/cjs/lib/users-filter.d.ts.map +1 -0
  69. package/dist/cjs/lib/users-filter.js +63 -0
  70. package/dist/cjs/lib/users-filter.js.map +1 -0
  71. package/dist/cjs/lib/users-management-context.d.ts +23 -0
  72. package/dist/cjs/lib/users-management-context.d.ts.map +1 -0
  73. package/dist/cjs/lib/users-management-context.js +83 -0
  74. package/dist/cjs/lib/users-management-context.js.map +1 -0
  75. package/dist/cjs/lib/users-management-state.d.ts +22 -0
  76. package/dist/cjs/lib/users-management-state.d.ts.map +1 -0
  77. package/dist/cjs/lib/users-management-state.js +143 -0
  78. package/dist/cjs/lib/users-management-state.js.map +1 -0
  79. package/dist/cjs/lib/users-management.d.ts +12 -0
  80. package/dist/cjs/lib/users-management.d.ts.map +1 -0
  81. package/dist/cjs/lib/users-management.js +141 -0
  82. package/dist/cjs/lib/users-management.js.map +1 -0
  83. package/dist/cjs/lib/users-search.d.ts +3 -0
  84. package/dist/cjs/lib/users-search.d.ts.map +1 -0
  85. package/dist/cjs/lib/users-search.js +65 -0
  86. package/dist/cjs/lib/users-search.js.map +1 -0
  87. package/dist/cjs/lib/utils.d.ts +15 -0
  88. package/dist/cjs/lib/utils.d.ts.map +1 -0
  89. package/dist/cjs/lib/utils.js +78 -0
  90. package/dist/cjs/lib/utils.js.map +1 -0
  91. package/dist/cjs/lib/widgets-context.d.ts +11 -0
  92. package/dist/cjs/lib/widgets-context.d.ts.map +1 -0
  93. package/dist/cjs/lib/widgets-context.js +45 -0
  94. package/dist/cjs/lib/widgets-context.js.map +1 -0
  95. package/dist/cjs/users-management.client.d.ts +6 -0
  96. package/dist/cjs/users-management.client.d.ts.map +1 -0
  97. package/dist/cjs/users-management.client.js +57 -0
  98. package/dist/cjs/users-management.client.js.map +1 -0
  99. package/dist/cjs/workos-widgets.client.d.ts +17 -0
  100. package/dist/cjs/workos-widgets.client.d.ts.map +1 -0
  101. package/dist/cjs/workos-widgets.client.js +55 -0
  102. package/dist/cjs/workos-widgets.client.js.map +1 -0
  103. package/dist/esm/index.d.ts +3 -0
  104. package/dist/esm/index.d.ts.map +1 -0
  105. package/dist/esm/index.js +3 -0
  106. package/dist/esm/index.js.map +1 -0
  107. package/dist/esm/lib/api/config.d.ts +9 -0
  108. package/dist/esm/lib/api/config.d.ts.map +1 -0
  109. package/dist/esm/lib/api/config.js +9 -0
  110. package/dist/esm/lib/api/config.js.map +1 -0
  111. package/dist/esm/lib/api/role.d.ts +9 -0
  112. package/dist/esm/lib/api/role.d.ts.map +1 -0
  113. package/dist/esm/lib/api/role.js +89 -0
  114. package/dist/esm/lib/api/role.js.map +1 -0
  115. package/dist/esm/lib/api/user.d.ts +61 -0
  116. package/dist/esm/lib/api/user.d.ts.map +1 -0
  117. package/dist/esm/lib/api/user.js +302 -0
  118. package/dist/esm/lib/api/user.js.map +1 -0
  119. package/dist/esm/lib/constants.d.ts +3 -0
  120. package/dist/esm/lib/constants.d.ts.map +1 -0
  121. package/dist/esm/lib/constants.js +3 -0
  122. package/dist/esm/lib/constants.js.map +1 -0
  123. package/dist/esm/lib/delete-user-dialog.d.ts +12 -0
  124. package/dist/esm/lib/delete-user-dialog.d.ts.map +1 -0
  125. package/dist/esm/lib/delete-user-dialog.js +33 -0
  126. package/dist/esm/lib/delete-user-dialog.js.map +1 -0
  127. package/dist/esm/lib/edit-user-details-dialog.d.ts +12 -0
  128. package/dist/esm/lib/edit-user-details-dialog.d.ts.map +1 -0
  129. package/dist/esm/lib/edit-user-details-dialog.js +54 -0
  130. package/dist/esm/lib/edit-user-details-dialog.js.map +1 -0
  131. package/dist/esm/lib/elements.d.ts +32 -0
  132. package/dist/esm/lib/elements.d.ts.map +1 -0
  133. package/dist/esm/lib/elements.js +54 -0
  134. package/dist/esm/lib/elements.js.map +1 -0
  135. package/dist/esm/lib/invite-user-dialog.d.ts +7 -0
  136. package/dist/esm/lib/invite-user-dialog.d.ts.map +1 -0
  137. package/dist/esm/lib/invite-user-dialog.js +140 -0
  138. package/dist/esm/lib/invite-user-dialog.js.map +1 -0
  139. package/dist/esm/lib/label.d.ts +7 -0
  140. package/dist/esm/lib/label.d.ts.map +1 -0
  141. package/dist/esm/lib/label.js +6 -0
  142. package/dist/esm/lib/label.js.map +1 -0
  143. package/dist/esm/lib/pagination.d.ts +8 -0
  144. package/dist/esm/lib/pagination.d.ts.map +1 -0
  145. package/dist/esm/lib/pagination.js +40 -0
  146. package/dist/esm/lib/pagination.js.map +1 -0
  147. package/dist/esm/lib/resend-invite-dialog.d.ts +10 -0
  148. package/dist/esm/lib/resend-invite-dialog.d.ts.map +1 -0
  149. package/dist/esm/lib/resend-invite-dialog.js +44 -0
  150. package/dist/esm/lib/resend-invite-dialog.js.map +1 -0
  151. package/dist/esm/lib/revoke-invite-dialog.d.ts +10 -0
  152. package/dist/esm/lib/revoke-invite-dialog.d.ts.map +1 -0
  153. package/dist/esm/lib/revoke-invite-dialog.js +33 -0
  154. package/dist/esm/lib/revoke-invite-dialog.js.map +1 -0
  155. package/dist/esm/lib/search-provider.d.ts +11 -0
  156. package/dist/esm/lib/search-provider.d.ts.map +1 -0
  157. package/dist/esm/lib/search-provider.js +27 -0
  158. package/dist/esm/lib/search-provider.js.map +1 -0
  159. package/dist/esm/lib/use-is-hydrated.d.ts +2 -0
  160. package/dist/esm/lib/use-is-hydrated.d.ts.map +1 -0
  161. package/dist/esm/lib/use-is-hydrated.js +8 -0
  162. package/dist/esm/lib/use-is-hydrated.js.map +1 -0
  163. package/dist/esm/lib/user-actions-dropdown.d.ts +9 -0
  164. package/dist/esm/lib/user-actions-dropdown.d.ts.map +1 -0
  165. package/dist/esm/lib/user-actions-dropdown.js +56 -0
  166. package/dist/esm/lib/user-actions-dropdown.js.map +1 -0
  167. package/dist/esm/lib/users-filter.d.ts +9 -0
  168. package/dist/esm/lib/users-filter.d.ts.map +1 -0
  169. package/dist/esm/lib/users-filter.js +36 -0
  170. package/dist/esm/lib/users-filter.js.map +1 -0
  171. package/dist/esm/lib/users-management-context.d.ts +23 -0
  172. package/dist/esm/lib/users-management-context.d.ts.map +1 -0
  173. package/dist/esm/lib/users-management-context.js +54 -0
  174. package/dist/esm/lib/users-management-context.js.map +1 -0
  175. package/dist/esm/lib/users-management-state.d.ts +22 -0
  176. package/dist/esm/lib/users-management-state.d.ts.map +1 -0
  177. package/dist/esm/lib/users-management-state.js +117 -0
  178. package/dist/esm/lib/users-management-state.js.map +1 -0
  179. package/dist/esm/lib/users-management.d.ts +12 -0
  180. package/dist/esm/lib/users-management.d.ts.map +1 -0
  181. package/dist/esm/lib/users-management.js +114 -0
  182. package/dist/esm/lib/users-management.js.map +1 -0
  183. package/dist/esm/lib/users-search.d.ts +3 -0
  184. package/dist/esm/lib/users-search.d.ts.map +1 -0
  185. package/dist/esm/lib/users-search.js +39 -0
  186. package/dist/esm/lib/users-search.js.map +1 -0
  187. package/dist/esm/lib/utils.d.ts +15 -0
  188. package/dist/esm/lib/utils.d.ts.map +1 -0
  189. package/dist/esm/lib/utils.js +70 -0
  190. package/dist/esm/lib/utils.js.map +1 -0
  191. package/dist/esm/lib/widgets-context.d.ts +11 -0
  192. package/dist/esm/lib/widgets-context.d.ts.map +1 -0
  193. package/dist/esm/lib/widgets-context.js +17 -0
  194. package/dist/esm/lib/widgets-context.js.map +1 -0
  195. package/dist/esm/users-management.client.d.ts +6 -0
  196. package/dist/esm/users-management.client.d.ts.map +1 -0
  197. package/dist/esm/users-management.client.js +30 -0
  198. package/dist/esm/users-management.client.js.map +1 -0
  199. package/dist/esm/workos-widgets.client.d.ts +17 -0
  200. package/dist/esm/workos-widgets.client.d.ts.map +1 -0
  201. package/dist/esm/workos-widgets.client.js +28 -0
  202. package/dist/esm/workos-widgets.client.js.map +1 -0
  203. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  204. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  205. package/package.json +69 -0
  206. package/src/index.ts +5 -0
  207. package/src/lib/api/config.ts +9 -0
  208. package/src/lib/api/role.ts +124 -0
  209. package/src/lib/api/user.ts +458 -0
  210. package/src/lib/constants.ts +2 -0
  211. package/src/lib/delete-user-dialog.tsx +103 -0
  212. package/src/lib/edit-user-details-dialog.tsx +170 -0
  213. package/src/lib/elements.tsx +175 -0
  214. package/src/lib/invite-user-dialog.tsx +319 -0
  215. package/src/lib/label.tsx +14 -0
  216. package/src/lib/pagination.tsx +69 -0
  217. package/src/lib/resend-invite-dialog.tsx +136 -0
  218. package/src/lib/revoke-invite-dialog.tsx +104 -0
  219. package/src/lib/search-provider.tsx +51 -0
  220. package/src/lib/use-is-hydrated.ts +13 -0
  221. package/src/lib/user-actions-dropdown.tsx +161 -0
  222. package/src/lib/users-filter.tsx +122 -0
  223. package/src/lib/users-management-context.tsx +89 -0
  224. package/src/lib/users-management-state.ts +165 -0
  225. package/src/lib/users-management.tsx +461 -0
  226. package/src/lib/users-search.tsx +130 -0
  227. package/src/lib/utils.ts +94 -0
  228. package/src/lib/widgets-context.ts +29 -0
  229. package/src/users-management.client.tsx +59 -0
  230. 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,2 @@
1
+ export const USER_ROW_LIMIT = 10;
2
+ export const WIDGETS_API_VERSION = "v1";
@@ -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
+ }