@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.
Files changed (110) hide show
  1. package/.eslintrc.js +21 -0
  2. package/.vercelignore +4 -0
  3. package/CHANGELOG.md +56 -0
  4. package/LICENSE +21 -0
  5. package/README.md +50 -0
  6. package/index.html +125 -0
  7. package/package.json +61 -0
  8. package/prettier.config.js +1 -0
  9. package/public/favicon.ico +0 -0
  10. package/public/manifest.json +15 -0
  11. package/src/App.spec.tsx +42 -0
  12. package/src/App.tsx +232 -0
  13. package/src/AuthCallback.tsx +138 -0
  14. package/src/Layout.tsx +12 -0
  15. package/src/TenantsApp.tsx +115 -0
  16. package/src/auth0DataProvider.ts +1242 -0
  17. package/src/authProvider.ts +521 -0
  18. package/src/components/CertificateErrorDialog.tsx +116 -0
  19. package/src/components/DomainSelector.tsx +401 -0
  20. package/src/components/TenantAppBar.tsx +83 -0
  21. package/src/components/TenantLayout.tsx +25 -0
  22. package/src/components/TenantsAppBar.tsx +21 -0
  23. package/src/components/TenantsLayout.tsx +28 -0
  24. package/src/components/activity/ActivityDashboard.tsx +381 -0
  25. package/src/components/activity/index.ts +1 -0
  26. package/src/components/branding/BrandingList.tsx +0 -0
  27. package/src/components/branding/BrandingShow.tsx +0 -0
  28. package/src/components/branding/ThemesTab.tsx +286 -0
  29. package/src/components/branding/edit.tsx +149 -0
  30. package/src/components/branding/hooks/useThemesData.ts +123 -0
  31. package/src/components/branding/index.ts +2 -0
  32. package/src/components/branding/list.tsx +12 -0
  33. package/src/components/clients/create.tsx +12 -0
  34. package/src/components/clients/edit.tsx +1285 -0
  35. package/src/components/clients/index.ts +3 -0
  36. package/src/components/clients/list.tsx +37 -0
  37. package/src/components/common/DateAgo.tsx +6 -0
  38. package/src/components/common/JsonOutput.tsx +26 -0
  39. package/src/components/common/index.ts +1 -0
  40. package/src/components/connections/create.tsx +35 -0
  41. package/src/components/connections/edit.tsx +212 -0
  42. package/src/components/connections/index.ts +3 -0
  43. package/src/components/connections/list.tsx +15 -0
  44. package/src/components/custom-domains/create.tsx +26 -0
  45. package/src/components/custom-domains/edit.tsx +101 -0
  46. package/src/components/custom-domains/index.ts +3 -0
  47. package/src/components/custom-domains/list.tsx +16 -0
  48. package/src/components/flows/create.tsx +30 -0
  49. package/src/components/flows/edit.tsx +238 -0
  50. package/src/components/flows/index.ts +3 -0
  51. package/src/components/flows/list.tsx +15 -0
  52. package/src/components/forms/FlowEditor.tsx +1363 -0
  53. package/src/components/forms/NodeEditor.tsx +1119 -0
  54. package/src/components/forms/RichTextEditor.tsx +145 -0
  55. package/src/components/forms/create.tsx +30 -0
  56. package/src/components/forms/edit.tsx +256 -0
  57. package/src/components/forms/index.ts +3 -0
  58. package/src/components/forms/list.tsx +16 -0
  59. package/src/components/hooks/create.tsx +96 -0
  60. package/src/components/hooks/edit.tsx +114 -0
  61. package/src/components/hooks/index.ts +3 -0
  62. package/src/components/hooks/list.tsx +17 -0
  63. package/src/components/listActions/PostListActions.tsx +10 -0
  64. package/src/components/logs/LogIcon.tsx +32 -0
  65. package/src/components/logs/LogShow.tsx +82 -0
  66. package/src/components/logs/LogType.tsx +38 -0
  67. package/src/components/logs/index.ts +4 -0
  68. package/src/components/logs/list.tsx +41 -0
  69. package/src/components/organizations/create.tsx +13 -0
  70. package/src/components/organizations/edit.tsx +682 -0
  71. package/src/components/organizations/index.ts +3 -0
  72. package/src/components/organizations/list.tsx +21 -0
  73. package/src/components/resource-servers/create.tsx +87 -0
  74. package/src/components/resource-servers/edit.tsx +121 -0
  75. package/src/components/resource-servers/index.ts +3 -0
  76. package/src/components/resource-servers/list.tsx +47 -0
  77. package/src/components/roles/create.tsx +12 -0
  78. package/src/components/roles/edit.tsx +426 -0
  79. package/src/components/roles/index.ts +3 -0
  80. package/src/components/roles/list.tsx +24 -0
  81. package/src/components/sessions/edit.tsx +101 -0
  82. package/src/components/sessions/index.ts +3 -0
  83. package/src/components/sessions/list.tsx +20 -0
  84. package/src/components/sessions/show.tsx +113 -0
  85. package/src/components/settings/edit.tsx +236 -0
  86. package/src/components/settings/index.ts +2 -0
  87. package/src/components/settings/list.tsx +14 -0
  88. package/src/components/tenants/create.tsx +20 -0
  89. package/src/components/tenants/edit.tsx +54 -0
  90. package/src/components/tenants/index.ts +2 -0
  91. package/src/components/tenants/list.tsx +67 -0
  92. package/src/components/themes/edit.tsx +200 -0
  93. package/src/components/themes/index.ts +2 -0
  94. package/src/components/themes/list.tsx +12 -0
  95. package/src/components/users/create.tsx +144 -0
  96. package/src/components/users/edit.tsx +1711 -0
  97. package/src/components/users/index.ts +3 -0
  98. package/src/components/users/list.tsx +35 -0
  99. package/src/data.json +121 -0
  100. package/src/dataProvider.ts +97 -0
  101. package/src/index.tsx +106 -0
  102. package/src/lib/logs.ts +21 -0
  103. package/src/types/reactflow.d.ts +86 -0
  104. package/src/utils/domainUtils.ts +169 -0
  105. package/src/utils/tokenUtils.ts +75 -0
  106. package/src/vite-env.d.ts +1 -0
  107. package/tsconfig.json +37 -0
  108. package/tsconfig.node.json +10 -0
  109. package/vercel.json +17 -0
  110. package/vite.config.ts +30 -0
@@ -0,0 +1,381 @@
1
+ import { useState, useEffect } from "react";
2
+ import {
3
+ Card,
4
+ CardContent,
5
+ Typography,
6
+ Box,
7
+ Paper,
8
+ Button,
9
+ CircularProgress,
10
+ Alert,
11
+ } from "@mui/material";
12
+ import Grid from "@mui/material/Grid";
13
+ import { useDataProvider, useBasename } from "react-admin";
14
+ import { useNavigate } from "react-router-dom";
15
+ import {
16
+ LineChart,
17
+ Line,
18
+ XAxis,
19
+ YAxis,
20
+ CartesianGrid,
21
+ Tooltip,
22
+ ResponsiveContainer,
23
+ } from "recharts";
24
+ import { format, subDays, parseISO } from "date-fns";
25
+
26
+ interface DailyStats {
27
+ date: string;
28
+ logins: number;
29
+ signups: number;
30
+ leaked_passwords: number;
31
+ updated_at: string;
32
+ created_at: string;
33
+ }
34
+
35
+ interface StatsCardProps {
36
+ title: string;
37
+ value: string | number;
38
+ loading?: boolean;
39
+ }
40
+
41
+ function StatsCard({ title, value, loading }: StatsCardProps) {
42
+ return (
43
+ <Card sx={{ height: "100%" }}>
44
+ <CardContent>
45
+ <Typography color="textSecondary" gutterBottom variant="body2">
46
+ {title}
47
+ </Typography>
48
+ {loading ? (
49
+ <CircularProgress size={24} />
50
+ ) : (
51
+ <Typography variant="h4" component="div">
52
+ {typeof value === "number" ? value.toLocaleString() : value}
53
+ </Typography>
54
+ )}
55
+ </CardContent>
56
+ </Card>
57
+ );
58
+ }
59
+
60
+ interface ChartCardProps {
61
+ title: string;
62
+ data: Array<{ date: string; value: number }>;
63
+ loading?: boolean;
64
+ color?: string;
65
+ showViewLogs?: boolean;
66
+ logsFilter?: string;
67
+ }
68
+
69
+ function ChartCard({
70
+ title,
71
+ data,
72
+ loading,
73
+ color = "#1976d2",
74
+ showViewLogs,
75
+ logsFilter,
76
+ }: ChartCardProps) {
77
+ const navigate = useNavigate();
78
+ const basename = useBasename();
79
+ const total = data.reduce((sum, item) => sum + item.value, 0);
80
+
81
+ const handleViewLogs = () => {
82
+ if (logsFilter) {
83
+ navigate(`${basename}/logs?filter=${encodeURIComponent(logsFilter)}`);
84
+ } else {
85
+ navigate(`${basename}/logs`);
86
+ }
87
+ };
88
+
89
+ return (
90
+ <Paper sx={{ p: 2, height: "100%" }}>
91
+ <Box
92
+ sx={{
93
+ display: "flex",
94
+ justifyContent: "space-between",
95
+ alignItems: "flex-start",
96
+ mb: 1,
97
+ }}
98
+ >
99
+ <Box>
100
+ <Typography variant="subtitle1" color="textSecondary">
101
+ {title}
102
+ </Typography>
103
+ {loading ? (
104
+ <CircularProgress size={20} />
105
+ ) : (
106
+ <Typography variant="h5" sx={{ fontWeight: "bold" }}>
107
+ {total.toLocaleString()}
108
+ </Typography>
109
+ )}
110
+ </Box>
111
+ {showViewLogs && (
112
+ <Button size="small" onClick={handleViewLogs}>
113
+ View logs
114
+ </Button>
115
+ )}
116
+ </Box>
117
+ {!loading && data.length > 0 && (
118
+ <ResponsiveContainer width="100%" height={150}>
119
+ <LineChart data={data}>
120
+ <CartesianGrid strokeDasharray="3 3" vertical={false} />
121
+ <XAxis
122
+ dataKey="date"
123
+ tick={{ fontSize: 11 }}
124
+ tickFormatter={(value) => {
125
+ try {
126
+ return format(parseISO(value), "MMM d");
127
+ } catch {
128
+ return value;
129
+ }
130
+ }}
131
+ interval="preserveStartEnd"
132
+ />
133
+ <YAxis tick={{ fontSize: 11 }} width={40} />
134
+ <Tooltip
135
+ labelFormatter={(value) => {
136
+ try {
137
+ return format(parseISO(value as string), "MMM d, yyyy");
138
+ } catch {
139
+ return value;
140
+ }
141
+ }}
142
+ />
143
+ <Line
144
+ type="monotone"
145
+ dataKey="value"
146
+ stroke={color}
147
+ strokeWidth={2}
148
+ dot={false}
149
+ activeDot={{ r: 4 }}
150
+ />
151
+ </LineChart>
152
+ </ResponsiveContainer>
153
+ )}
154
+ {!loading && data.length === 0 && (
155
+ <Box
156
+ sx={{
157
+ height: 150,
158
+ display: "flex",
159
+ alignItems: "center",
160
+ justifyContent: "center",
161
+ }}
162
+ >
163
+ <Typography color="textSecondary">No data available</Typography>
164
+ </Box>
165
+ )}
166
+ </Paper>
167
+ );
168
+ }
169
+
170
+ export function ActivityDashboard() {
171
+ const dataProvider = useDataProvider();
172
+ const [loading, setLoading] = useState(true);
173
+ const [error, setError] = useState<string | null>(null);
174
+ const [stats, setStats] = useState<DailyStats[]>([]);
175
+ const [totalUsers, setTotalUsers] = useState<number>(0);
176
+ const [totalClients, setTotalClients] = useState<number>(0);
177
+ const [totalConnections, setTotalConnections] = useState<number>(0);
178
+ const [totalResourceServers, setTotalResourceServers] = useState<number>(0);
179
+
180
+ useEffect(() => {
181
+ const fetchData = async () => {
182
+ setLoading(true);
183
+ setError(null);
184
+
185
+ try {
186
+ // Fetch daily stats
187
+ const fromDate = format(subDays(new Date(), 30), "yyyyMMdd");
188
+ const toDate = format(new Date(), "yyyyMMdd");
189
+
190
+ // The dataProvider doesn't have a generic custom endpoint method,
191
+ // so we'll use getList with our custom stats resource
192
+ // First, let's try to get the stats data via a custom fetch
193
+ const statsResponse = await dataProvider.getList("stats/daily", {
194
+ pagination: { page: 1, perPage: 100 },
195
+ sort: { field: "date", order: "ASC" },
196
+ filter: { from: fromDate, to: toDate },
197
+ });
198
+ setStats(statsResponse.data || []);
199
+
200
+ // Fetch counts for summary cards
201
+ const [usersResult, clientsResult, connectionsResult, apisResult] =
202
+ await Promise.all([
203
+ dataProvider
204
+ .getList("users", {
205
+ pagination: { page: 1, perPage: 1 },
206
+ sort: { field: "created_at", order: "DESC" },
207
+ filter: {},
208
+ })
209
+ .catch(() => ({ total: 0 })),
210
+ dataProvider
211
+ .getList("clients", {
212
+ pagination: { page: 1, perPage: 1 },
213
+ sort: { field: "created_at", order: "DESC" },
214
+ filter: {},
215
+ })
216
+ .catch(() => ({ total: 0 })),
217
+ dataProvider
218
+ .getList("connections", {
219
+ pagination: { page: 1, perPage: 1 },
220
+ sort: { field: "created_at", order: "DESC" },
221
+ filter: {},
222
+ })
223
+ .catch(() => ({ total: 0 })),
224
+ dataProvider
225
+ .getList("resource-servers", {
226
+ pagination: { page: 1, perPage: 1 },
227
+ sort: { field: "created_at", order: "DESC" },
228
+ filter: {},
229
+ })
230
+ .catch(() => ({ total: 0 })),
231
+ ]);
232
+
233
+ setTotalUsers(usersResult.total || 0);
234
+ setTotalClients(clientsResult.total || 0);
235
+ setTotalConnections(connectionsResult.total || 0);
236
+ setTotalResourceServers(apisResult.total || 0);
237
+ } catch (err: any) {
238
+ console.error("Error fetching activity data:", err);
239
+ setError(err.message || "Failed to load activity data");
240
+ } finally {
241
+ setLoading(false);
242
+ }
243
+ };
244
+
245
+ fetchData();
246
+ }, [dataProvider]);
247
+
248
+ // Transform stats data for charts
249
+ const dailyActiveUsersData = stats.map((s) => ({
250
+ date: s.date,
251
+ value: s.logins,
252
+ }));
253
+
254
+ const signupsData = stats.map((s) => ({
255
+ date: s.date,
256
+ value: s.signups,
257
+ }));
258
+
259
+ // Calculate user retention (simplified: returning users / total logins)
260
+ // For a proper implementation, this would need session tracking
261
+ const retentionData = stats.map((s, index) => {
262
+ // Simple placeholder retention calculation
263
+ const prevStats = index > 0 ? stats[index - 1] : null;
264
+ const prevLogins = prevStats?.logins ?? 0;
265
+ const retention =
266
+ s.logins > 0 && prevLogins > 0
267
+ ? Math.min(100, Math.round((prevLogins / s.logins) * 100))
268
+ : 0;
269
+ return {
270
+ date: s.date,
271
+ value: retention,
272
+ };
273
+ });
274
+
275
+ // Failed logins - we need to calculate this from log types
276
+ // For now, we'll show 0 as we don't have failed login data in daily stats
277
+ const failedLoginsData = stats.map((s) => ({
278
+ date: s.date,
279
+ value: 0, // This would need a separate query for failed logins
280
+ }));
281
+
282
+ // Date range display
283
+ const fromDisplay =
284
+ stats.length > 0 && stats[0]?.date
285
+ ? format(parseISO(stats[0].date), "EEE MMM dd yyyy")
286
+ : format(subDays(new Date(), 30), "EEE MMM dd yyyy");
287
+ const toDisplay = format(new Date(), "EEE MMM dd yyyy");
288
+
289
+ if (error) {
290
+ return (
291
+ <Box sx={{ p: 3 }}>
292
+ <Alert severity="error">{error}</Alert>
293
+ </Box>
294
+ );
295
+ }
296
+
297
+ return (
298
+ <Box sx={{ p: 3 }}>
299
+ <Typography variant="h4" gutterBottom>
300
+ Activity
301
+ </Typography>
302
+
303
+ {/* Summary Stats */}
304
+ <Grid container spacing={3} sx={{ mb: 4 }}>
305
+ <Grid size={{ xs: 12, sm: 6, md: 3 }}>
306
+ <StatsCard
307
+ title="Total Users"
308
+ value={totalUsers}
309
+ loading={loading}
310
+ />
311
+ </Grid>
312
+ <Grid size={{ xs: 12, sm: 6, md: 3 }}>
313
+ <StatsCard
314
+ title="Applications"
315
+ value={totalClients}
316
+ loading={loading}
317
+ />
318
+ </Grid>
319
+ <Grid size={{ xs: 12, sm: 6, md: 3 }}>
320
+ <StatsCard
321
+ title="APIs"
322
+ value={totalResourceServers}
323
+ loading={loading}
324
+ />
325
+ </Grid>
326
+ <Grid size={{ xs: 12, sm: 6, md: 3 }}>
327
+ <StatsCard
328
+ title="Connections"
329
+ value={totalConnections}
330
+ loading={loading}
331
+ />
332
+ </Grid>
333
+ </Grid>
334
+
335
+ {/* Date Range */}
336
+ <Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
337
+ {fromDisplay} 00:00:00 GMT+0000 - {toDisplay} 23:59:59 GMT+0000
338
+ </Typography>
339
+
340
+ {/* Charts */}
341
+ <Grid container spacing={3}>
342
+ <Grid size={{ xs: 12, md: 6 }}>
343
+ <ChartCard
344
+ title="Daily Active Users"
345
+ data={dailyActiveUsersData}
346
+ loading={loading}
347
+ color="#1976d2"
348
+ />
349
+ </Grid>
350
+ <Grid size={{ xs: 12, md: 6 }}>
351
+ <ChartCard
352
+ title="User Retention"
353
+ data={retentionData}
354
+ loading={loading}
355
+ color="#2e7d32"
356
+ />
357
+ </Grid>
358
+ <Grid size={{ xs: 12, md: 6 }}>
359
+ <ChartCard
360
+ title="Signups"
361
+ data={signupsData}
362
+ loading={loading}
363
+ color="#9c27b0"
364
+ showViewLogs
365
+ logsFilter='{"type":"ss"}'
366
+ />
367
+ </Grid>
368
+ <Grid size={{ xs: 12, md: 6 }}>
369
+ <ChartCard
370
+ title="Failed Logins"
371
+ data={failedLoginsData}
372
+ loading={loading}
373
+ color="#d32f2f"
374
+ showViewLogs
375
+ logsFilter='{"type":"f"}'
376
+ />
377
+ </Grid>
378
+ </Grid>
379
+ </Box>
380
+ );
381
+ }
@@ -0,0 +1 @@
1
+ export { ActivityDashboard } from "./ActivityDashboard";
File without changes
File without changes
@@ -0,0 +1,286 @@
1
+ import { TextInput, NumberInput, BooleanInput, SelectInput } from "react-admin";
2
+ import { ColorInput } from "react-admin-color-picker";
3
+ import { Box, Typography, Divider } from "@mui/material";
4
+
5
+ export function ThemesTab() {
6
+ return (
7
+ <Box sx={{ maxWidth: 800, padding: 2 }}>
8
+ <Typography variant="h6" sx={{ mb: 3 }}>
9
+ Theme Configuration
10
+ </Typography>
11
+
12
+ <Typography variant="body2" sx={{ mb: 3, color: "text.secondary" }}>
13
+ Configure the visual theme for your authentication pages. These settings
14
+ control colors, fonts, and layout.
15
+ </Typography>
16
+
17
+ <TextInput source="themes.displayName" label="Display Name" fullWidth />
18
+
19
+ <Divider sx={{ my: 3 }} />
20
+
21
+ {/* Colors Section */}
22
+ <Typography variant="h6" sx={{ mb: 2 }}>
23
+ Colors
24
+ </Typography>
25
+ <Box
26
+ sx={{
27
+ display: "grid",
28
+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
29
+ gap: 2,
30
+ mb: 3,
31
+ }}
32
+ >
33
+ <ColorInput
34
+ source="themes.colors.primary_button"
35
+ label="Primary Button"
36
+ />
37
+ <ColorInput
38
+ source="themes.colors.primary_button_label"
39
+ label="Primary Button Label"
40
+ />
41
+ <ColorInput
42
+ source="themes.colors.secondary_button_border"
43
+ label="Secondary Button Border"
44
+ />
45
+ <ColorInput
46
+ source="themes.colors.secondary_button_label"
47
+ label="Secondary Button Label"
48
+ />
49
+ <ColorInput
50
+ source="themes.colors.base_focus_color"
51
+ label="Base Focus Color"
52
+ />
53
+ <ColorInput
54
+ source="themes.colors.base_hover_color"
55
+ label="Base Hover Color"
56
+ />
57
+ <ColorInput source="themes.colors.body_text" label="Body Text" />
58
+ <ColorInput source="themes.colors.error" label="Error" />
59
+ <ColorInput source="themes.colors.header" label="Header" />
60
+ <ColorInput source="themes.colors.icons" label="Icons" />
61
+ <ColorInput
62
+ source="themes.colors.input_background"
63
+ label="Input Background"
64
+ />
65
+ <ColorInput source="themes.colors.input_border" label="Input Border" />
66
+ <ColorInput
67
+ source="themes.colors.input_filled_text"
68
+ label="Input Filled Text"
69
+ />
70
+ <ColorInput
71
+ source="themes.colors.input_labels_placeholders"
72
+ label="Input Labels/Placeholders"
73
+ />
74
+ <ColorInput
75
+ source="themes.colors.links_focused_components"
76
+ label="Links/Focused Components"
77
+ />
78
+ <ColorInput source="themes.colors.success" label="Success" />
79
+ <ColorInput
80
+ source="themes.colors.widget_background"
81
+ label="Widget Background"
82
+ />
83
+ <ColorInput
84
+ source="themes.colors.widget_border"
85
+ label="Widget Border"
86
+ />
87
+ </Box>
88
+
89
+ <SelectInput
90
+ source="themes.colors.captcha_widget_theme"
91
+ label="Captcha Widget Theme"
92
+ choices={[
93
+ { id: "auto", name: "Auto" },
94
+ { id: "dark", name: "Dark" },
95
+ { id: "light", name: "Light" },
96
+ ]}
97
+ />
98
+
99
+ <Divider sx={{ my: 3 }} />
100
+
101
+ {/* Borders Section */}
102
+ <Typography variant="h6" sx={{ mb: 2 }}>
103
+ Borders
104
+ </Typography>
105
+ <Box
106
+ sx={{
107
+ display: "grid",
108
+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
109
+ gap: 2,
110
+ mb: 3,
111
+ }}
112
+ >
113
+ <NumberInput
114
+ source="themes.borders.button_border_radius"
115
+ label="Button Border Radius"
116
+ />
117
+ <NumberInput
118
+ source="themes.borders.button_border_weight"
119
+ label="Button Border Weight"
120
+ />
121
+ <NumberInput
122
+ source="themes.borders.input_border_radius"
123
+ label="Input Border Radius"
124
+ />
125
+ <NumberInput
126
+ source="themes.borders.input_border_weight"
127
+ label="Input Border Weight"
128
+ />
129
+ <NumberInput
130
+ source="themes.borders.widget_border_weight"
131
+ label="Widget Border Weight"
132
+ />
133
+ <NumberInput
134
+ source="themes.borders.widget_corner_radius"
135
+ label="Widget Corner Radius"
136
+ />
137
+ </Box>
138
+
139
+ <Box
140
+ sx={{
141
+ display: "grid",
142
+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
143
+ gap: 2,
144
+ mb: 3,
145
+ }}
146
+ >
147
+ <SelectInput
148
+ source="themes.borders.buttons_style"
149
+ label="Buttons Style"
150
+ choices={[
151
+ { id: "pill", name: "Pill" },
152
+ { id: "rounded", name: "Rounded" },
153
+ { id: "sharp", name: "Sharp" },
154
+ ]}
155
+ />
156
+ <SelectInput
157
+ source="themes.borders.inputs_style"
158
+ label="Inputs Style"
159
+ choices={[
160
+ { id: "pill", name: "Pill" },
161
+ { id: "rounded", name: "Rounded" },
162
+ { id: "sharp", name: "Sharp" },
163
+ ]}
164
+ />
165
+ <BooleanInput
166
+ source="themes.borders.show_widget_shadow"
167
+ label="Show Widget Shadow"
168
+ />
169
+ </Box>
170
+
171
+ <Divider sx={{ my: 3 }} />
172
+
173
+ {/* Fonts Section */}
174
+ <Typography variant="h6" sx={{ mb: 2 }}>
175
+ Fonts
176
+ </Typography>
177
+ <TextInput source="themes.fonts.font_url" label="Font URL" fullWidth />
178
+ <NumberInput
179
+ source="themes.fonts.reference_text_size"
180
+ label="Reference Text Size"
181
+ />
182
+
183
+ {/* Font sections for different text types */}
184
+ {[
185
+ { key: "body_text", label: "Body Text" },
186
+ { key: "buttons_text", label: "Buttons Text" },
187
+ { key: "input_labels", label: "Input Labels" },
188
+ { key: "links", label: "Links" },
189
+ { key: "subtitle", label: "Subtitle" },
190
+ { key: "title", label: "Title" },
191
+ ].map(({ key, label }) => (
192
+ <Box key={key} sx={{ mb: 2 }}>
193
+ <Typography variant="subtitle1" sx={{ mb: 1 }}>
194
+ {label}
195
+ </Typography>
196
+ <Box sx={{ display: "flex", gap: 2, alignItems: "center" }}>
197
+ <BooleanInput source={`themes.fonts.${key}.bold`} label="Bold" />
198
+ <NumberInput source={`themes.fonts.${key}.size`} label="Size" />
199
+ </Box>
200
+ </Box>
201
+ ))}
202
+
203
+ <SelectInput
204
+ source="themes.fonts.links_style"
205
+ label="Links Style"
206
+ choices={[
207
+ { id: "normal", name: "Normal" },
208
+ { id: "underlined", name: "Underlined" },
209
+ ]}
210
+ />
211
+
212
+ <Divider sx={{ my: 3 }} />
213
+
214
+ {/* Page Background Section */}
215
+ <Typography variant="h6" sx={{ mb: 2 }}>
216
+ Page Background
217
+ </Typography>
218
+ <Box sx={{ mb: 3 }}>
219
+ <ColorInput
220
+ source="themes.page_background.background_color"
221
+ label="Background Color"
222
+ />
223
+ <TextInput
224
+ source="themes.page_background.background_image_url"
225
+ label="Background Image URL"
226
+ fullWidth
227
+ />
228
+ <SelectInput
229
+ source="themes.page_background.page_layout"
230
+ label="Page Layout"
231
+ choices={[
232
+ { id: "center", name: "Center" },
233
+ { id: "left", name: "Left" },
234
+ { id: "right", name: "Right" },
235
+ ]}
236
+ />
237
+ </Box>
238
+
239
+ <Divider sx={{ my: 3 }} />
240
+
241
+ {/* Widget Section */}
242
+ <Typography variant="h6" sx={{ mb: 2 }}>
243
+ Widget
244
+ </Typography>
245
+ <Box
246
+ sx={{
247
+ display: "grid",
248
+ gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
249
+ gap: 2,
250
+ mb: 3,
251
+ }}
252
+ >
253
+ <SelectInput
254
+ source="themes.widget.header_text_alignment"
255
+ label="Header Text Alignment"
256
+ choices={[
257
+ { id: "center", name: "Center" },
258
+ { id: "left", name: "Left" },
259
+ { id: "right", name: "Right" },
260
+ ]}
261
+ />
262
+ <NumberInput source="themes.widget.logo_height" label="Logo Height" />
263
+ <SelectInput
264
+ source="themes.widget.logo_position"
265
+ label="Logo Position"
266
+ choices={[
267
+ { id: "center", name: "Center" },
268
+ { id: "left", name: "Left" },
269
+ { id: "none", name: "None" },
270
+ { id: "right", name: "Right" },
271
+ ]}
272
+ />
273
+ <SelectInput
274
+ source="themes.widget.social_buttons_layout"
275
+ label="Social Buttons Layout"
276
+ choices={[
277
+ { id: "bottom", name: "Bottom" },
278
+ { id: "top", name: "Top" },
279
+ ]}
280
+ />
281
+ </Box>
282
+
283
+ <TextInput source="themes.widget.logo_url" label="Logo URL" fullWidth />
284
+ </Box>
285
+ );
286
+ }