@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
package/src/lib/utils.ts
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
import { Member } from "../api";
|
|
2
|
-
import { UAParser } from "ua-parser-js";
|
|
3
|
-
import { WIDGETS_CLASS_NAMESPACE } from "./constants";
|
|
4
|
-
|
|
5
|
-
export const canUseDOM = !!(
|
|
6
|
-
typeof window !== "undefined" &&
|
|
7
|
-
window.document &&
|
|
8
|
-
window.document.createElement
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
export function getBestName({
|
|
12
|
-
firstName,
|
|
13
|
-
lastName,
|
|
14
|
-
}: Pick<Member, "firstName" | "lastName">) {
|
|
15
|
-
return [firstName, lastName].filter(Boolean).join(" ") || null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function getComparativeReadableDate(
|
|
19
|
-
now: Date,
|
|
20
|
-
then: Date,
|
|
21
|
-
options?: { timeZone?: string },
|
|
22
|
-
): string {
|
|
23
|
-
const timeSince = now.getTime() - then.getTime();
|
|
24
|
-
|
|
25
|
-
// Has it been less than a minute?
|
|
26
|
-
if (timeSince < 60_000) {
|
|
27
|
-
return "Just now";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Has it been less than an hour?
|
|
31
|
-
if (timeSince < 3_600_000) {
|
|
32
|
-
const timePassed = Math.floor(timeSince / 60_000);
|
|
33
|
-
return timePassed === 1 ? "1 minute ago" : `${timePassed} minutes ago`;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Has it been less than a day?
|
|
37
|
-
if (timeSince < 86_400_000) {
|
|
38
|
-
const timePassed = Math.floor(timeSince / 3_600_000);
|
|
39
|
-
return timePassed === 1 ? "1 hour ago" : `${timePassed} hours ago`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Has it been less than a week?
|
|
43
|
-
if (timeSince < 604_800_000) {
|
|
44
|
-
const timePassed = Math.floor(timeSince / 86_400_000);
|
|
45
|
-
return timePassed === 1 ? "1 day ago" : `${timePassed} days ago`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Has it been less than a month?
|
|
49
|
-
if (timeSince < 2_592_000_000) {
|
|
50
|
-
const timePassed = Math.floor(timeSince / 604_800_000);
|
|
51
|
-
return timePassed === 1 ? "1 week ago" : `${timePassed} weeks ago`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Any later?
|
|
55
|
-
return then.toLocaleDateString("en-US", {
|
|
56
|
-
timeZone: options?.timeZone,
|
|
57
|
-
month: "long",
|
|
58
|
-
day: "numeric",
|
|
59
|
-
// omit year if it's the same as the current year
|
|
60
|
-
year: now.getFullYear() !== then.getFullYear() ? "numeric" : undefined,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function isObjectLike(value: unknown): value is Record<string, unknown> {
|
|
65
|
-
return typeof value === "object" && value !== null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function isErrorLike(
|
|
69
|
-
value: unknown,
|
|
70
|
-
): value is Record<string, unknown> & { message: string } {
|
|
71
|
-
return isObjectLike(value) && typeof value.message === "string";
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function parseErrorResponse(
|
|
75
|
-
response: Response,
|
|
76
|
-
): Promise<{ message: string; status: number }> {
|
|
77
|
-
try {
|
|
78
|
-
const json = await response.json();
|
|
79
|
-
if (!isObjectLike(json) || typeof json.message !== "string") {
|
|
80
|
-
return {
|
|
81
|
-
status: response.status,
|
|
82
|
-
message: response.statusText,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
...json,
|
|
87
|
-
status: response.status,
|
|
88
|
-
message: json.message || response.statusText,
|
|
89
|
-
};
|
|
90
|
-
} catch {
|
|
91
|
-
return {
|
|
92
|
-
status: response.status,
|
|
93
|
-
message: response.statusText,
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function namespaceClassNames(className: string): string;
|
|
99
|
-
export function namespaceClassNames(...classNames: string[]): string;
|
|
100
|
-
export function namespaceClassNames(...classNames: string[]): string {
|
|
101
|
-
return classNames
|
|
102
|
-
.map((className) => `${WIDGETS_CLASS_NAMESPACE}-${className}`)
|
|
103
|
-
.join(" ");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function parseUserAgent(userAgent?: string | null) {
|
|
107
|
-
const { browser, device, os } = UAParser(userAgent ?? "");
|
|
108
|
-
|
|
109
|
-
const browserName = browser.name?.replace(/^Mobile\s*/i, "");
|
|
110
|
-
const pretty = [browserName, os.name].filter(Boolean).join(" on ");
|
|
111
|
-
|
|
112
|
-
return { pretty, isMobile: device.type === "mobile" };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function getUserLocation(
|
|
116
|
-
location?: {
|
|
117
|
-
cityName: string;
|
|
118
|
-
countryISOCode: string;
|
|
119
|
-
} | null,
|
|
120
|
-
ipAddress?: string | null,
|
|
121
|
-
) {
|
|
122
|
-
if (location) {
|
|
123
|
-
return `${location.cityName}, ${location.countryISOCode}`;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (ipAddress) {
|
|
127
|
-
return ipAddress;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return "Unknown location";
|
|
131
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import { DEFAULT_API_HOSTNAME } from "./api/config";
|
|
3
|
-
import type { Elements } from "./elements";
|
|
4
|
-
|
|
5
|
-
export interface WidgetsContextType {
|
|
6
|
-
elements: Elements;
|
|
7
|
-
apiBaseUrl: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const WidgetsContext = React.createContext<WidgetsContextType | undefined>(
|
|
11
|
-
undefined,
|
|
12
|
-
);
|
|
13
|
-
WidgetsContext.displayName = "WidgetsContext";
|
|
14
|
-
export { WidgetsContext };
|
|
15
|
-
|
|
16
|
-
export function useWorkOsApiUrl() {
|
|
17
|
-
const context = React.useContext(WidgetsContext);
|
|
18
|
-
return context?.apiBaseUrl || `https://${DEFAULT_API_HOSTNAME}`;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useElement<K extends keyof Elements>(
|
|
22
|
-
key: K,
|
|
23
|
-
): NonNullable<Elements[K]> {
|
|
24
|
-
const context = React.useContext(WidgetsContext);
|
|
25
|
-
if (!context) {
|
|
26
|
-
throw new Error("useElement must be used within a WidgetsContext provider");
|
|
27
|
-
}
|
|
28
|
-
return context.elements[key] || {};
|
|
29
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
ApiProvider,
|
|
5
|
-
AuthToken,
|
|
6
|
-
OrganizationInfo,
|
|
7
|
-
useOrganizations,
|
|
8
|
-
} from "./api";
|
|
9
|
-
import * as React from "react";
|
|
10
|
-
import { ErrorBoundary } from "./lib/error-boundary";
|
|
11
|
-
import { useIsHydrated } from "./lib/use-is-hydrated";
|
|
12
|
-
import {
|
|
13
|
-
OrganizationSwitcherError,
|
|
14
|
-
OrganizationSwitcherLoading,
|
|
15
|
-
OrganizationSwitcherPassthroughProps,
|
|
16
|
-
OrganizationSwitcher as OrganizationSwitcherPresentational,
|
|
17
|
-
} from "./lib/organization-switcher";
|
|
18
|
-
import { useWorkOsApiUrl } from "./lib/widgets-context";
|
|
19
|
-
import { keepPreviousData } from "@tanstack/react-query";
|
|
20
|
-
|
|
21
|
-
export interface OrganizationSwitcherProps
|
|
22
|
-
extends OrganizationSwitcherPassthroughProps {
|
|
23
|
-
authToken: AuthToken;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const OrganizationSwitcher: React.FC<OrganizationSwitcherProps> = ({
|
|
27
|
-
authToken,
|
|
28
|
-
...passthroughProps
|
|
29
|
-
}) => {
|
|
30
|
-
const baseUrl = useWorkOsApiUrl();
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<ErrorBoundary FallbackComponent={OrganizationSwitcherError}>
|
|
34
|
-
<ApiProvider
|
|
35
|
-
widgetType="organization-switcher"
|
|
36
|
-
authToken={authToken}
|
|
37
|
-
baseUrl={baseUrl}
|
|
38
|
-
>
|
|
39
|
-
<OrganizationSwitcherContent {...passthroughProps} />
|
|
40
|
-
</ApiProvider>
|
|
41
|
-
</ErrorBoundary>
|
|
42
|
-
);
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// Constant reference to avoid unnecessary prop changes
|
|
46
|
-
const emptyOrganizations: OrganizationInfo[] = [];
|
|
47
|
-
|
|
48
|
-
const OrganizationSwitcherContent = ({
|
|
49
|
-
variant,
|
|
50
|
-
...rest
|
|
51
|
-
}: OrganizationSwitcherPassthroughProps) => {
|
|
52
|
-
const isHydrated = useIsHydrated();
|
|
53
|
-
const organizationsQuery = useOrganizations({
|
|
54
|
-
query: {
|
|
55
|
-
placeholderData: keepPreviousData,
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const organizations = organizationsQuery.data?.data ?? emptyOrganizations;
|
|
60
|
-
|
|
61
|
-
if (
|
|
62
|
-
organizationsQuery.isLoading ||
|
|
63
|
-
// render loading state on the server to prevent FOUC or hydration mismatch
|
|
64
|
-
!isHydrated
|
|
65
|
-
) {
|
|
66
|
-
return <OrganizationSwitcherLoading organizations={organizations} />;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (organizationsQuery.isError) {
|
|
70
|
-
return <OrganizationSwitcherError error={organizationsQuery.error} />;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<ErrorBoundary FallbackComponent={OrganizationSwitcherError}>
|
|
75
|
-
<OrganizationSwitcherPresentational
|
|
76
|
-
organizations={organizations}
|
|
77
|
-
{...rest}
|
|
78
|
-
/>
|
|
79
|
-
</ErrorBoundary>
|
|
80
|
-
);
|
|
81
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import {
|
|
5
|
-
UserProfileError,
|
|
6
|
-
UserProfileLoading,
|
|
7
|
-
UserProfile as UserProfilePresentational,
|
|
8
|
-
} from "./lib/user-profile";
|
|
9
|
-
import { useIsHydrated } from "./lib/use-is-hydrated";
|
|
10
|
-
import { ApiProvider, AuthToken, useMe } from "./api";
|
|
11
|
-
import { useWorkOsApiUrl } from "./lib/widgets-context";
|
|
12
|
-
import { ErrorBoundary } from "./lib/error-boundary";
|
|
13
|
-
|
|
14
|
-
export interface UserProfileProps {
|
|
15
|
-
authToken: AuthToken;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const UserProfile: React.FC<UserProfileProps> = ({ authToken }) => {
|
|
19
|
-
const baseUrl = useWorkOsApiUrl();
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<ApiProvider
|
|
23
|
-
widgetType="user-profile"
|
|
24
|
-
authToken={authToken}
|
|
25
|
-
baseUrl={baseUrl}
|
|
26
|
-
>
|
|
27
|
-
<UserProfileContent />
|
|
28
|
-
</ApiProvider>
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const UserProfileContent = () => {
|
|
33
|
-
const isHydrated = useIsHydrated();
|
|
34
|
-
const meQuery = useMe();
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
// render loading state on the server to prevent FOUC or hydration mismatch
|
|
38
|
-
!isHydrated ||
|
|
39
|
-
meQuery.isLoading
|
|
40
|
-
) {
|
|
41
|
-
return <UserProfileLoading />;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (meQuery.isError) {
|
|
45
|
-
return <UserProfileError error={meQuery.error} />;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const user = meQuery.data!;
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<ErrorBoundary FallbackComponent={UserProfileError}>
|
|
52
|
-
<UserProfilePresentational userData={user} />
|
|
53
|
-
</ErrorBoundary>
|
|
54
|
-
);
|
|
55
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { ApiProvider, AuthToken, useAuthenticationInformation } from "./api";
|
|
5
|
-
import {
|
|
6
|
-
UserSecurityError,
|
|
7
|
-
UserSecurityLoading,
|
|
8
|
-
UserSecurity as UserSecurityPresentational,
|
|
9
|
-
} from "./lib/user-security";
|
|
10
|
-
import { useIsHydrated } from "./lib/use-is-hydrated";
|
|
11
|
-
import { useWorkOsApiUrl } from "./lib/widgets-context";
|
|
12
|
-
import { ErrorBoundary } from "./lib/error-boundary";
|
|
13
|
-
|
|
14
|
-
export interface UserSecurityProps {
|
|
15
|
-
authToken: AuthToken;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const UserSecurity: React.FC<UserSecurityProps> = ({ authToken }) => {
|
|
19
|
-
const baseUrl = useWorkOsApiUrl();
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<ApiProvider
|
|
23
|
-
widgetType="user-security"
|
|
24
|
-
authToken={authToken}
|
|
25
|
-
baseUrl={baseUrl}
|
|
26
|
-
>
|
|
27
|
-
<UserSecurityContent />
|
|
28
|
-
</ApiProvider>
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const UserSecurityContent = () => {
|
|
33
|
-
const isHydrated = useIsHydrated();
|
|
34
|
-
const {
|
|
35
|
-
data: settings,
|
|
36
|
-
isLoading,
|
|
37
|
-
isError,
|
|
38
|
-
isSuccess,
|
|
39
|
-
error,
|
|
40
|
-
} = useAuthenticationInformation();
|
|
41
|
-
|
|
42
|
-
if (!isHydrated || isLoading) {
|
|
43
|
-
return <UserSecurityLoading />;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (isError || !isSuccess) {
|
|
47
|
-
return <UserSecurityError error={error} />;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<ErrorBoundary FallbackComponent={UserSecurityError}>
|
|
52
|
-
<UserSecurityPresentational settings={settings.data} />
|
|
53
|
-
</ErrorBoundary>
|
|
54
|
-
);
|
|
55
|
-
};
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import {
|
|
5
|
-
UserSessionsError,
|
|
6
|
-
UserSessionsLoading,
|
|
7
|
-
UserSessions as UserSessionsPresentational,
|
|
8
|
-
} from "./lib/user-sessions";
|
|
9
|
-
import { useIsHydrated } from "./lib/use-is-hydrated";
|
|
10
|
-
import { ApiProvider, useApi, useSessions } from "./api";
|
|
11
|
-
import { useWorkOsApiUrl } from "./lib/widgets-context";
|
|
12
|
-
import { ErrorBoundary } from "./lib/error-boundary";
|
|
13
|
-
import { useQuery } from "@tanstack/react-query";
|
|
14
|
-
|
|
15
|
-
type UserSessionsWithoutCurrentSessionIdProps = {
|
|
16
|
-
authToken: () => Promise<string>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
type UserSessionsWithCurrentSessionIdProps = {
|
|
20
|
-
authToken: string;
|
|
21
|
-
currentSessionId: string;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
type UserSessionsProps =
|
|
25
|
-
| UserSessionsWithoutCurrentSessionIdProps
|
|
26
|
-
| UserSessionsWithCurrentSessionIdProps;
|
|
27
|
-
|
|
28
|
-
export const UserSessions: React.FC<UserSessionsProps> = (props) => {
|
|
29
|
-
const baseUrl = useWorkOsApiUrl();
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<ApiProvider
|
|
33
|
-
widgetType="user-sessions"
|
|
34
|
-
authToken={props.authToken}
|
|
35
|
-
baseUrl={baseUrl}
|
|
36
|
-
>
|
|
37
|
-
<UserSessionsContent
|
|
38
|
-
currentSessionId={
|
|
39
|
-
"currentSessionId" in props ? props.currentSessionId : undefined
|
|
40
|
-
}
|
|
41
|
-
/>
|
|
42
|
-
</ApiProvider>
|
|
43
|
-
);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const UserSessionsContent = ({
|
|
47
|
-
currentSessionId,
|
|
48
|
-
}: {
|
|
49
|
-
currentSessionId?: string;
|
|
50
|
-
}) => {
|
|
51
|
-
const isHydrated = useIsHydrated();
|
|
52
|
-
const sessionsQuery = useSessions();
|
|
53
|
-
const currentSessionIdQuery = useCurrentSessionIdQuery(currentSessionId);
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
!isHydrated ||
|
|
57
|
-
sessionsQuery.isLoading ||
|
|
58
|
-
currentSessionIdQuery.isLoading
|
|
59
|
-
) {
|
|
60
|
-
return <UserSessionsLoading />;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (sessionsQuery.isError || currentSessionIdQuery.isError) {
|
|
64
|
-
return (
|
|
65
|
-
<UserSessionsError
|
|
66
|
-
error={sessionsQuery.error || currentSessionIdQuery.error}
|
|
67
|
-
/>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<ErrorBoundary FallbackComponent={UserSessionsError}>
|
|
73
|
-
<UserSessionsPresentational
|
|
74
|
-
sessionsData={sessionsQuery.data?.data || []}
|
|
75
|
-
currentSessionId={currentSessionIdQuery.data ?? ""}
|
|
76
|
-
/>
|
|
77
|
-
</ErrorBoundary>
|
|
78
|
-
);
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
function useCurrentSessionIdQuery(currentSessionId?: string) {
|
|
82
|
-
const { authToken } = useApi();
|
|
83
|
-
|
|
84
|
-
return useQuery({
|
|
85
|
-
queryKey: ["authToken"],
|
|
86
|
-
queryFn: async () => {
|
|
87
|
-
if (currentSessionId) {
|
|
88
|
-
return currentSessionId;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const res = await Promise.resolve(authToken);
|
|
92
|
-
const claims = getClaims(res);
|
|
93
|
-
return claims.sid;
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function getClaims(accessToken: string) {
|
|
99
|
-
return JSON.parse(atob(accessToken.split(".")[1])) as { sid: string };
|
|
100
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { ApiProvider, AuthToken, useRoles } from "./api";
|
|
4
|
-
import * as React from "react";
|
|
5
|
-
import { useUsers } from "./lib/api/user";
|
|
6
|
-
import { useIsHydrated } from "./lib/use-is-hydrated";
|
|
7
|
-
import {
|
|
8
|
-
UsersManagementError,
|
|
9
|
-
UsersManagementLoading,
|
|
10
|
-
UsersManagement as UsersManagementPresentational,
|
|
11
|
-
} from "./lib/users-management";
|
|
12
|
-
import { UsersManagementContextProvider } from "./lib/users-management-context";
|
|
13
|
-
import { ErrorBoundary } from "./lib/error-boundary";
|
|
14
|
-
import { useWorkOsApiUrl } from "./lib/widgets-context";
|
|
15
|
-
|
|
16
|
-
export interface UsersManagementProps {
|
|
17
|
-
authToken: AuthToken;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export const UsersManagement: React.FC<UsersManagementProps> = ({
|
|
21
|
-
authToken,
|
|
22
|
-
}) => {
|
|
23
|
-
const baseUrl = useWorkOsApiUrl();
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<ErrorBoundary FallbackComponent={UsersManagementError}>
|
|
27
|
-
<ApiProvider
|
|
28
|
-
widgetType="user-management"
|
|
29
|
-
authToken={authToken}
|
|
30
|
-
baseUrl={baseUrl}
|
|
31
|
-
>
|
|
32
|
-
<UsersManagementContextProvider>
|
|
33
|
-
<UsersManagementContent />
|
|
34
|
-
</UsersManagementContextProvider>
|
|
35
|
-
</ApiProvider>
|
|
36
|
-
</ErrorBoundary>
|
|
37
|
-
);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const UsersManagementContent = () => {
|
|
41
|
-
const isHydrated = useIsHydrated();
|
|
42
|
-
const rolesQuery = useRoles({
|
|
43
|
-
query: { initialData: [] },
|
|
44
|
-
});
|
|
45
|
-
const usersQuery = useUsers();
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
usersQuery.isLoading ||
|
|
49
|
-
rolesQuery.isLoading ||
|
|
50
|
-
usersQuery.isPending ||
|
|
51
|
-
// render loading state on the server to prevent FOUC or hydration mismatch
|
|
52
|
-
!isHydrated
|
|
53
|
-
) {
|
|
54
|
-
return <UsersManagementLoading />;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (usersQuery.isError || rolesQuery.isError) {
|
|
58
|
-
return (
|
|
59
|
-
<UsersManagementError error={usersQuery.error || rolesQuery.isError} />
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return (
|
|
64
|
-
<ErrorBoundary FallbackComponent={UsersManagementError}>
|
|
65
|
-
<UsersManagementPresentational
|
|
66
|
-
userData={usersQuery.data}
|
|
67
|
-
rolesData={rolesQuery.data}
|
|
68
|
-
isPending={usersQuery.isFetching}
|
|
69
|
-
disableRolesFilter={rolesQuery.isPending || rolesQuery.isFetching}
|
|
70
|
-
/>
|
|
71
|
-
</ErrorBoundary>
|
|
72
|
-
);
|
|
73
|
-
};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Theme as RadixThemes } from "@radix-ui/themes";
|
|
4
|
-
import type { GetPropDefTypes, themePropDefs } from "@radix-ui/themes/props";
|
|
5
|
-
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
6
|
-
import cx from "clsx";
|
|
7
|
-
import * as React from "react";
|
|
8
|
-
import { namespaceClassNames } from "./lib/utils";
|
|
9
|
-
import { DEFAULT_API_HOSTNAME } from "./lib/api/config";
|
|
10
|
-
import type { Elements } from "./lib/elements";
|
|
11
|
-
import { WidgetsContext } from "./lib/widgets-context";
|
|
12
|
-
|
|
13
|
-
const queryClient = new QueryClient();
|
|
14
|
-
|
|
15
|
-
export interface WorkOsWidgetsProps {
|
|
16
|
-
elements?: Elements;
|
|
17
|
-
theme?: Omit<GetPropDefTypes<typeof themePropDefs>, "asChild"> & {
|
|
18
|
-
fontFamily?: string;
|
|
19
|
-
};
|
|
20
|
-
style?: React.CSSProperties;
|
|
21
|
-
className?: string;
|
|
22
|
-
children: React.ReactNode;
|
|
23
|
-
apiHostname?: string;
|
|
24
|
-
port?: number | null;
|
|
25
|
-
https?: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export const WorkOsWidgets: React.FC<WorkOsWidgetsProps> = ({
|
|
29
|
-
elements = {},
|
|
30
|
-
className,
|
|
31
|
-
theme: { fontFamily, ...radixThemesProps } = {},
|
|
32
|
-
style = {},
|
|
33
|
-
children,
|
|
34
|
-
apiHostname = DEFAULT_API_HOSTNAME,
|
|
35
|
-
port = null,
|
|
36
|
-
https = true,
|
|
37
|
-
}) => {
|
|
38
|
-
const customStyle = (
|
|
39
|
-
fontFamily
|
|
40
|
-
? {
|
|
41
|
-
"--default-font-family": fontFamily,
|
|
42
|
-
...style,
|
|
43
|
-
}
|
|
44
|
-
: style
|
|
45
|
-
) as React.CSSProperties;
|
|
46
|
-
|
|
47
|
-
const apiBaseUrl = React.useMemo(() => {
|
|
48
|
-
try {
|
|
49
|
-
const url = new URL(
|
|
50
|
-
"/",
|
|
51
|
-
`${https ? "https" : "http"}://${apiHostname}${port ? `:${port}` : ""}`,
|
|
52
|
-
);
|
|
53
|
-
return url.toString().slice(0, -1);
|
|
54
|
-
} catch {
|
|
55
|
-
console.error(
|
|
56
|
-
"Failed to validate the WorkOS API URL. Check to ensure that valid `apiHostname` and `port` props are passed to WorkOsWidgets",
|
|
57
|
-
);
|
|
58
|
-
return `https://${DEFAULT_API_HOSTNAME}`;
|
|
59
|
-
}
|
|
60
|
-
}, [apiHostname, https, port]);
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<QueryClientProvider client={queryClient}>
|
|
64
|
-
<RadixThemes
|
|
65
|
-
{...radixThemesProps}
|
|
66
|
-
style={customStyle}
|
|
67
|
-
className={cx(namespaceClassNames("root"), className)}
|
|
68
|
-
>
|
|
69
|
-
<WidgetsContext.Provider value={{ elements, apiBaseUrl }}>
|
|
70
|
-
{children}
|
|
71
|
-
</WidgetsContext.Provider>
|
|
72
|
-
</RadixThemes>
|
|
73
|
-
</QueryClientProvider>
|
|
74
|
-
);
|
|
75
|
-
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|