@workos-inc/widgets 0.0.0-pre.1 → 0.0.0-pre.3

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 (181) hide show
  1. package/dist/cjs/lib/api/role.d.ts.map +1 -1
  2. package/dist/cjs/lib/api/role.js +35 -14
  3. package/dist/cjs/lib/api/role.js.map +1 -1
  4. package/dist/cjs/lib/api/user.d.ts.map +1 -1
  5. package/dist/cjs/lib/api/user.js +105 -67
  6. package/dist/cjs/lib/api/user.js.map +1 -1
  7. package/dist/cjs/lib/constants.d.ts +2 -1
  8. package/dist/cjs/lib/constants.d.ts.map +1 -1
  9. package/dist/cjs/lib/constants.js +3 -2
  10. package/dist/cjs/lib/constants.js.map +1 -1
  11. package/dist/cjs/lib/delete-user-dialog.d.ts.map +1 -1
  12. package/dist/cjs/lib/delete-user-dialog.js +1 -1
  13. package/dist/cjs/lib/delete-user-dialog.js.map +1 -1
  14. package/dist/cjs/lib/edit-user-details-dialog.d.ts.map +1 -1
  15. package/dist/cjs/lib/edit-user-details-dialog.js +3 -5
  16. package/dist/cjs/lib/edit-user-details-dialog.js.map +1 -1
  17. package/dist/cjs/lib/elements.d.ts +23 -12
  18. package/dist/cjs/lib/elements.d.ts.map +1 -1
  19. package/dist/cjs/lib/elements.js +104 -30
  20. package/dist/cjs/lib/elements.js.map +1 -1
  21. package/dist/cjs/lib/error-boundary.d.ts +58 -0
  22. package/dist/cjs/lib/error-boundary.d.ts.map +1 -0
  23. package/dist/cjs/lib/error-boundary.js +113 -0
  24. package/dist/cjs/lib/error-boundary.js.map +1 -0
  25. package/dist/cjs/lib/errors.d.ts +34 -0
  26. package/dist/cjs/lib/errors.d.ts.map +1 -0
  27. package/dist/cjs/lib/errors.js +40 -0
  28. package/dist/cjs/lib/errors.js.map +1 -0
  29. package/dist/cjs/lib/invite-user-dialog.d.ts.map +1 -1
  30. package/dist/cjs/lib/invite-user-dialog.js +4 -4
  31. package/dist/cjs/lib/invite-user-dialog.js.map +1 -1
  32. package/dist/cjs/lib/resend-invite-dialog.d.ts.map +1 -1
  33. package/dist/cjs/lib/resend-invite-dialog.js +2 -2
  34. package/dist/cjs/lib/resend-invite-dialog.js.map +1 -1
  35. package/dist/cjs/lib/revoke-invite-dialog.d.ts.map +1 -1
  36. package/dist/cjs/lib/revoke-invite-dialog.js +1 -1
  37. package/dist/cjs/lib/revoke-invite-dialog.js.map +1 -1
  38. package/dist/cjs/lib/use-layout-effect.d.ts +4 -0
  39. package/dist/cjs/lib/use-layout-effect.d.ts.map +1 -0
  40. package/dist/cjs/lib/use-layout-effect.js +8 -0
  41. package/dist/cjs/lib/use-layout-effect.js.map +1 -0
  42. package/dist/cjs/lib/user-actions-dropdown.d.ts.map +1 -1
  43. package/dist/cjs/lib/user-actions-dropdown.js +2 -9
  44. package/dist/cjs/lib/user-actions-dropdown.js.map +1 -1
  45. package/dist/cjs/lib/users-filter.d.ts +1 -0
  46. package/dist/cjs/lib/users-filter.d.ts.map +1 -1
  47. package/dist/cjs/lib/users-filter.js +2 -9
  48. package/dist/cjs/lib/users-filter.js.map +1 -1
  49. package/dist/cjs/lib/users-management-context.d.ts +2 -2
  50. package/dist/cjs/lib/users-management-context.d.ts.map +1 -1
  51. package/dist/cjs/lib/users-management.d.ts +9 -6
  52. package/dist/cjs/lib/users-management.d.ts.map +1 -1
  53. package/dist/cjs/lib/users-management.js +77 -20
  54. package/dist/cjs/lib/users-management.js.map +1 -1
  55. package/dist/cjs/lib/users-search.d.ts +1 -1
  56. package/dist/cjs/lib/users-search.d.ts.map +1 -1
  57. package/dist/cjs/lib/users-search.js +3 -9
  58. package/dist/cjs/lib/users-search.js.map +1 -1
  59. package/dist/cjs/lib/utils.d.ts +2 -0
  60. package/dist/cjs/lib/utils.d.ts.map +1 -1
  61. package/dist/cjs/lib/utils.js +18 -0
  62. package/dist/cjs/lib/utils.js.map +1 -1
  63. package/dist/cjs/users-management.client.d.ts +1 -1
  64. package/dist/cjs/users-management.client.d.ts.map +1 -1
  65. package/dist/cjs/users-management.client.js +15 -5
  66. package/dist/cjs/users-management.client.js.map +1 -1
  67. package/dist/cjs/workos-widgets.client.d.ts.map +1 -1
  68. package/dist/cjs/workos-widgets.client.js +2 -1
  69. package/dist/cjs/workos-widgets.client.js.map +1 -1
  70. package/dist/esm/lib/api/role.d.ts.map +1 -1
  71. package/dist/esm/lib/api/role.js +35 -14
  72. package/dist/esm/lib/api/role.js.map +1 -1
  73. package/dist/esm/lib/api/user.d.ts.map +1 -1
  74. package/dist/esm/lib/api/user.js +105 -67
  75. package/dist/esm/lib/api/user.js.map +1 -1
  76. package/dist/esm/lib/constants.d.ts +2 -1
  77. package/dist/esm/lib/constants.d.ts.map +1 -1
  78. package/dist/esm/lib/constants.js +2 -1
  79. package/dist/esm/lib/constants.js.map +1 -1
  80. package/dist/esm/lib/delete-user-dialog.d.ts.map +1 -1
  81. package/dist/esm/lib/delete-user-dialog.js +2 -2
  82. package/dist/esm/lib/delete-user-dialog.js.map +1 -1
  83. package/dist/esm/lib/edit-user-details-dialog.d.ts.map +1 -1
  84. package/dist/esm/lib/edit-user-details-dialog.js +4 -6
  85. package/dist/esm/lib/edit-user-details-dialog.js.map +1 -1
  86. package/dist/esm/lib/elements.d.ts +23 -12
  87. package/dist/esm/lib/elements.d.ts.map +1 -1
  88. package/dist/esm/lib/elements.js +81 -30
  89. package/dist/esm/lib/elements.js.map +1 -1
  90. package/dist/esm/lib/error-boundary.d.ts +58 -0
  91. package/dist/esm/lib/error-boundary.d.ts.map +1 -0
  92. package/dist/esm/lib/error-boundary.js +86 -0
  93. package/dist/esm/lib/error-boundary.js.map +1 -0
  94. package/dist/esm/lib/errors.d.ts +34 -0
  95. package/dist/esm/lib/errors.d.ts.map +1 -0
  96. package/dist/esm/lib/errors.js +34 -0
  97. package/dist/esm/lib/errors.js.map +1 -0
  98. package/dist/esm/lib/invite-user-dialog.d.ts.map +1 -1
  99. package/dist/esm/lib/invite-user-dialog.js +4 -4
  100. package/dist/esm/lib/invite-user-dialog.js.map +1 -1
  101. package/dist/esm/lib/resend-invite-dialog.d.ts.map +1 -1
  102. package/dist/esm/lib/resend-invite-dialog.js +3 -3
  103. package/dist/esm/lib/resend-invite-dialog.js.map +1 -1
  104. package/dist/esm/lib/revoke-invite-dialog.d.ts.map +1 -1
  105. package/dist/esm/lib/revoke-invite-dialog.js +2 -2
  106. package/dist/esm/lib/revoke-invite-dialog.js.map +1 -1
  107. package/dist/esm/lib/use-layout-effect.d.ts +4 -0
  108. package/dist/esm/lib/use-layout-effect.d.ts.map +1 -0
  109. package/dist/esm/lib/use-layout-effect.js +5 -0
  110. package/dist/esm/lib/use-layout-effect.js.map +1 -0
  111. package/dist/esm/lib/user-actions-dropdown.d.ts.map +1 -1
  112. package/dist/esm/lib/user-actions-dropdown.js +3 -10
  113. package/dist/esm/lib/user-actions-dropdown.js.map +1 -1
  114. package/dist/esm/lib/users-filter.d.ts +1 -0
  115. package/dist/esm/lib/users-filter.d.ts.map +1 -1
  116. package/dist/esm/lib/users-filter.js +4 -11
  117. package/dist/esm/lib/users-filter.js.map +1 -1
  118. package/dist/esm/lib/users-management-context.d.ts +2 -2
  119. package/dist/esm/lib/users-management-context.d.ts.map +1 -1
  120. package/dist/esm/lib/users-management.d.ts +9 -6
  121. package/dist/esm/lib/users-management.d.ts.map +1 -1
  122. package/dist/esm/lib/users-management.js +74 -22
  123. package/dist/esm/lib/users-management.js.map +1 -1
  124. package/dist/esm/lib/users-search.d.ts +1 -1
  125. package/dist/esm/lib/users-search.d.ts.map +1 -1
  126. package/dist/esm/lib/users-search.js +5 -11
  127. package/dist/esm/lib/users-search.js.map +1 -1
  128. package/dist/esm/lib/utils.d.ts +2 -0
  129. package/dist/esm/lib/utils.d.ts.map +1 -1
  130. package/dist/esm/lib/utils.js +13 -0
  131. package/dist/esm/lib/utils.js.map +1 -1
  132. package/dist/esm/users-management.client.d.ts +1 -1
  133. package/dist/esm/users-management.client.d.ts.map +1 -1
  134. package/dist/esm/users-management.client.js +16 -6
  135. package/dist/esm/users-management.client.js.map +1 -1
  136. package/dist/esm/workos-widgets.client.d.ts.map +1 -1
  137. package/dist/esm/workos-widgets.client.js +2 -1
  138. package/dist/esm/workos-widgets.client.js.map +1 -1
  139. package/dist/tsconfig.cjs.tsbuildinfo +1 -1
  140. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  141. package/package.json +8 -3
  142. package/src/base.css +111 -0
  143. package/src/lib/api/role.ts +39 -16
  144. package/src/lib/api/user.ts +119 -75
  145. package/src/lib/constants.ts +2 -1
  146. package/src/lib/delete-user-dialog.tsx +7 -3
  147. package/src/lib/edit-user-details-dialog.tsx +15 -10
  148. package/src/lib/elements.tsx +254 -70
  149. package/src/lib/error-boundary.tsx +166 -0
  150. package/src/lib/errors.ts +49 -0
  151. package/src/lib/invite-user-dialog.tsx +22 -13
  152. package/src/lib/resend-invite-dialog.tsx +11 -5
  153. package/src/lib/revoke-invite-dialog.tsx +7 -3
  154. package/src/lib/use-layout-effect.ts +6 -0
  155. package/src/lib/user-actions-dropdown.tsx +8 -16
  156. package/src/lib/users-filter.tsx +13 -73
  157. package/src/lib/users-management-context.tsx +1 -1
  158. package/src/lib/users-management.tsx +339 -184
  159. package/src/lib/users-search.tsx +5 -63
  160. package/src/lib/utils.ts +21 -0
  161. package/src/users-management.client.tsx +37 -16
  162. package/src/users-management.css +4 -0
  163. package/src/workos-widgets.client.tsx +2 -1
  164. package/dist/cjs/lib/label.d.ts +0 -7
  165. package/dist/cjs/lib/label.d.ts.map +0 -1
  166. package/dist/cjs/lib/label.js +0 -9
  167. package/dist/cjs/lib/label.js.map +0 -1
  168. package/dist/cjs/lib/pagination.d.ts +0 -8
  169. package/dist/cjs/lib/pagination.d.ts.map +0 -1
  170. package/dist/cjs/lib/pagination.js +0 -67
  171. package/dist/cjs/lib/pagination.js.map +0 -1
  172. package/dist/esm/lib/label.d.ts +0 -7
  173. package/dist/esm/lib/label.d.ts.map +0 -1
  174. package/dist/esm/lib/label.js +0 -6
  175. package/dist/esm/lib/label.js.map +0 -1
  176. package/dist/esm/lib/pagination.d.ts +0 -8
  177. package/dist/esm/lib/pagination.d.ts.map +0 -1
  178. package/dist/esm/lib/pagination.js +0 -40
  179. package/dist/esm/lib/pagination.js.map +0 -1
  180. package/src/lib/label.tsx +0 -14
  181. package/src/lib/pagination.tsx +0 -69
@@ -5,6 +5,8 @@ import {
5
5
  Box,
6
6
  Flex,
7
7
  Grid,
8
+ Heading,
9
+ Select,
8
10
  Skeleton,
9
11
  Table,
10
12
  Text,
@@ -19,9 +21,12 @@ import {
19
21
  IconButton,
20
22
  PrimaryButton,
21
23
  SecondaryButton,
24
+ SelectContent,
25
+ SelectItem,
26
+ SelectTrigger,
27
+ TextField,
22
28
  } from "./elements";
23
29
  import { InviteUserDialog } from "./invite-user-dialog";
24
- import { Pagination } from "./pagination";
25
30
  import { SearchProvider, useSearchContext } from "./search-provider";
26
31
  import { useIsHydrated } from "./use-is-hydrated";
27
32
  import { UserActionsDropdown } from "./user-actions-dropdown";
@@ -30,65 +35,75 @@ import { UsersSearch } from "./users-search";
30
35
  import { getBestName, getComparativeReadableDate } from "./utils";
31
36
  import { USER_ROW_LIMIT } from "./constants";
32
37
  import { useUsersManagementContext } from "./users-management-context";
38
+ import clsx from "clsx";
39
+ import { ApiError, FetchError, NoAuthTokenError } from "./errors";
33
40
 
34
41
  interface UsersManagementProps {
35
- rolesData: Role[] | undefined;
36
- userData: Paginated<User[]> | undefined;
37
-
38
- // Render the rows with an alternate background color
39
- alternate?: boolean;
42
+ rolesData: Role[];
43
+ userData: Paginated<User[]>;
44
+ disableRolesFilter?: boolean;
40
45
  // When the users list is loading new users
41
- isPending?: boolean;
42
- // Show the skeleton UI
43
- isInitialLoading?: boolean;
46
+ isPending: boolean | undefined;
44
47
  }
45
48
 
46
49
  export const UsersManagement = ({
47
50
  userData,
48
51
  rolesData,
49
- alternate,
50
52
  isPending,
51
- isInitialLoading,
53
+ disableRolesFilter,
52
54
  }: UsersManagementProps) => {
53
55
  const users = userData?.data;
54
56
  const usersCount = users?.length ?? 0;
55
57
  const isHydrated = useIsHydrated();
58
+ const pagination = userData?.pagination;
59
+ const { dispatch } = useUsersManagementContext();
60
+
61
+ // we only want to show the loading indicator for some buttons if the request
62
+ // is still pending after 500ms. If the request is fast enough the indicator
63
+ // is a bit jarring.
64
+ const [deferredLoading, setDeferredLoading] = React.useState(false);
65
+ React.useEffect(() => {
66
+ if (isPending) {
67
+ const timeoutId = window.setTimeout(() => {
68
+ setDeferredLoading(true);
69
+ }, 500);
70
+ return () => {
71
+ window.clearTimeout(timeoutId);
72
+ };
73
+ } else {
74
+ setDeferredLoading(false);
75
+ }
76
+ }, [isPending]);
77
+
56
78
  return (
57
79
  <SearchProvider>
58
- <Flex direction="column" gap="3">
80
+ <UsersManagementRoot>
59
81
  <Grid columns="1fr auto" gap="2">
60
82
  <Flex gap="2" align="center">
61
- <Skeleton loading={isInitialLoading}>
62
- <Box flexBasis="380px" flexGrow="0" flexShrink="1">
63
- <UsersSearch />
64
- </Box>
65
- </Skeleton>
66
- <Skeleton loading={isInitialLoading}>
67
- <Box flexGrow="0" flexShrink="0">
68
- <UsersFilter roles={rolesData} />
69
- </Box>
70
- </Skeleton>
71
- </Flex>
72
-
73
- <Skeleton loading={isInitialLoading}>
74
- <Box flexGrow="0" flexShrink="0" style={{ placeSelf: "flex-end" }}>
75
- <InviteUserDialog>
76
- <PrimaryButton>Invite user</PrimaryButton>
77
- </InviteUserDialog>
83
+ <Box flexBasis="380px" flexGrow="0" flexShrink="1">
84
+ <UsersSearch />
78
85
  </Box>
79
- </Skeleton>
86
+ <Box flexGrow="0" flexShrink="0">
87
+ <UsersFilter roles={rolesData} disabled={disableRolesFilter} />
88
+ </Box>
89
+ </Flex>
90
+ <Box flexGrow="0" flexShrink="0" style={{ placeSelf: "flex-end" }}>
91
+ <InviteUserDialog>
92
+ <PrimaryButton>Invite user</PrimaryButton>
93
+ </InviteUserDialog>
94
+ </Box>
80
95
  </Grid>
81
96
  <Table.Root variant="ghost" size="1">
82
97
  <Table.Header>
83
98
  <Table.Row>
84
99
  <Table.ColumnHeaderCell width="260px">
85
- <Skeleton loading={isInitialLoading}>User</Skeleton>
100
+ User
86
101
  </Table.ColumnHeaderCell>
87
102
  <Table.ColumnHeaderCell width="100px">
88
- <Skeleton loading={isInitialLoading}>Role</Skeleton>
103
+ Role
89
104
  </Table.ColumnHeaderCell>
90
105
  <Table.ColumnHeaderCell width="140px">
91
- <Skeleton loading={isInitialLoading}>Last active</Skeleton>
106
+ Last active
92
107
  </Table.ColumnHeaderCell>
93
108
  <Table.ColumnHeaderCell width="28px" />
94
109
  </Table.Row>
@@ -100,118 +115,108 @@ export const UsersManagement = ({
100
115
  opacity: isPending && usersCount > 0 ? 0.5 : 1,
101
116
  }}
102
117
  >
103
- {isInitialLoading && (
104
- <SkeletonRows length={USER_ROW_LIMIT} alternate={alternate} />
105
- )}
106
- {users?.map((user, i) => {
107
- // TODO only support one role for now
108
- const userRole = user.roles[0]?.name;
109
- const userDisplayName = getBestName(user);
110
- const dimText =
111
- user.status === "InviteRevoked" ||
112
- user.status === "InviteExpired";
113
- return (
114
- <Table.Row
115
- key={user.id}
116
- align="center"
117
- style={{
118
- background:
119
- alternate && i % 2 === 1 ? "var(--gray-a1)" : undefined,
120
- }}
121
- >
122
- <Table.RowHeaderCell>
123
- <Flex
124
- align="center"
125
- gap="3"
126
- overflow="hidden"
127
- height="var(--space-7)"
128
- >
129
- <Avatar
130
- size="2"
131
- fallback={<FallbackUserIcon />}
132
- src={user.profilePictureUrl ?? undefined}
133
- dim={dimText}
134
- />
118
+ {users.length > 0 ? (
119
+ users.map((user) => {
120
+ // TODO only support one role for now
121
+ const userRole = user.roles[0]?.name;
122
+ const userDisplayName = getBestName(user);
123
+ const dimText =
124
+ user.status === "InviteRevoked" ||
125
+ user.status === "InviteExpired";
126
+ return (
127
+ <Table.Row key={user.id} align="center">
128
+ <Table.RowHeaderCell>
129
+ <Flex
130
+ align="center"
131
+ gap="3"
132
+ overflow="hidden"
133
+ height="var(--space-7)"
134
+ >
135
+ <Avatar
136
+ size="2"
137
+ fallback={<FallbackUserIcon />}
138
+ src={user.profilePictureUrl ?? undefined}
139
+ dim={dimText}
140
+ />
135
141
 
136
- {userDisplayName ? (
137
- <Flex
138
- direction="column"
139
- align="start"
140
- height="var(--space-7)"
141
- justify="center"
142
- overflow="hidden"
143
- >
142
+ {userDisplayName ? (
143
+ <Flex
144
+ direction="column"
145
+ align="start"
146
+ height="var(--space-7)"
147
+ justify="center"
148
+ overflow="hidden"
149
+ >
150
+ <Flex gap="2" align="center" minWidth="0">
151
+ <TableCellText dim={dimText}>
152
+ {userDisplayName}
153
+ </TableCellText>
154
+ <UserBadge user={user} />
155
+ </Flex>
156
+ <TableCellText
157
+ level="secondary"
158
+ title={user.email}
159
+ dim={dimText}
160
+ >
161
+ {user.email}
162
+ </TableCellText>
163
+ </Flex>
164
+ ) : (
144
165
  <Flex gap="2" align="center" minWidth="0">
145
- <TableCellText dim={dimText}>
146
- {userDisplayName}
166
+ <TableCellText dim={dimText} title={user.email}>
167
+ {user.email}
147
168
  </TableCellText>
148
169
  <UserBadge user={user} />
149
170
  </Flex>
150
- <TableCellText
151
- level="secondary"
152
- title={user.email}
153
- dim={dimText}
171
+ )}
172
+ </Flex>
173
+ </Table.RowHeaderCell>
174
+ <Table.Cell>
175
+ <TableCellText dim={dimText}>
176
+ {userRole || (
177
+ <>
178
+ <VisuallyHidden>No roles assigned</VisuallyHidden>
179
+ <span aria-hidden style={{ userSelect: "none" }}>
180
+
181
+ </span>
182
+ </>
183
+ )}
184
+ </TableCellText>
185
+ </Table.Cell>
186
+ <Table.Cell>
187
+ <LastActive
188
+ user={user}
189
+ isHydrated={isHydrated}
190
+ dim={dimText}
191
+ />
192
+ </Table.Cell>
193
+ <Table.Cell justify="end">
194
+ <UserActionsDropdown user={user}>
195
+ <IconButton title="User actions">
196
+ <VisuallyHidden>User actions</VisuallyHidden>
197
+ <svg
198
+ xmlns="http://www.w3.org/2000/svg"
199
+ fill="none"
200
+ viewBox="0 0 24 24"
201
+ width="16"
202
+ height="16"
203
+ strokeWidth={1.5}
204
+ stroke="currentColor"
205
+ aria-hidden
154
206
  >
155
- {user.email}
156
- </TableCellText>
157
- </Flex>
158
- ) : (
159
- <Flex gap="2" align="center" minWidth="0">
160
- <TableCellText dim={dimText} title={user.email}>
161
- {user.email}
162
- </TableCellText>
163
- <UserBadge user={user} />
164
- </Flex>
165
- )}
166
- </Flex>
167
- </Table.RowHeaderCell>
168
- <Table.Cell>
169
- <TableCellText dim={dimText}>
170
- {userRole || (
171
- <>
172
- <VisuallyHidden>No roles assigned</VisuallyHidden>
173
- <span aria-hidden style={{ userSelect: "none" }}>
174
-
175
- </span>
176
- </>
177
- )}
178
- </TableCellText>
179
- </Table.Cell>
180
- <Table.Cell>
181
- <LastActive
182
- user={user}
183
- isHydrated={isHydrated}
184
- dim={dimText}
185
- />
186
- </Table.Cell>
187
- <Table.Cell justify="end">
188
- <UserActionsDropdown user={user}>
189
- <IconButton title="User actions">
190
- <VisuallyHidden>User actions</VisuallyHidden>
191
- <svg
192
- xmlns="http://www.w3.org/2000/svg"
193
- fill="none"
194
- viewBox="0 0 24 24"
195
- width="16"
196
- height="16"
197
- strokeWidth={1.5}
198
- stroke="currentColor"
199
- aria-hidden
200
- >
201
- <path
202
- strokeLinecap="round"
203
- strokeLinejoin="round"
204
- d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
205
- />
206
- </svg>
207
- </IconButton>
208
- </UserActionsDropdown>
209
- </Table.Cell>
210
- </Table.Row>
211
- );
212
- })}
213
-
214
- {users?.length === 0 && (
207
+ <path
208
+ strokeLinecap="round"
209
+ strokeLinejoin="round"
210
+ d="M6.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM12.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0ZM18.75 12a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0Z"
211
+ />
212
+ </svg>
213
+ </IconButton>
214
+ </UserActionsDropdown>
215
+ </Table.Cell>
216
+ </Table.Row>
217
+ );
218
+ })
219
+ ) : (
215
220
  <Table.Row align="center">
216
221
  <Table.Cell colSpan={4}>
217
222
  <UsersManagementEmptyState />
@@ -221,12 +226,205 @@ export const UsersManagement = ({
221
226
  </Table.Body>
222
227
  </Table.Root>
223
228
 
224
- <Pagination isPending={isPending} pagination={userData?.pagination} />
225
- </Flex>
229
+ <Flex gap="2" justify="end">
230
+ <SecondaryButton
231
+ size="1"
232
+ disabled={!pagination?.after || isPending || undefined}
233
+ loading={deferredLoading}
234
+ onClick={() => {
235
+ if (pagination?.after) {
236
+ dispatch({
237
+ type: "SET_PAGINATION",
238
+ pagination: { after: pagination.after },
239
+ });
240
+ }
241
+ }}
242
+ >
243
+ Previous
244
+ </SecondaryButton>
245
+ <SecondaryButton
246
+ size="1"
247
+ disabled={!pagination?.before || isPending || undefined}
248
+ loading={deferredLoading}
249
+ onClick={() => {
250
+ if (pagination?.before) {
251
+ dispatch({
252
+ type: "SET_PAGINATION",
253
+ pagination: { before: pagination.before },
254
+ });
255
+ }
256
+ }}
257
+ >
258
+ Next
259
+ </SecondaryButton>
260
+ </Flex>
261
+ </UsersManagementRoot>
226
262
  </SearchProvider>
227
263
  );
228
264
  };
229
265
 
266
+ export function UsersManagementLoading() {
267
+ return (
268
+ <UsersManagementRoot>
269
+ <Grid columns="1fr auto" gap="2">
270
+ <Flex gap="2" align="center">
271
+ <Skeleton loading>
272
+ <Box flexBasis="380px" flexGrow="0" flexShrink="1">
273
+ <TextField />
274
+ </Box>
275
+ </Skeleton>
276
+ <Skeleton loading>
277
+ <Box flexGrow="0" flexShrink="0">
278
+ <Select.Root value="all" onValueChange={() => void 0}>
279
+ <SelectTrigger>All</SelectTrigger>
280
+ <SelectContent>
281
+ <SelectItem value="all">All</SelectItem>
282
+ </SelectContent>
283
+ </Select.Root>
284
+ </Box>
285
+ </Skeleton>
286
+ </Flex>
287
+ <Skeleton loading>
288
+ <Box flexGrow="0" flexShrink="0" style={{ placeSelf: "flex-end" }}>
289
+ <PrimaryButton>Invite user</PrimaryButton>
290
+ </Box>
291
+ </Skeleton>
292
+ </Grid>
293
+ <Table.Root variant="ghost" size="1">
294
+ <Table.Header>
295
+ <Table.Row>
296
+ <Table.ColumnHeaderCell width="260px">
297
+ <Skeleton loading>User</Skeleton>
298
+ </Table.ColumnHeaderCell>
299
+ <Table.ColumnHeaderCell width="100px">
300
+ <Skeleton>Role</Skeleton>
301
+ </Table.ColumnHeaderCell>
302
+ <Table.ColumnHeaderCell width="140px">
303
+ <Skeleton>Last active</Skeleton>
304
+ </Table.ColumnHeaderCell>
305
+ <Table.ColumnHeaderCell width="28px" />
306
+ </Table.Row>
307
+ </Table.Header>
308
+
309
+ <Table.Body>
310
+ {Array.from({ length: USER_ROW_LIMIT }, (_, index) => (
311
+ <Table.Row key={index} align="center">
312
+ <Table.RowHeaderCell>
313
+ <Flex align="center" gap="3">
314
+ <Skeleton>
315
+ <Avatar size="2" fallback="F" />
316
+ </Skeleton>
317
+
318
+ <Flex
319
+ direction="column"
320
+ height="var(--space-7)"
321
+ justify="center"
322
+ >
323
+ <Skeleton width="180px" height="var(--space-4)" />
324
+ <Skeleton width="90px" height="var(--space-3)" mt="1" />
325
+ </Flex>
326
+ </Flex>
327
+ </Table.RowHeaderCell>
328
+ <Table.Cell>
329
+ <Flex wrap="wrap" gap="1">
330
+ <Skeleton width="75px" height="var(--space-4)" />
331
+ </Flex>
332
+ </Table.Cell>
333
+ <Table.Cell>
334
+ <Skeleton width="120px" height="var(--space-4)" />
335
+ </Table.Cell>
336
+ <Table.Cell justify="end" />
337
+ </Table.Row>
338
+ ))}
339
+ </Table.Body>
340
+ </Table.Root>
341
+
342
+ <Flex gap="2" justify="end">
343
+ <Skeleton loading>
344
+ <SecondaryButton size="1">Previous</SecondaryButton>
345
+ </Skeleton>
346
+ <Skeleton loading>
347
+ <SecondaryButton size="1">Next</SecondaryButton>
348
+ </Skeleton>
349
+ </Flex>
350
+ </UsersManagementRoot>
351
+ );
352
+ }
353
+
354
+ export function UsersManagementError({ error }: { error: unknown }) {
355
+ React.useEffect(() => {
356
+ console.error(error);
357
+ }, [error]);
358
+
359
+ const render = (heading: string, message: React.ReactNode) => (
360
+ <UsersManagementRoot justify="center" align="center" minHeight="676px">
361
+ <Flex
362
+ p="8"
363
+ justify="center"
364
+ align="center"
365
+ direction="column"
366
+ maxWidth="520px"
367
+ >
368
+ <Heading size="5" align="center" mb="1" wrap="balance" asChild>
369
+ <h3>{heading}</h3>
370
+ </Heading>
371
+ <Text align="center" wrap="balance">
372
+ {message}
373
+ </Text>
374
+ </Flex>
375
+ </UsersManagementRoot>
376
+ );
377
+
378
+ if (error instanceof FetchError) {
379
+ return render(
380
+ "Error fetching data",
381
+ "An error occurred. You may need to configure CORS in the WorkOS Dashboard. " +
382
+ "Contact your organization admin for support.",
383
+ );
384
+ }
385
+
386
+ if (error instanceof NoAuthTokenError) {
387
+ return render(
388
+ "Error fetching data",
389
+ "Authorization error. You likely forgot to provide an authorization " +
390
+ "token to the Users Management Widget.",
391
+ );
392
+ }
393
+
394
+ if (error instanceof ApiError && error.status === 404) {
395
+ // The widgets API treats all authorization errors as 404s. If there is a
396
+ // legitimate 404, it's a bug on our end but there's currently no way to
397
+ // distinguish between the two.
398
+ return render(
399
+ "Error fetching data",
400
+ "Authorization error. Contact your organization admin for support.",
401
+ );
402
+ }
403
+
404
+ return render(
405
+ "Error fetching data",
406
+ "Unknown error. Contact your organization admin for support.",
407
+ );
408
+ }
409
+
410
+ function UsersManagementRoot({
411
+ className,
412
+ children,
413
+ ...props
414
+ }: React.ComponentProps<typeof Flex>) {
415
+ return (
416
+ <Flex
417
+ className={clsx("woswidgets-widget", className)}
418
+ data-woswidgets-widget-id="users-management"
419
+ direction="column"
420
+ gap="3"
421
+ {...props}
422
+ >
423
+ {children}
424
+ </Flex>
425
+ );
426
+ }
427
+
230
428
  function UserBadge({ user }: { user: User }) {
231
429
  // TODO: This is not yet available in the data. Update here after API is updated.
232
430
  if (user.isLoggedInUser) {
@@ -343,49 +541,6 @@ function LastActiveImpl({
343
541
  );
344
542
  }
345
543
 
346
- const SkeletonRows = ({
347
- length,
348
- alternate = false,
349
- }: {
350
- length: number;
351
- alternate?: boolean;
352
- }) => {
353
- return Array.from({ length }, (_, index) => {
354
- return (
355
- <Table.Row
356
- key={index}
357
- align="center"
358
- style={{
359
- background:
360
- alternate && index % 2 === 1 ? "var(--gray-a1)" : undefined,
361
- }}
362
- >
363
- <Table.RowHeaderCell>
364
- <Flex align="center" gap="3">
365
- <Skeleton>
366
- <Avatar size="2" fallback="F" />
367
- </Skeleton>
368
-
369
- <Flex direction="column" height="var(--space-7)" justify="center">
370
- <Skeleton width="180px" height="var(--space-4)" />
371
- <Skeleton width="90px" height="var(--space-3)" mt="1" />
372
- </Flex>
373
- </Flex>
374
- </Table.RowHeaderCell>
375
- <Table.Cell>
376
- <Flex wrap="wrap" gap="1">
377
- <Skeleton width="75px" height="var(--space-4)" />
378
- </Flex>
379
- </Table.Cell>
380
- <Table.Cell>
381
- <Skeleton width="120px" height="var(--space-4)" />
382
- </Table.Cell>
383
- <Table.Cell justify="end" />
384
- </Table.Row>
385
- );
386
- });
387
- };
388
-
389
544
  const TableCellText = React.forwardRef<HTMLSpanElement, TableCellTextProps>(
390
545
  function TableCellText(
391
546
  { children, dim, level = "primary", ...props },