@authhero/react-admin 0.10.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/.eslintrc.js +21 -0
- package/.vercelignore +4 -0
- package/CHANGELOG.md +56 -0
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/index.html +125 -0
- package/package.json +61 -0
- package/prettier.config.js +1 -0
- package/public/favicon.ico +0 -0
- package/public/manifest.json +15 -0
- package/src/App.spec.tsx +42 -0
- package/src/App.tsx +232 -0
- package/src/AuthCallback.tsx +138 -0
- package/src/Layout.tsx +12 -0
- package/src/TenantsApp.tsx +115 -0
- package/src/auth0DataProvider.ts +1242 -0
- package/src/authProvider.ts +521 -0
- package/src/components/CertificateErrorDialog.tsx +116 -0
- package/src/components/DomainSelector.tsx +401 -0
- package/src/components/TenantAppBar.tsx +83 -0
- package/src/components/TenantLayout.tsx +25 -0
- package/src/components/TenantsAppBar.tsx +21 -0
- package/src/components/TenantsLayout.tsx +28 -0
- package/src/components/activity/ActivityDashboard.tsx +381 -0
- package/src/components/activity/index.ts +1 -0
- package/src/components/branding/BrandingList.tsx +0 -0
- package/src/components/branding/BrandingShow.tsx +0 -0
- package/src/components/branding/ThemesTab.tsx +286 -0
- package/src/components/branding/edit.tsx +149 -0
- package/src/components/branding/hooks/useThemesData.ts +123 -0
- package/src/components/branding/index.ts +2 -0
- package/src/components/branding/list.tsx +12 -0
- package/src/components/clients/create.tsx +12 -0
- package/src/components/clients/edit.tsx +1285 -0
- package/src/components/clients/index.ts +3 -0
- package/src/components/clients/list.tsx +37 -0
- package/src/components/common/DateAgo.tsx +6 -0
- package/src/components/common/JsonOutput.tsx +26 -0
- package/src/components/common/index.ts +1 -0
- package/src/components/connections/create.tsx +35 -0
- package/src/components/connections/edit.tsx +212 -0
- package/src/components/connections/index.ts +3 -0
- package/src/components/connections/list.tsx +15 -0
- package/src/components/custom-domains/create.tsx +26 -0
- package/src/components/custom-domains/edit.tsx +101 -0
- package/src/components/custom-domains/index.ts +3 -0
- package/src/components/custom-domains/list.tsx +16 -0
- package/src/components/flows/create.tsx +30 -0
- package/src/components/flows/edit.tsx +238 -0
- package/src/components/flows/index.ts +3 -0
- package/src/components/flows/list.tsx +15 -0
- package/src/components/forms/FlowEditor.tsx +1363 -0
- package/src/components/forms/NodeEditor.tsx +1119 -0
- package/src/components/forms/RichTextEditor.tsx +145 -0
- package/src/components/forms/create.tsx +30 -0
- package/src/components/forms/edit.tsx +256 -0
- package/src/components/forms/index.ts +3 -0
- package/src/components/forms/list.tsx +16 -0
- package/src/components/hooks/create.tsx +96 -0
- package/src/components/hooks/edit.tsx +114 -0
- package/src/components/hooks/index.ts +3 -0
- package/src/components/hooks/list.tsx +17 -0
- package/src/components/listActions/PostListActions.tsx +10 -0
- package/src/components/logs/LogIcon.tsx +32 -0
- package/src/components/logs/LogShow.tsx +82 -0
- package/src/components/logs/LogType.tsx +38 -0
- package/src/components/logs/index.ts +4 -0
- package/src/components/logs/list.tsx +41 -0
- package/src/components/organizations/create.tsx +13 -0
- package/src/components/organizations/edit.tsx +682 -0
- package/src/components/organizations/index.ts +3 -0
- package/src/components/organizations/list.tsx +21 -0
- package/src/components/resource-servers/create.tsx +87 -0
- package/src/components/resource-servers/edit.tsx +121 -0
- package/src/components/resource-servers/index.ts +3 -0
- package/src/components/resource-servers/list.tsx +47 -0
- package/src/components/roles/create.tsx +12 -0
- package/src/components/roles/edit.tsx +426 -0
- package/src/components/roles/index.ts +3 -0
- package/src/components/roles/list.tsx +24 -0
- package/src/components/sessions/edit.tsx +101 -0
- package/src/components/sessions/index.ts +3 -0
- package/src/components/sessions/list.tsx +20 -0
- package/src/components/sessions/show.tsx +113 -0
- package/src/components/settings/edit.tsx +236 -0
- package/src/components/settings/index.ts +2 -0
- package/src/components/settings/list.tsx +14 -0
- package/src/components/tenants/create.tsx +20 -0
- package/src/components/tenants/edit.tsx +54 -0
- package/src/components/tenants/index.ts +2 -0
- package/src/components/tenants/list.tsx +67 -0
- package/src/components/themes/edit.tsx +200 -0
- package/src/components/themes/index.ts +2 -0
- package/src/components/themes/list.tsx +12 -0
- package/src/components/users/create.tsx +144 -0
- package/src/components/users/edit.tsx +1711 -0
- package/src/components/users/index.ts +3 -0
- package/src/components/users/list.tsx +35 -0
- package/src/data.json +121 -0
- package/src/dataProvider.ts +97 -0
- package/src/index.tsx +106 -0
- package/src/lib/logs.ts +21 -0
- package/src/types/reactflow.d.ts +86 -0
- package/src/utils/domainUtils.ts +169 -0
- package/src/utils/tokenUtils.ts +75 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +37 -0
- package/tsconfig.node.json +10 -0
- package/vercel.json +17 -0
- package/vite.config.ts +30 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
List,
|
|
3
|
+
Datagrid,
|
|
4
|
+
TextField,
|
|
5
|
+
EmailField,
|
|
6
|
+
TextInput,
|
|
7
|
+
FunctionField,
|
|
8
|
+
} from "react-admin";
|
|
9
|
+
import { PostListActions } from "../listActions/PostListActions";
|
|
10
|
+
import { DateAgo } from "../common";
|
|
11
|
+
|
|
12
|
+
export function UsersList() {
|
|
13
|
+
const postFilters = [
|
|
14
|
+
<TextInput key="search" label="Search" source="q" alwaysOn />,
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<List
|
|
19
|
+
actions={<PostListActions />}
|
|
20
|
+
filters={postFilters}
|
|
21
|
+
sort={{ field: "user_id", order: "DESC" }}
|
|
22
|
+
>
|
|
23
|
+
<Datagrid rowClick="edit" bulkActionButtons={false}>
|
|
24
|
+
<EmailField source="email" />
|
|
25
|
+
<TextField source="phone_number" />
|
|
26
|
+
<TextField source="connection" />
|
|
27
|
+
<TextField source="login_count" />
|
|
28
|
+
<FunctionField
|
|
29
|
+
label="Last login"
|
|
30
|
+
render={(record: any) => <DateAgo date={record.last_login} />}
|
|
31
|
+
/>
|
|
32
|
+
</Datagrid>
|
|
33
|
+
</List>
|
|
34
|
+
);
|
|
35
|
+
}
|
package/src/data.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"posts": [
|
|
3
|
+
{
|
|
4
|
+
"id": 0,
|
|
5
|
+
"title": "Post 1",
|
|
6
|
+
"content": "Content 1"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"id": 1,
|
|
10
|
+
"title": "Post 2",
|
|
11
|
+
"content": "Content 2"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": 2,
|
|
15
|
+
"title": "Post 3",
|
|
16
|
+
"content": "Content 3"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 3,
|
|
20
|
+
"title": "Post 4",
|
|
21
|
+
"content": "Content 4"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": 4,
|
|
25
|
+
"title": "Post 5",
|
|
26
|
+
"content": "Content 5"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": 5,
|
|
30
|
+
"title": "Post 6",
|
|
31
|
+
"content": "Content 6"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": 6,
|
|
35
|
+
"title": "Post 7",
|
|
36
|
+
"content": "Content 7"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": 7,
|
|
40
|
+
"title": "Post 8",
|
|
41
|
+
"content": "Content 8"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": 8,
|
|
45
|
+
"title": "Post 9",
|
|
46
|
+
"content": "Content 9"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": 9,
|
|
50
|
+
"title": "Post 10",
|
|
51
|
+
"content": "Content 10"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": 10,
|
|
55
|
+
"title": "Post 11",
|
|
56
|
+
"content": "Content 11"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": 11,
|
|
60
|
+
"title": "Post 12",
|
|
61
|
+
"content": "Content 12"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"comments": [
|
|
65
|
+
{
|
|
66
|
+
"id": 0,
|
|
67
|
+
"postId": 0,
|
|
68
|
+
"content": "Comment 1"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": 1,
|
|
72
|
+
"postId": 0,
|
|
73
|
+
"content": "Comment 2"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": 2,
|
|
77
|
+
"postId": 1,
|
|
78
|
+
"content": "Comment 3"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": 3,
|
|
82
|
+
"postId": 1,
|
|
83
|
+
"content": "Comment 4"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": 4,
|
|
87
|
+
"postId": 2,
|
|
88
|
+
"content": "Comment 5"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"id": 5,
|
|
92
|
+
"postId": 2,
|
|
93
|
+
"content": "Comment 6"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": 6,
|
|
97
|
+
"postId": 3,
|
|
98
|
+
"content": "Comment 7"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": 7,
|
|
102
|
+
"postId": 3,
|
|
103
|
+
"content": "Comment 8"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": 8,
|
|
107
|
+
"postId": 3,
|
|
108
|
+
"content": "Comment 9"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": 9,
|
|
112
|
+
"postId": 4,
|
|
113
|
+
"content": "Comment 10"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": 10,
|
|
117
|
+
"postId": 4,
|
|
118
|
+
"content": "Comment 11"
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { UpdateParams, withLifecycleCallbacks } from "react-admin";
|
|
2
|
+
import { authorizedHttpClient } from "./authProvider";
|
|
3
|
+
import auth0DataProvider from "./auth0DataProvider";
|
|
4
|
+
import {
|
|
5
|
+
getDomainFromStorage,
|
|
6
|
+
buildUrlWithProtocol,
|
|
7
|
+
} from "./utils/domainUtils";
|
|
8
|
+
|
|
9
|
+
async function removeExtraFields(params: UpdateParams) {
|
|
10
|
+
delete params.data?.id;
|
|
11
|
+
delete params.data?.tenant_id;
|
|
12
|
+
delete params.data?.updated_at;
|
|
13
|
+
delete params.data?.created_at;
|
|
14
|
+
|
|
15
|
+
// Remove empty properties
|
|
16
|
+
Object.keys(params.data).forEach((key) => {
|
|
17
|
+
if (params.data[key] === undefined) {
|
|
18
|
+
delete params.data[key];
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return params;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getDataprovider(auth0Domain?: string) {
|
|
26
|
+
// Create the complete base URL using the selected domain
|
|
27
|
+
let baseUrl = import.meta.env.VITE_SIMPLE_REST_URL;
|
|
28
|
+
|
|
29
|
+
if (auth0Domain) {
|
|
30
|
+
// Check if there's a custom REST API URL configured for this domain
|
|
31
|
+
const domains = getDomainFromStorage();
|
|
32
|
+
const domainConfig = domains.find((d) => d.url === auth0Domain);
|
|
33
|
+
|
|
34
|
+
if (domainConfig?.restApiUrl) {
|
|
35
|
+
// Use the custom REST API URL if configured
|
|
36
|
+
baseUrl = domainConfig.restApiUrl;
|
|
37
|
+
} else {
|
|
38
|
+
// Otherwise use the auth domain with HTTPS
|
|
39
|
+
baseUrl = buildUrlWithProtocol(auth0Domain);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// TODO - duplicate auth0DataProvider to tenantsDataProvider
|
|
44
|
+
// we are introducing non-auth0 endpoints AND we don't require the tenants-id header
|
|
45
|
+
const provider = auth0DataProvider(
|
|
46
|
+
baseUrl,
|
|
47
|
+
authorizedHttpClient,
|
|
48
|
+
undefined,
|
|
49
|
+
auth0Domain,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return withLifecycleCallbacks(provider, [
|
|
53
|
+
{
|
|
54
|
+
resource: "tenants",
|
|
55
|
+
beforeUpdate: removeExtraFields,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function getDataproviderForTenant(
|
|
61
|
+
tenantId: string,
|
|
62
|
+
auth0Domain?: string,
|
|
63
|
+
) {
|
|
64
|
+
// Start with a default API URL
|
|
65
|
+
let apiUrl;
|
|
66
|
+
|
|
67
|
+
if (auth0Domain) {
|
|
68
|
+
// Check if there's a custom REST API URL configured for this domain
|
|
69
|
+
const domains = getDomainFromStorage();
|
|
70
|
+
|
|
71
|
+
const domainConfig = domains.find((d) => d.url === auth0Domain);
|
|
72
|
+
|
|
73
|
+
if (domainConfig?.restApiUrl) {
|
|
74
|
+
// Use the custom REST API URL if configured
|
|
75
|
+
apiUrl = domainConfig.restApiUrl;
|
|
76
|
+
} else {
|
|
77
|
+
// Otherwise construct an API URL using the auth0Domain with HTTPS
|
|
78
|
+
apiUrl = buildUrlWithProtocol(auth0Domain);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Fallback to the environment variable
|
|
82
|
+
apiUrl = import.meta.env.VITE_AUTH0_API_URL;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Ensure apiUrl doesn't end with a slash
|
|
86
|
+
apiUrl = apiUrl.replace(/\/$/, "");
|
|
87
|
+
|
|
88
|
+
// Create the auth0Provider with the API URL, tenant ID, and domain
|
|
89
|
+
const auth0Provider = auth0DataProvider(
|
|
90
|
+
apiUrl,
|
|
91
|
+
authorizedHttpClient,
|
|
92
|
+
tenantId,
|
|
93
|
+
auth0Domain,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return auth0Provider;
|
|
97
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React, { useState, useEffect } from "react";
|
|
2
|
+
import ReactDOM from "react-dom/client";
|
|
3
|
+
import { BrowserRouter } from "react-router-dom";
|
|
4
|
+
import { App } from "./App";
|
|
5
|
+
import { TenantsApp } from "./TenantsApp";
|
|
6
|
+
import { AuthCallback } from "./AuthCallback";
|
|
7
|
+
import { DomainSelector } from "./components/DomainSelector";
|
|
8
|
+
import { getSelectedDomainFromStorage } from "./utils/domainUtils";
|
|
9
|
+
|
|
10
|
+
// Check if running on local.authhero.net - if so, auto-connect to localhost:3000
|
|
11
|
+
const isLocalDevelopment = window.location.hostname.startsWith("local.");
|
|
12
|
+
const LOCAL_DOMAIN = "localhost:3000";
|
|
13
|
+
|
|
14
|
+
function Root() {
|
|
15
|
+
const [selectedDomain, setSelectedDomain] = useState<string | null>(
|
|
16
|
+
isLocalDevelopment ? LOCAL_DOMAIN : null,
|
|
17
|
+
);
|
|
18
|
+
const currentPath = location.pathname;
|
|
19
|
+
const isAuthCallback = currentPath === "/auth-callback";
|
|
20
|
+
const isRootPath = currentPath === "/";
|
|
21
|
+
// Only match /tenants exactly or /tenants/create (not /tenants/:id which would be a tenant admin route)
|
|
22
|
+
const isTenantsPath =
|
|
23
|
+
currentPath === "/tenants" ||
|
|
24
|
+
currentPath.startsWith("/tenants/create") ||
|
|
25
|
+
currentPath === "/tenants/";
|
|
26
|
+
|
|
27
|
+
// Load domain from cookies on component mount (skip for local development)
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (isLocalDevelopment) {
|
|
30
|
+
// For local development, always use localhost:3000
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const savedDomain = getSelectedDomainFromStorage();
|
|
34
|
+
if (savedDomain) {
|
|
35
|
+
setSelectedDomain(savedDomain);
|
|
36
|
+
}
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
// Handle auth callback separately without basename
|
|
40
|
+
if (isAuthCallback) {
|
|
41
|
+
return (
|
|
42
|
+
<React.StrictMode>
|
|
43
|
+
<BrowserRouter>
|
|
44
|
+
<AuthCallback onAuthComplete={() => {}} />
|
|
45
|
+
</BrowserRouter>
|
|
46
|
+
</React.StrictMode>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Show domain selector on root path or if no domain is selected
|
|
51
|
+
// Skip for local development - redirect to /tenants instead
|
|
52
|
+
if (!isLocalDevelopment && (isRootPath || !selectedDomain)) {
|
|
53
|
+
return (
|
|
54
|
+
<DomainSelector
|
|
55
|
+
onDomainSelected={(domain) => setSelectedDomain(domain)}
|
|
56
|
+
disableCloseOnRootPath={isRootPath}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// For local development on root path, redirect to /tenants
|
|
62
|
+
if (isLocalDevelopment && isRootPath) {
|
|
63
|
+
window.location.href = "/tenants";
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Handle tenants management routes without basename
|
|
68
|
+
if (isTenantsPath) {
|
|
69
|
+
return (
|
|
70
|
+
<React.StrictMode>
|
|
71
|
+
<BrowserRouter>
|
|
72
|
+
<TenantsApp initialDomain={selectedDomain || ""} />
|
|
73
|
+
</BrowserRouter>
|
|
74
|
+
</React.StrictMode>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Handle tenant-specific routes
|
|
79
|
+
const pathSegments = currentPath.split("/").filter(Boolean);
|
|
80
|
+
const tenantId = pathSegments[0];
|
|
81
|
+
|
|
82
|
+
if (tenantId) {
|
|
83
|
+
return (
|
|
84
|
+
<React.StrictMode>
|
|
85
|
+
<BrowserRouter basename={`/${tenantId}`}>
|
|
86
|
+
<App tenantId={tenantId} initialDomain={selectedDomain || ""} />
|
|
87
|
+
</BrowserRouter>
|
|
88
|
+
</React.StrictMode>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fallback to domain selector (or redirect for local development)
|
|
93
|
+
if (isLocalDevelopment) {
|
|
94
|
+
window.location.href = "/tenants";
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<DomainSelector
|
|
100
|
+
onDomainSelected={(domain) => setSelectedDomain(domain)}
|
|
101
|
+
disableCloseOnRootPath={false}
|
|
102
|
+
/>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(<Root />);
|
package/src/lib/logs.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Subset of LogTypes used in react-admin app
|
|
2
|
+
// Values match those defined in @authhero/adapter-interfaces
|
|
3
|
+
export const LogTypes = {
|
|
4
|
+
SUCCESS_API_OPERATION: "sapi",
|
|
5
|
+
SUCCESS_SILENT_AUTH: "ssa",
|
|
6
|
+
FAILED_SILENT_AUTH: "fsa",
|
|
7
|
+
SUCCESS_SIGNUP: "ss",
|
|
8
|
+
FAILED_SIGNUP: "fs",
|
|
9
|
+
SUCCESS_LOGIN: "s",
|
|
10
|
+
FAILED_LOGIN_INCORRECT_PASSWORD: "fp",
|
|
11
|
+
FAILED_LOGIN_INVALID_EMAIL_USERNAME: "fu",
|
|
12
|
+
SUCCESS_LOGOUT: "slo",
|
|
13
|
+
SUCCESS_CROSS_ORIGIN_AUTHENTICATION: "scoa",
|
|
14
|
+
FAILED_CROSS_ORIGIN_AUTHENTICATION: "fcoa",
|
|
15
|
+
CODE_LINK_SENT: "cls", // Updated to match the main schema
|
|
16
|
+
FAILED_LOGIN: "f",
|
|
17
|
+
SUCCESS_EXCHANGE_AUTHORIZATION_CODE_FOR_ACCESS_TOKEN: "seacft",
|
|
18
|
+
FAILED_EXCHANGE_AUTHORIZATION_CODE_FOR_ACCESS_TOKEN: "feacft",
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export type LogTypes = (typeof LogTypes)[keyof typeof LogTypes];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
declare module "reactflow" {
|
|
2
|
+
export interface NodeProps<T = Record<string, unknown>> {
|
|
3
|
+
id: string;
|
|
4
|
+
data: T;
|
|
5
|
+
position: { x: number; y: number };
|
|
6
|
+
type?: string;
|
|
7
|
+
className?: string;
|
|
8
|
+
style?: React.CSSProperties;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface Connection {
|
|
12
|
+
source: string;
|
|
13
|
+
target: string;
|
|
14
|
+
sourceHandle?: string;
|
|
15
|
+
targetHandle?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface EdgeProps {
|
|
19
|
+
id: string;
|
|
20
|
+
source: string;
|
|
21
|
+
target: string;
|
|
22
|
+
type?: string;
|
|
23
|
+
label?: string;
|
|
24
|
+
animated?: boolean;
|
|
25
|
+
style?: React.CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface NodeChange {
|
|
29
|
+
id: string;
|
|
30
|
+
type: string;
|
|
31
|
+
position?: { x: number; y: number };
|
|
32
|
+
// Add other properties as needed
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface EdgeChange {
|
|
36
|
+
id: string;
|
|
37
|
+
type: string;
|
|
38
|
+
// Add other properties as needed
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type Node = NodeProps;
|
|
42
|
+
export type Edge = EdgeProps;
|
|
43
|
+
|
|
44
|
+
export enum ConnectionLineType {
|
|
45
|
+
Bezier = "bezier",
|
|
46
|
+
Step = "step",
|
|
47
|
+
SmoothStep = "smoothstep",
|
|
48
|
+
Straight = "straight",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function applyNodeChanges(
|
|
52
|
+
changes: NodeChange[],
|
|
53
|
+
nodes: Node[],
|
|
54
|
+
): Node[];
|
|
55
|
+
export function applyEdgeChanges(
|
|
56
|
+
changes: EdgeChange[],
|
|
57
|
+
edges: Edge[],
|
|
58
|
+
): Edge[];
|
|
59
|
+
|
|
60
|
+
// Define ReactFlow component
|
|
61
|
+
export interface ReactFlowProps {
|
|
62
|
+
nodes: Node[];
|
|
63
|
+
edges: Edge[];
|
|
64
|
+
onNodesChange?: (changes: NodeChange[]) => void;
|
|
65
|
+
onEdgesChange?: (changes: EdgeChange[]) => void;
|
|
66
|
+
onConnect?: (connection: Connection) => void;
|
|
67
|
+
nodeTypes?: Record<string, React.ComponentType<any>>;
|
|
68
|
+
edgeTypes?: Record<string, React.ComponentType<any>>;
|
|
69
|
+
connectionLineType?: ConnectionLineType;
|
|
70
|
+
fitView?: boolean;
|
|
71
|
+
children?: React.ReactNode;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Export ReactFlowProvider component
|
|
75
|
+
export const ReactFlowProvider: React.FC<{ children: React.ReactNode }>;
|
|
76
|
+
|
|
77
|
+
// Export the Controls component
|
|
78
|
+
export const Controls: React.FC<any>;
|
|
79
|
+
|
|
80
|
+
// Export the Background component
|
|
81
|
+
export const Background: React.FC<any>;
|
|
82
|
+
|
|
83
|
+
// Default export
|
|
84
|
+
const ReactFlow: React.FC<ReactFlowProps>;
|
|
85
|
+
export default ReactFlow;
|
|
86
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utility functions for domain management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Storage key constants
|
|
6
|
+
export const DOMAINS_STORAGE_KEY = "authhero_domains";
|
|
7
|
+
export const SELECTED_DOMAIN_STORAGE_KEY = "authhero_selected_domain";
|
|
8
|
+
|
|
9
|
+
// Connection method types
|
|
10
|
+
export type ConnectionMethod = "login" | "token" | "client_credentials";
|
|
11
|
+
|
|
12
|
+
// Domain configuration interface
|
|
13
|
+
export interface DomainConfig {
|
|
14
|
+
url: string;
|
|
15
|
+
connectionMethod: ConnectionMethod;
|
|
16
|
+
// Login method fields
|
|
17
|
+
clientId?: string;
|
|
18
|
+
restApiUrl?: string;
|
|
19
|
+
// Token method field
|
|
20
|
+
token?: string;
|
|
21
|
+
// Client credentials method fields
|
|
22
|
+
clientSecret?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Gets domains from localStorage
|
|
27
|
+
* Handles both formats (array of objects or array of strings) for backward compatibility
|
|
28
|
+
*/
|
|
29
|
+
export const getDomainFromStorage = (): DomainConfig[] => {
|
|
30
|
+
try {
|
|
31
|
+
const storedValue = localStorage.getItem(DOMAINS_STORAGE_KEY);
|
|
32
|
+
if (!storedValue) return [];
|
|
33
|
+
|
|
34
|
+
const parsedData = JSON.parse(storedValue);
|
|
35
|
+
|
|
36
|
+
// Handle both formats: array of objects with url property or array of strings
|
|
37
|
+
if (Array.isArray(parsedData)) {
|
|
38
|
+
return parsedData
|
|
39
|
+
.filter((item) => item !== null && item !== undefined)
|
|
40
|
+
.map((item) => {
|
|
41
|
+
if (typeof item === "object" && item !== null && "url" in item) {
|
|
42
|
+
// Add connectionMethod if it doesn't exist (for backward compatibility)
|
|
43
|
+
if (!("connectionMethod" in item)) {
|
|
44
|
+
return {
|
|
45
|
+
...item,
|
|
46
|
+
connectionMethod: "login" as ConnectionMethod, // Assume login for existing entries
|
|
47
|
+
} as DomainConfig;
|
|
48
|
+
}
|
|
49
|
+
return item as DomainConfig;
|
|
50
|
+
} else {
|
|
51
|
+
// Convert string domains to DomainConfig format (backward compatibility)
|
|
52
|
+
return {
|
|
53
|
+
url: String(item),
|
|
54
|
+
connectionMethod: "login" as ConnectionMethod,
|
|
55
|
+
clientId: "", // Empty clientId for legacy entries
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
.filter((domain) => domain.url.trim() !== ""); // Remove empty domains
|
|
60
|
+
}
|
|
61
|
+
return [];
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error("Failed to parse domains from localStorage", e);
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Saves domains to localStorage
|
|
70
|
+
*/
|
|
71
|
+
export const saveDomainToStorage = (domains: DomainConfig[]): void => {
|
|
72
|
+
try {
|
|
73
|
+
console.log("Saving domains to localStorage:", domains);
|
|
74
|
+
localStorage.setItem(DOMAINS_STORAGE_KEY, JSON.stringify(domains));
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.error("Failed to save domains to localStorage", e);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gets the selected domain from localStorage
|
|
82
|
+
* Falls back to first domain in storage if no selected domain is set
|
|
83
|
+
*/
|
|
84
|
+
export const getSelectedDomainFromStorage = (): string => {
|
|
85
|
+
try {
|
|
86
|
+
const selectedDomain = localStorage.getItem(SELECTED_DOMAIN_STORAGE_KEY);
|
|
87
|
+
if (selectedDomain) return selectedDomain;
|
|
88
|
+
|
|
89
|
+
// Fallback to first domain in storage if no selected domain
|
|
90
|
+
const domains = getDomainFromStorage();
|
|
91
|
+
return domains[0]?.url || "";
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error("Failed to get selected domain from localStorage", e);
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Saves the selected domain to localStorage
|
|
100
|
+
*/
|
|
101
|
+
export const saveSelectedDomainToStorage = (domain: string): void => {
|
|
102
|
+
try {
|
|
103
|
+
console.log("Saving selected domain to localStorage:", domain);
|
|
104
|
+
localStorage.setItem(SELECTED_DOMAIN_STORAGE_KEY, domain);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error("Failed to save selected domain to localStorage", e);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Gets client ID for a specific domain
|
|
112
|
+
* Falls back to environment variable if not found
|
|
113
|
+
*/
|
|
114
|
+
export const getClientIdFromStorage = (domain: string): string => {
|
|
115
|
+
// Ensure domain is properly formatted for comparison
|
|
116
|
+
const formattedDomain = formatDomain(domain);
|
|
117
|
+
|
|
118
|
+
const domains = getDomainFromStorage();
|
|
119
|
+
|
|
120
|
+
// Look for matching domain in the array
|
|
121
|
+
for (const d of domains) {
|
|
122
|
+
if (d.url === formattedDomain && d.clientId) {
|
|
123
|
+
return d.clientId;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Fallback to environment variable
|
|
128
|
+
const fallbackClientId = import.meta.env.VITE_AUTH0_CLIENT_ID;
|
|
129
|
+
return fallbackClientId;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Formats a domain string (removes protocol if present)
|
|
134
|
+
*/
|
|
135
|
+
export const formatDomain = (domain: string): string => {
|
|
136
|
+
return domain.trim().replace(/^https?:\/\//, "");
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Constructs a full URL with HTTPS protocol
|
|
141
|
+
* - If domain starts with "local.", connects to https://localhost:3000
|
|
142
|
+
* - Always uses https:// for all domains (including localhost with self-signed certs)
|
|
143
|
+
* - Preserves existing https:// protocol if already present
|
|
144
|
+
* - Converts http:// to https://
|
|
145
|
+
*/
|
|
146
|
+
export const buildUrlWithProtocol = (domain: string): string => {
|
|
147
|
+
const trimmedDomain = domain.trim();
|
|
148
|
+
|
|
149
|
+
// Extract hostname without protocol for local. check
|
|
150
|
+
const hostnameOnly = trimmedDomain.replace(/^https?:\/\//, "");
|
|
151
|
+
|
|
152
|
+
// If hostname starts with "local.", redirect to local development server
|
|
153
|
+
if (hostnameOnly.startsWith("local.")) {
|
|
154
|
+
return "https://localhost:3000";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check if it already has a protocol
|
|
158
|
+
if (trimmedDomain.startsWith("https://")) {
|
|
159
|
+
return trimmedDomain;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Convert http:// to https://
|
|
163
|
+
if (trimmedDomain.startsWith("http://")) {
|
|
164
|
+
return trimmedDomain.replace("http://", "https://");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// No protocol specified - add https://
|
|
168
|
+
return `https://${trimmedDomain}`;
|
|
169
|
+
};
|