@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,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
|
+
}
|