@authhero/react-admin 0.23.0 → 0.25.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/CHANGELOG.md +18 -0
- package/README.md +16 -1
- package/package.json +1 -1
- package/src/auth0DataProvider.ts +80 -9
- package/src/index.tsx +23 -6
- package/vercel.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @authhero/react-admin
|
|
2
2
|
|
|
3
|
+
## 0.25.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7bf78f7: Add deploy buttons for react-admin
|
|
8
|
+
|
|
9
|
+
## 0.24.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 9d6cfb8: Wrap adapters as part of the multi-tenant package
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [9d6cfb8]
|
|
18
|
+
- @authhero/adapter-interfaces@0.122.0
|
|
19
|
+
- @authhero/widget@0.7.1
|
|
20
|
+
|
|
3
21
|
## 0.23.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# react-admin
|
|
2
2
|
|
|
3
|
+
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fauthhero%2Fauthhero&env=VITE_AUTH0_DOMAIN,VITE_AUTH0_CLIENT_ID,VITE_AUTH0_API_URL,VITE_SINGLE_DOMAIN_MODE&envDescription=Configure%20your%20AuthHero%20connection.%20Set%20VITE_SINGLE_DOMAIN_MODE%20to%20%22true%22%20to%20skip%20domain%20selector.&envLink=https%3A%2F%2Fgithub.com%2Fauthhero%2Fauthhero%2Fblob%2Fmain%2Fapps%2Freact-admin%2FREADME.md&project-name=authhero-admin&repository-name=authhero-admin&root-directory=apps%2Freact-admin&build-command=pnpm%20run%20build&output-directory=dist&install-command=corepack%20enable%20%26%26%20corepack%20prepare%20pnpm%4010.11.0%20--activate%20%26%26%20pnpm%20install%20--no-frozen-lockfile)
|
|
4
|
+
[](https://deploy.workers.cloudflare.com/?url=https://github.com/authhero/authhero)
|
|
5
|
+
|
|
6
|
+
> **Cloudflare Pages Setup:** Select "Pages" during setup, set root directory to `apps/react-admin`, build command to `pnpm run build`, and output directory to `dist`. Add environment variables `VITE_AUTH0_DOMAIN`, `VITE_AUTH0_CLIENT_ID`, `VITE_AUTH0_API_URL`, and `VITE_SINGLE_DOMAIN_MODE`.
|
|
7
|
+
|
|
3
8
|
## Installation
|
|
4
9
|
|
|
5
10
|
Install the application dependencies by running:
|
|
@@ -26,6 +31,9 @@ VITE_AUTH0_API_URL=https://auth2.sesamy.com
|
|
|
26
31
|
VITE_AUTH0_DOMAIN=localhost:3000
|
|
27
32
|
VITE_AUTH0_CLIENT_ID=auth-admin
|
|
28
33
|
VITE_AUTH0_API_URL=https://localhost:3000
|
|
34
|
+
|
|
35
|
+
# Optional: Skip domain selector and use configured domain directly
|
|
36
|
+
VITE_SINGLE_DOMAIN_MODE=true
|
|
29
37
|
```
|
|
30
38
|
|
|
31
39
|
See `.env.example` for more details.
|
|
@@ -33,7 +41,8 @@ See `.env.example` for more details.
|
|
|
33
41
|
**Notes:**
|
|
34
42
|
|
|
35
43
|
- If `VITE_AUTH0_DOMAIN` is set, it will be automatically added to the domain list
|
|
36
|
-
- Users can still add additional domains through the UI
|
|
44
|
+
- Users can still add additional domains through the UI (unless `VITE_SINGLE_DOMAIN_MODE=true`)
|
|
45
|
+
- Set `VITE_SINGLE_DOMAIN_MODE=true` to skip the domain selector entirely
|
|
37
46
|
|
|
38
47
|
## Development
|
|
39
48
|
|
|
@@ -55,6 +64,12 @@ npm run build
|
|
|
55
64
|
yarn build
|
|
56
65
|
```
|
|
57
66
|
|
|
67
|
+
## Deployment
|
|
68
|
+
|
|
69
|
+
The React Admin application can be deployed to Vercel. See the [Vercel deployment guide](../docs/deployment-targets/vercel.md) for detailed instructions.
|
|
70
|
+
|
|
71
|
+
**Important:** When deploying to Vercel, you must set the environment variable `ENABLE_EXPERIMENTAL_COREPACK=1` to avoid build errors with pnpm.
|
|
72
|
+
|
|
58
73
|
## DataProvider
|
|
59
74
|
|
|
60
75
|
The included data provider use [FakeREST](https://github.com/marmelab/fakerest) to simulate a backend.
|
package/package.json
CHANGED
package/src/auth0DataProvider.ts
CHANGED
|
@@ -88,6 +88,65 @@ function createHeaders(tenantId?: string): Headers {
|
|
|
88
88
|
return headers;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
// Helper for client-side paging, sorting, and search on pre-fetched data
|
|
92
|
+
interface ClientSideListParams {
|
|
93
|
+
data: any[];
|
|
94
|
+
page: number;
|
|
95
|
+
perPage: number;
|
|
96
|
+
sortField?: string;
|
|
97
|
+
sortOrder?: "ASC" | "DESC";
|
|
98
|
+
searchQuery?: string;
|
|
99
|
+
searchFields?: string[];
|
|
100
|
+
idKey?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function clientSideListHandler({
|
|
104
|
+
data,
|
|
105
|
+
page,
|
|
106
|
+
perPage,
|
|
107
|
+
sortField,
|
|
108
|
+
sortOrder,
|
|
109
|
+
searchQuery,
|
|
110
|
+
searchFields = ["name"],
|
|
111
|
+
idKey = "id",
|
|
112
|
+
}: ClientSideListParams): { data: any[]; total: number } {
|
|
113
|
+
let filtered = data;
|
|
114
|
+
|
|
115
|
+
// Apply client-side search filter
|
|
116
|
+
if (searchQuery) {
|
|
117
|
+
const query = searchQuery.toLowerCase();
|
|
118
|
+
filtered = data.filter((item: any) =>
|
|
119
|
+
searchFields.some((field) =>
|
|
120
|
+
item[field]?.toString().toLowerCase().includes(query),
|
|
121
|
+
),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Apply client-side sorting
|
|
126
|
+
if (sortField) {
|
|
127
|
+
filtered = [...filtered].sort((a: any, b: any) => {
|
|
128
|
+
const aVal = a[sortField] || "";
|
|
129
|
+
const bVal = b[sortField] || "";
|
|
130
|
+
const comparison = String(aVal).localeCompare(String(bVal));
|
|
131
|
+
return sortOrder === "DESC" ? -comparison : comparison;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Apply client-side pagination
|
|
136
|
+
const total = filtered.length;
|
|
137
|
+
const startIndex = (page - 1) * perPage;
|
|
138
|
+
const endIndex = startIndex + perPage;
|
|
139
|
+
const paged = filtered.slice(startIndex, endIndex);
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
data: paged.map((item: any) => ({
|
|
143
|
+
id: item[idKey] || item.id,
|
|
144
|
+
...item,
|
|
145
|
+
})),
|
|
146
|
+
total,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
91
150
|
// Helper to handle singleton resource fetching
|
|
92
151
|
async function fetchSingleton(
|
|
93
152
|
resource: string,
|
|
@@ -201,15 +260,7 @@ export default (
|
|
|
201
260
|
resourceKey: "resource_servers",
|
|
202
261
|
idKey: "id",
|
|
203
262
|
},
|
|
204
|
-
|
|
205
|
-
fetch: (client) =>
|
|
206
|
-
client.organizations.list({
|
|
207
|
-
from: String((page - 1) * (perPage || 10)),
|
|
208
|
-
take: perPage || 10,
|
|
209
|
-
}),
|
|
210
|
-
resourceKey: "organizations",
|
|
211
|
-
idKey: "id",
|
|
212
|
-
},
|
|
263
|
+
// Organizations handled separately with client-side paging/search
|
|
213
264
|
// Logs removed from SDK handlers - using HTTP directly for full control
|
|
214
265
|
rules: {
|
|
215
266
|
fetch: (client) => client.rules.list(),
|
|
@@ -273,6 +324,26 @@ export default (
|
|
|
273
324
|
);
|
|
274
325
|
}
|
|
275
326
|
|
|
327
|
+
// Handle organizations with client-side paging and search (fetch 500, filter locally)
|
|
328
|
+
if (resource === "organizations" && !resourcePath.includes("/")) {
|
|
329
|
+
const result = await managementClient.organizations.list({
|
|
330
|
+
from: "0",
|
|
331
|
+
take: 500,
|
|
332
|
+
});
|
|
333
|
+
const { data: allOrgs } = normalizeSDKResponse(result, "organizations");
|
|
334
|
+
|
|
335
|
+
return clientSideListHandler({
|
|
336
|
+
data: allOrgs,
|
|
337
|
+
page,
|
|
338
|
+
perPage: perPage || 10,
|
|
339
|
+
sortField: field,
|
|
340
|
+
sortOrder: order,
|
|
341
|
+
searchQuery: params.filter?.q,
|
|
342
|
+
searchFields: ["name", "display_name"],
|
|
343
|
+
idKey: "id",
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
276
347
|
// Handle logs with direct HTTP for full control over query params
|
|
277
348
|
if (resource === "logs") {
|
|
278
349
|
const headers = createHeaders(tenantId);
|
package/src/index.tsx
CHANGED
|
@@ -11,9 +11,20 @@ import { getSelectedDomainFromStorage } from "./utils/domainUtils";
|
|
|
11
11
|
const isLocalDevelopment = window.location.hostname.startsWith("local.");
|
|
12
12
|
const LOCAL_DOMAIN = "localhost:3000";
|
|
13
13
|
|
|
14
|
+
// Check if single domain mode is enabled - skips the domain selector entirely
|
|
15
|
+
const isSingleDomainMode = import.meta.env.VITE_SINGLE_DOMAIN_MODE === "true";
|
|
16
|
+
const envDomain = import.meta.env.VITE_AUTH0_DOMAIN;
|
|
17
|
+
|
|
14
18
|
function Root() {
|
|
19
|
+
// In single domain mode, always use the configured domain from env
|
|
20
|
+
const getInitialDomain = () => {
|
|
21
|
+
if (isLocalDevelopment) return LOCAL_DOMAIN;
|
|
22
|
+
if (isSingleDomainMode && envDomain) return envDomain;
|
|
23
|
+
return null;
|
|
24
|
+
};
|
|
25
|
+
|
|
15
26
|
const [selectedDomain, setSelectedDomain] = useState<string | null>(
|
|
16
|
-
|
|
27
|
+
getInitialDomain(),
|
|
17
28
|
);
|
|
18
29
|
const currentPath = location.pathname;
|
|
19
30
|
const isAuthCallback = currentPath === "/auth-callback";
|
|
@@ -24,10 +35,10 @@ function Root() {
|
|
|
24
35
|
currentPath.startsWith("/tenants/create") ||
|
|
25
36
|
currentPath === "/tenants/";
|
|
26
37
|
|
|
27
|
-
// Load domain from cookies on component mount (skip for local development)
|
|
38
|
+
// Load domain from cookies on component mount (skip for local development and single domain mode)
|
|
28
39
|
useEffect(() => {
|
|
29
|
-
if (isLocalDevelopment) {
|
|
30
|
-
// For local development, always use
|
|
40
|
+
if (isLocalDevelopment || isSingleDomainMode) {
|
|
41
|
+
// For local development or single domain mode, always use the configured domain
|
|
31
42
|
return;
|
|
32
43
|
}
|
|
33
44
|
const savedDomain = getSelectedDomainFromStorage();
|
|
@@ -48,8 +59,8 @@ function Root() {
|
|
|
48
59
|
}
|
|
49
60
|
|
|
50
61
|
// 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)) {
|
|
62
|
+
// Skip for local development and single domain mode - redirect to /tenants instead
|
|
63
|
+
if (!isLocalDevelopment && !isSingleDomainMode && (isRootPath || !selectedDomain)) {
|
|
53
64
|
return (
|
|
54
65
|
<DomainSelector
|
|
55
66
|
onDomainSelected={(domain) => setSelectedDomain(domain)}
|
|
@@ -58,6 +69,12 @@ function Root() {
|
|
|
58
69
|
);
|
|
59
70
|
}
|
|
60
71
|
|
|
72
|
+
// For single domain mode on root path, redirect to /tenants
|
|
73
|
+
if (isSingleDomainMode && isRootPath) {
|
|
74
|
+
window.location.href = "/tenants";
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
61
78
|
// For local development on root path, redirect to /tenants
|
|
62
79
|
if (isLocalDevelopment && isRootPath) {
|
|
63
80
|
window.location.href = "/tenants";
|
package/vercel.json
CHANGED