@authhero/react-admin 0.26.0 → 0.28.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/package.json +1 -1
- package/src/App.spec.tsx +8 -4
- package/src/auth0DataProvider.ts +52 -9
- package/src/components/branding/edit.tsx +50 -19
- package/src/components/clients/edit.tsx +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @authhero/react-admin
|
|
2
2
|
|
|
3
|
+
## 0.28.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2d0a7f4: Add a auth0-conformance flag
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [2d0a7f4]
|
|
12
|
+
- @authhero/adapter-interfaces@0.123.0
|
|
13
|
+
- @authhero/widget@0.7.2
|
|
14
|
+
|
|
15
|
+
## 0.27.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 76510cd: Fixes for branding page and endpoint
|
|
20
|
+
|
|
3
21
|
## 0.26.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/package.json
CHANGED
package/src/App.spec.tsx
CHANGED
|
@@ -29,10 +29,14 @@ vi.mock("./utils/domainUtils", () => ({
|
|
|
29
29
|
buildUrlWithProtocol: (url: string) => `https://${url}`,
|
|
30
30
|
}));
|
|
31
31
|
|
|
32
|
-
vi.mock("react-router-dom", () =>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
vi.mock("react-router-dom", async (importOriginal) => {
|
|
33
|
+
const actual = (await importOriginal()) as any;
|
|
34
|
+
return {
|
|
35
|
+
...actual,
|
|
36
|
+
useNavigate: () => vi.fn(),
|
|
37
|
+
useLocation: () => ({ pathname: "/" }),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
36
40
|
|
|
37
41
|
// Mock color picker to avoid CSS import issues in tests
|
|
38
42
|
vi.mock("react-admin-color-picker", () => ({
|
package/src/auth0DataProvider.ts
CHANGED
|
@@ -315,7 +315,29 @@ export default (
|
|
|
315
315
|
|
|
316
316
|
// Handle singleton resources
|
|
317
317
|
if (resource === "branding") {
|
|
318
|
-
|
|
318
|
+
const branding = await managementClient.branding.get();
|
|
319
|
+
// Also fetch themes to include in branding data
|
|
320
|
+
const headers = createHeaders(tenantId);
|
|
321
|
+
let themes = null;
|
|
322
|
+
try {
|
|
323
|
+
const themesResponse = await httpClient(
|
|
324
|
+
`${apiUrl}/api/v2/branding/themes/default`,
|
|
325
|
+
{ headers },
|
|
326
|
+
);
|
|
327
|
+
themes = themesResponse.json;
|
|
328
|
+
} catch (e) {
|
|
329
|
+
// Themes might not exist yet, that's ok
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
data: [
|
|
333
|
+
{
|
|
334
|
+
...branding,
|
|
335
|
+
themes,
|
|
336
|
+
id: resource,
|
|
337
|
+
},
|
|
338
|
+
],
|
|
339
|
+
total: 1,
|
|
340
|
+
};
|
|
319
341
|
}
|
|
320
342
|
|
|
321
343
|
if (resource === "settings") {
|
|
@@ -534,9 +556,22 @@ export default (
|
|
|
534
556
|
// Handle singleton resources
|
|
535
557
|
if (resource === "branding") {
|
|
536
558
|
const result = await managementClient.branding.get();
|
|
559
|
+
// Also fetch themes to include in branding data
|
|
560
|
+
const headers = createHeaders(tenantId);
|
|
561
|
+
let themes = null;
|
|
562
|
+
try {
|
|
563
|
+
const themesResponse = await httpClient(
|
|
564
|
+
`${apiUrl}/api/v2/branding/themes/default`,
|
|
565
|
+
{ headers },
|
|
566
|
+
);
|
|
567
|
+
themes = themesResponse.json;
|
|
568
|
+
} catch (e) {
|
|
569
|
+
// Themes might not exist yet, that's ok
|
|
570
|
+
}
|
|
537
571
|
return {
|
|
538
572
|
data: {
|
|
539
573
|
...result,
|
|
574
|
+
themes,
|
|
540
575
|
id: resource,
|
|
541
576
|
},
|
|
542
577
|
};
|
|
@@ -915,9 +950,12 @@ export default (
|
|
|
915
950
|
|
|
916
951
|
// Special handling for branding to update theme data separately
|
|
917
952
|
if (resource === "branding") {
|
|
918
|
-
//
|
|
953
|
+
// Extract themes from the payload - it's updated via a separate endpoint
|
|
954
|
+
const { themes, ...brandingData } = cleanParams.data;
|
|
955
|
+
|
|
956
|
+
// Update branding (without themes)
|
|
919
957
|
const brandingResult = await managementClient.branding.update(
|
|
920
|
-
|
|
958
|
+
brandingData,
|
|
921
959
|
);
|
|
922
960
|
|
|
923
961
|
// Update themes if provided
|
|
@@ -926,12 +964,17 @@ export default (
|
|
|
926
964
|
...brandingResult,
|
|
927
965
|
};
|
|
928
966
|
|
|
929
|
-
if (
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
967
|
+
if (themes) {
|
|
968
|
+
// Use HTTP directly since the SDK doesn't have this method
|
|
969
|
+
const themeResponse = await httpClient(
|
|
970
|
+
`${apiUrl}/api/v2/branding/themes/default`,
|
|
971
|
+
{
|
|
972
|
+
headers,
|
|
973
|
+
method: "PATCH",
|
|
974
|
+
body: JSON.stringify(themes),
|
|
975
|
+
},
|
|
976
|
+
);
|
|
977
|
+
result.themes = themeResponse.json;
|
|
935
978
|
}
|
|
936
979
|
|
|
937
980
|
return { data: result };
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DateField,
|
|
3
|
-
Edit,
|
|
4
|
-
FieldTitle,
|
|
5
|
-
Labeled,
|
|
6
|
-
TextInput,
|
|
7
|
-
TabbedForm,
|
|
8
|
-
} from "react-admin";
|
|
1
|
+
import { Edit, TextInput, TabbedForm } from "react-admin";
|
|
9
2
|
import { ColorInput } from "react-admin-color-picker";
|
|
10
3
|
import { useInput, useRecordContext } from "react-admin";
|
|
11
4
|
import { useState, useEffect } from "react";
|
|
@@ -13,6 +6,54 @@ import { Box } from "@mui/material";
|
|
|
13
6
|
import { ThemesTab } from "./ThemesTab";
|
|
14
7
|
import { BrandingPreview } from "./BrandingPreview";
|
|
15
8
|
|
|
9
|
+
// Helper to recursively remove null values and empty objects from data
|
|
10
|
+
// This is needed because react-admin sends null for empty form fields,
|
|
11
|
+
// but the server schema expects fields to be omitted rather than null
|
|
12
|
+
function cleanObject(obj: Record<string, unknown>): Record<string, unknown> {
|
|
13
|
+
const result: Record<string, unknown> = {};
|
|
14
|
+
|
|
15
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
16
|
+
// Skip null and undefined values
|
|
17
|
+
if (value === null || value === undefined) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Recursively clean nested objects (but not arrays)
|
|
22
|
+
if (typeof value === "object" && !Array.isArray(value)) {
|
|
23
|
+
const cleaned = cleanObject(value as Record<string, unknown>);
|
|
24
|
+
// Only include non-empty objects
|
|
25
|
+
if (Object.keys(cleaned).length > 0) {
|
|
26
|
+
result[key] = cleaned;
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
result[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Transform function to clean up empty objects and null values before sending to server
|
|
37
|
+
// The server schema has strict requirements:
|
|
38
|
+
// - font.url is required if font object is present
|
|
39
|
+
// - page_background must be an object or omitted, not null
|
|
40
|
+
// - Many fields cannot be null, only omitted
|
|
41
|
+
const transformBranding = (data: Record<string, unknown>) => {
|
|
42
|
+
// First, recursively clean all null values and empty objects
|
|
43
|
+
const result = cleanObject(data);
|
|
44
|
+
|
|
45
|
+
// Remove font object if url is not set (font.url is required if font exists)
|
|
46
|
+
if (
|
|
47
|
+
result.font &&
|
|
48
|
+
typeof result.font === "object" &&
|
|
49
|
+
!(result.font as Record<string, unknown>).url
|
|
50
|
+
) {
|
|
51
|
+
delete result.font;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
|
|
16
57
|
function PageBackgroundInput(props) {
|
|
17
58
|
const { field } = useInput(props);
|
|
18
59
|
const record = useRecordContext();
|
|
@@ -128,16 +169,6 @@ function BrandingFormContent() {
|
|
|
128
169
|
{/* Form Section */}
|
|
129
170
|
<Box sx={{ flex: "1 1 60%", minWidth: 0 }}>
|
|
130
171
|
<TabbedForm>
|
|
131
|
-
<TabbedForm.Tab label="Info">
|
|
132
|
-
<TextInput source="id" />
|
|
133
|
-
<TextInput source="name" />
|
|
134
|
-
<Labeled label={<FieldTitle source="created_at" />}>
|
|
135
|
-
<DateField source="created_at" showTime={true} />
|
|
136
|
-
</Labeled>
|
|
137
|
-
<Labeled label={<FieldTitle source="updated_at" />}>
|
|
138
|
-
<DateField source="updated_at" showTime={true} />
|
|
139
|
-
</Labeled>
|
|
140
|
-
</TabbedForm.Tab>
|
|
141
172
|
<TabbedForm.Tab label="Style">
|
|
142
173
|
<ColorInput source="colors.primary" label="Primary Color" />
|
|
143
174
|
<PageBackgroundInput source="colors.page_background" />
|
|
@@ -184,7 +215,7 @@ function BrandingFormContent() {
|
|
|
184
215
|
|
|
185
216
|
export function BrandingEdit() {
|
|
186
217
|
return (
|
|
187
|
-
<Edit>
|
|
218
|
+
<Edit transform={transformBranding}>
|
|
188
219
|
<BrandingFormContent />
|
|
189
220
|
</Edit>
|
|
190
221
|
);
|
|
@@ -1226,6 +1226,12 @@ export function ClientEdit() {
|
|
|
1226
1226
|
format={(value) => value === "true" || value === true}
|
|
1227
1227
|
parse={(value) => (value ? "true" : "false")}
|
|
1228
1228
|
/>
|
|
1229
|
+
<BooleanInput
|
|
1230
|
+
source="auth0_conformant"
|
|
1231
|
+
label="Auth0 Conformant Mode"
|
|
1232
|
+
helperText="Enable Auth0-compatible behavior. Disable for strict OIDC compliance."
|
|
1233
|
+
defaultValue={true}
|
|
1234
|
+
/>
|
|
1229
1235
|
<ClientMetadataInput source="client_metadata" />
|
|
1230
1236
|
<GrantTypesInput source="grant_types" />
|
|
1231
1237
|
<ArrayInput source="callbacks">
|