@authhero/react-admin 0.26.0 → 0.27.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @authhero/react-admin
2
2
 
3
+ ## 0.27.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 76510cd: Fixes for branding page and endpoint
8
+
3
9
  ## 0.26.0
4
10
 
5
11
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authhero/react-admin",
3
- "version": "0.26.0",
3
+ "version": "0.27.0",
4
4
  "packageManager": "pnpm@10.20.0",
5
5
  "private": false,
6
6
  "repository": {
@@ -315,7 +315,29 @@ export default (
315
315
 
316
316
  // Handle singleton resources
317
317
  if (resource === "branding") {
318
- return fetchSingleton(resource, () => managementClient.branding.get());
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
- // Update branding
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
- cleanParams.data,
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 (cleanParams.data.themes) {
930
- const themeUpdateResult = await (
931
- managementClient.branding.themes as any
932
- ).default.patch(cleanParams.data.themes);
933
- result.themes =
934
- (themeUpdateResult as any).response || themeUpdateResult;
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
  );