flarecms 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/dist/auth/index.js +201 -1
- package/dist/cli/commands.js +5554 -55
- package/dist/cli/index.js +5554 -55
- package/dist/cli/mcp.js +30 -0
- package/dist/client/index.js +23576 -0
- package/dist/db/index.js +10392 -25
- package/dist/index.js +56776 -7582
- package/dist/server/index.js +43280 -0
- package/dist/style.css +5536 -0
- package/package.json +33 -30
- package/scripts/fix-api-paths.mjs +0 -32
- package/scripts/fix-imports.mjs +0 -38
- package/scripts/prefix-css.mjs +0 -45
- package/src/api/lib/cache.ts +0 -45
- package/src/api/lib/response.ts +0 -40
- package/src/api/middlewares/auth.ts +0 -186
- package/src/api/middlewares/cors.ts +0 -10
- package/src/api/middlewares/rbac.ts +0 -85
- package/src/api/routes/auth.ts +0 -377
- package/src/api/routes/collections.ts +0 -205
- package/src/api/routes/content.ts +0 -175
- package/src/api/routes/device.ts +0 -160
- package/src/api/routes/magic.ts +0 -150
- package/src/api/routes/mcp.ts +0 -273
- package/src/api/routes/oauth.ts +0 -160
- package/src/api/routes/settings.ts +0 -43
- package/src/api/routes/setup.ts +0 -307
- package/src/api/routes/tokens.ts +0 -80
- package/src/api/schemas/auth.ts +0 -15
- package/src/api/schemas/index.ts +0 -51
- package/src/api/schemas/tokens.ts +0 -24
- package/src/auth/index.ts +0 -28
- package/src/cli/commands.ts +0 -217
- package/src/cli/index.ts +0 -21
- package/src/cli/mcp.ts +0 -210
- package/src/cli/tests/cli.test.ts +0 -40
- package/src/cli/tests/create.test.ts +0 -87
- package/src/client/FlareAdminRouter.tsx +0 -47
- package/src/client/app.tsx +0 -175
- package/src/client/components/app-sidebar.tsx +0 -227
- package/src/client/components/collection-modal.tsx +0 -215
- package/src/client/components/content-list.tsx +0 -247
- package/src/client/components/dynamic-form.tsx +0 -190
- package/src/client/components/field-modal.tsx +0 -221
- package/src/client/components/settings/api-token-section.tsx +0 -400
- package/src/client/components/settings/general-section.tsx +0 -224
- package/src/client/components/settings/security-section.tsx +0 -154
- package/src/client/components/settings/seo-section.tsx +0 -200
- package/src/client/components/settings/signup-section.tsx +0 -257
- package/src/client/components/ui/accordion.tsx +0 -78
- package/src/client/components/ui/avatar.tsx +0 -107
- package/src/client/components/ui/badge.tsx +0 -52
- package/src/client/components/ui/button.tsx +0 -60
- package/src/client/components/ui/card.tsx +0 -103
- package/src/client/components/ui/checkbox.tsx +0 -27
- package/src/client/components/ui/collapsible.tsx +0 -19
- package/src/client/components/ui/dialog.tsx +0 -162
- package/src/client/components/ui/icon-picker.tsx +0 -485
- package/src/client/components/ui/icons-data.ts +0 -8476
- package/src/client/components/ui/input.tsx +0 -20
- package/src/client/components/ui/label.tsx +0 -20
- package/src/client/components/ui/popover.tsx +0 -91
- package/src/client/components/ui/select.tsx +0 -204
- package/src/client/components/ui/separator.tsx +0 -23
- package/src/client/components/ui/sheet.tsx +0 -141
- package/src/client/components/ui/sidebar.tsx +0 -722
- package/src/client/components/ui/skeleton.tsx +0 -13
- package/src/client/components/ui/sonner.tsx +0 -47
- package/src/client/components/ui/switch.tsx +0 -30
- package/src/client/components/ui/table.tsx +0 -116
- package/src/client/components/ui/tabs.tsx +0 -80
- package/src/client/components/ui/textarea.tsx +0 -18
- package/src/client/components/ui/tooltip.tsx +0 -68
- package/src/client/hooks/use-mobile.ts +0 -19
- package/src/client/index.css +0 -149
- package/src/client/index.ts +0 -7
- package/src/client/layouts/admin-layout.tsx +0 -93
- package/src/client/layouts/settings-layout.tsx +0 -104
- package/src/client/lib/api.ts +0 -72
- package/src/client/lib/utils.ts +0 -6
- package/src/client/main.tsx +0 -10
- package/src/client/pages/collection-detail.tsx +0 -634
- package/src/client/pages/collections.tsx +0 -180
- package/src/client/pages/dashboard.tsx +0 -133
- package/src/client/pages/device.tsx +0 -66
- package/src/client/pages/document-detail-page.tsx +0 -139
- package/src/client/pages/documents-page.tsx +0 -103
- package/src/client/pages/login.tsx +0 -345
- package/src/client/pages/settings.tsx +0 -65
- package/src/client/pages/setup.tsx +0 -129
- package/src/client/pages/signup.tsx +0 -188
- package/src/client/store/auth.ts +0 -30
- package/src/client/store/collections.ts +0 -13
- package/src/client/store/config.ts +0 -12
- package/src/client/store/fetcher.ts +0 -30
- package/src/client/store/router.ts +0 -95
- package/src/client/store/schema.ts +0 -39
- package/src/client/store/settings.ts +0 -31
- package/src/client/types.ts +0 -34
- package/src/db/dynamic.ts +0 -70
- package/src/db/index.ts +0 -16
- package/src/db/migrations/001_initial_schema.ts +0 -57
- package/src/db/migrations/002_auth_tables.ts +0 -84
- package/src/db/migrator.ts +0 -61
- package/src/db/schema.ts +0 -142
- package/src/index.ts +0 -12
- package/src/server/index.ts +0 -66
- package/src/types.ts +0 -20
- package/tests/css.test.ts +0 -21
- package/tests/modular.test.ts +0 -29
- package/tsconfig.json +0 -10
- /package/{style.css.d.ts → dist/style.css.d.ts} +0 -0
|
@@ -1,400 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { api } from '../../lib/api';
|
|
3
|
-
import { Button } from '../ui/button';
|
|
4
|
-
import { Input } from '../ui/input';
|
|
5
|
-
import { Badge } from '../ui/badge';
|
|
6
|
-
import { Checkbox } from '../ui/checkbox';
|
|
7
|
-
import { Label } from '../ui/label';
|
|
8
|
-
import {
|
|
9
|
-
Loader2Icon,
|
|
10
|
-
PlusIcon,
|
|
11
|
-
KeyIcon,
|
|
12
|
-
Trash2Icon,
|
|
13
|
-
CopyIcon,
|
|
14
|
-
CalendarIcon,
|
|
15
|
-
ShieldIcon,
|
|
16
|
-
} from 'lucide-react';
|
|
17
|
-
import {
|
|
18
|
-
Accordion,
|
|
19
|
-
AccordionContent,
|
|
20
|
-
AccordionItem,
|
|
21
|
-
AccordionTrigger,
|
|
22
|
-
} from '../ui/accordion';
|
|
23
|
-
|
|
24
|
-
export function APITokenSection() {
|
|
25
|
-
const [tokens, setTokens] = useState<any[]>([]);
|
|
26
|
-
const [collections, setCollections] = useState<any[]>([]);
|
|
27
|
-
const [name, setName] = useState('');
|
|
28
|
-
const [granularScopes, setGranularScopes] = useState<any[]>([]); // Array of { resource, actions }
|
|
29
|
-
const [newToken, setNewToken] = useState('');
|
|
30
|
-
const [loading, setLoading] = useState(false);
|
|
31
|
-
const [fetching, setFetching] = useState(true);
|
|
32
|
-
|
|
33
|
-
const systemResources = [
|
|
34
|
-
{
|
|
35
|
-
id: 'system.users',
|
|
36
|
-
label: 'Users & Roles',
|
|
37
|
-
desc: 'Manage administrative users',
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 'system.settings',
|
|
41
|
-
label: 'Site Settings',
|
|
42
|
-
desc: 'Modify global configurations',
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
id: 'system.collections',
|
|
46
|
-
label: 'Schema Engine',
|
|
47
|
-
desc: 'Create and modify collections',
|
|
48
|
-
},
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
const fetchTokens = async () => {
|
|
52
|
-
try {
|
|
53
|
-
const res = await api.get('/tokens');
|
|
54
|
-
const data = await res.json<any>();
|
|
55
|
-
setTokens(data.data || []);
|
|
56
|
-
} catch (err) {}
|
|
57
|
-
setFetching(false);
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const fetchCollections = async () => {
|
|
61
|
-
try {
|
|
62
|
-
const res = await api.get('/collections');
|
|
63
|
-
const data = await res.json<any>();
|
|
64
|
-
setCollections(data.data || []);
|
|
65
|
-
} catch (err) {}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
fetchTokens();
|
|
70
|
-
fetchCollections();
|
|
71
|
-
}, []);
|
|
72
|
-
|
|
73
|
-
const handleCreate = async (e: React.FormEvent) => {
|
|
74
|
-
e.preventDefault();
|
|
75
|
-
setLoading(true);
|
|
76
|
-
setNewToken('');
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const res = await api
|
|
80
|
-
.post('/tokens', {
|
|
81
|
-
json: {
|
|
82
|
-
name,
|
|
83
|
-
scopes: granularScopes,
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
const data = await res.json<any>();
|
|
87
|
-
setNewToken(data.data.token);
|
|
88
|
-
setName('');
|
|
89
|
-
setGranularScopes([]);
|
|
90
|
-
fetchTokens();
|
|
91
|
-
} catch (err) {
|
|
92
|
-
console.error('Token creation failed:', err);
|
|
93
|
-
alert('Failed to generate token. Check console for details.');
|
|
94
|
-
}
|
|
95
|
-
setLoading(false);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const handleRevoke = async (id: string) => {
|
|
99
|
-
if (
|
|
100
|
-
!confirm(
|
|
101
|
-
'Revoke this access token? This action is immediate and permanent.',
|
|
102
|
-
)
|
|
103
|
-
)
|
|
104
|
-
return;
|
|
105
|
-
await api.delete(`/tokens/${id}`);
|
|
106
|
-
fetchTokens();
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const togglePermission = (resource: string, action: string) => {
|
|
110
|
-
setGranularScopes((prev) => {
|
|
111
|
-
const existing = prev.find((s) => s.resource === resource);
|
|
112
|
-
if (existing) {
|
|
113
|
-
const newActions = existing.actions.includes(action)
|
|
114
|
-
? existing.actions.filter((a: string) => a !== action)
|
|
115
|
-
: [...existing.actions, action];
|
|
116
|
-
|
|
117
|
-
if (newActions.length === 0)
|
|
118
|
-
return prev.filter((s) => s.resource !== resource);
|
|
119
|
-
return prev.map((s) =>
|
|
120
|
-
s.resource === resource ? { ...s, actions: newActions } : s,
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
return [...prev, { resource, actions: [action] }];
|
|
124
|
-
});
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const isChecked = (resource: string, action: string) => {
|
|
128
|
-
return granularScopes
|
|
129
|
-
.find((s) => s.resource === resource)
|
|
130
|
-
?.actions.includes(action);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
if (fetching)
|
|
134
|
-
return (
|
|
135
|
-
<div className="flex items-center gap-2 text-muted-foreground">
|
|
136
|
-
<Loader2Icon className="size-4 animate-spin" /> Retrieving active
|
|
137
|
-
tokens...
|
|
138
|
-
</div>
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
return (
|
|
142
|
-
<div className="space-y-12">
|
|
143
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
144
|
-
<div>
|
|
145
|
-
<h3 className="text-sm font-bold uppercase tracking-widest text-foreground">
|
|
146
|
-
Issue Credential
|
|
147
|
-
</h3>
|
|
148
|
-
<p className="text-xs text-muted-foreground mt-2 leading-relaxed">
|
|
149
|
-
Personal Access Tokens allow secure programmatic access for external
|
|
150
|
-
scripts and AI agents.
|
|
151
|
-
</p>
|
|
152
|
-
</div>
|
|
153
|
-
<div className="md:col-span-2 bg-card border rounded-xl overflow-hidden shadow-sm">
|
|
154
|
-
<form onSubmit={handleCreate} className="p-8 space-y-8">
|
|
155
|
-
<div className="space-y-4">
|
|
156
|
-
<div className="grid gap-2">
|
|
157
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
158
|
-
Token Designation
|
|
159
|
-
</Label>
|
|
160
|
-
<Input
|
|
161
|
-
value={name}
|
|
162
|
-
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
|
163
|
-
setName(e.target.value)
|
|
164
|
-
}
|
|
165
|
-
required
|
|
166
|
-
placeholder="e.g. CI/CD Pipeline"
|
|
167
|
-
className="h-11 font-medium"
|
|
168
|
-
/>
|
|
169
|
-
</div>
|
|
170
|
-
|
|
171
|
-
<div className="grid gap-4 mt-6">
|
|
172
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
173
|
-
Permissions
|
|
174
|
-
</Label>
|
|
175
|
-
|
|
176
|
-
<Accordion className="w-full border rounded-lg bg-muted/5">
|
|
177
|
-
{/* Content Collections */}
|
|
178
|
-
<AccordionItem value="content" className="border-none px-4">
|
|
179
|
-
<AccordionTrigger className="hover:no-underline py-4">
|
|
180
|
-
<div className="flex items-center gap-3">
|
|
181
|
-
<ShieldIcon className="size-4 text-primary" />
|
|
182
|
-
<span className="text-xs font-bold uppercase tracking-wider">
|
|
183
|
-
Content Collections
|
|
184
|
-
</span>
|
|
185
|
-
</div>
|
|
186
|
-
</AccordionTrigger>
|
|
187
|
-
{collections.length === 0 ? (
|
|
188
|
-
<AccordionContent className="pb-6 space-y-4">
|
|
189
|
-
<p className="text-xs text-center text-muted-foreground font-medium">
|
|
190
|
-
No collections found
|
|
191
|
-
</p>
|
|
192
|
-
</AccordionContent>
|
|
193
|
-
) : (
|
|
194
|
-
<AccordionContent className="pb-6 space-y-4">
|
|
195
|
-
{collections.map((col) => (
|
|
196
|
-
<div
|
|
197
|
-
key={col.id}
|
|
198
|
-
className="flex items-center justify-between p-3 rounded-lg border bg-background group"
|
|
199
|
-
>
|
|
200
|
-
<div className="space-y-0.5">
|
|
201
|
-
<p className="text-xs font-bold capitalize">
|
|
202
|
-
{col.label}
|
|
203
|
-
</p>
|
|
204
|
-
<p className="text-[9px] text-muted-foreground font-mono uppercase opacity-50">
|
|
205
|
-
{col.slug}
|
|
206
|
-
</p>
|
|
207
|
-
</div>
|
|
208
|
-
<div className="flex items-center gap-4">
|
|
209
|
-
{['read', 'write', 'update', 'delete'].map(
|
|
210
|
-
(action) => (
|
|
211
|
-
<label
|
|
212
|
-
key={action}
|
|
213
|
-
className="flex items-center gap-2 cursor-pointer group/label"
|
|
214
|
-
>
|
|
215
|
-
<Checkbox
|
|
216
|
-
checked={isChecked(col.slug, action)}
|
|
217
|
-
onCheckedChange={() =>
|
|
218
|
-
togglePermission(col.slug, action)
|
|
219
|
-
}
|
|
220
|
-
/>
|
|
221
|
-
<span className="text-[10px] uppercase font-black tracking-tighter opacity-70 group-hover/label:opacity-100">
|
|
222
|
-
{action}
|
|
223
|
-
</span>
|
|
224
|
-
</label>
|
|
225
|
-
),
|
|
226
|
-
)}
|
|
227
|
-
</div>
|
|
228
|
-
</div>
|
|
229
|
-
))}
|
|
230
|
-
</AccordionContent>
|
|
231
|
-
)}
|
|
232
|
-
</AccordionItem>
|
|
233
|
-
|
|
234
|
-
{/* System Resources */}
|
|
235
|
-
<AccordionItem value="system" className="border-none px-4">
|
|
236
|
-
<AccordionTrigger className="hover:no-underline py-4">
|
|
237
|
-
<div className="flex items-center gap-3">
|
|
238
|
-
<ShieldIcon className="size-4 text-primary" />
|
|
239
|
-
<span className="text-xs font-bold uppercase tracking-wider">
|
|
240
|
-
System Resources
|
|
241
|
-
</span>
|
|
242
|
-
</div>
|
|
243
|
-
</AccordionTrigger>
|
|
244
|
-
<AccordionContent className="pb-6 space-y-4">
|
|
245
|
-
{systemResources.map((res) => (
|
|
246
|
-
<div
|
|
247
|
-
key={res.id}
|
|
248
|
-
className="flex items-center justify-between p-3 rounded-lg border bg-background group"
|
|
249
|
-
>
|
|
250
|
-
<div className="space-y-0.5">
|
|
251
|
-
<p className="text-xs font-bold">{res.label}</p>
|
|
252
|
-
<p className="text-[9px] text-muted-foreground leading-tight">
|
|
253
|
-
{res.desc}
|
|
254
|
-
</p>
|
|
255
|
-
</div>
|
|
256
|
-
<div className="flex items-center gap-4">
|
|
257
|
-
{['read', 'write', 'update'].map((action) => (
|
|
258
|
-
<label
|
|
259
|
-
key={action}
|
|
260
|
-
className="flex items-center gap-2 cursor-pointer"
|
|
261
|
-
>
|
|
262
|
-
<Checkbox
|
|
263
|
-
checked={isChecked(res.id, action)}
|
|
264
|
-
onCheckedChange={() =>
|
|
265
|
-
togglePermission(res.id, action)
|
|
266
|
-
}
|
|
267
|
-
/>
|
|
268
|
-
<span className="text-[10px] uppercase font-black tracking-tighter opacity-70">
|
|
269
|
-
{action}
|
|
270
|
-
</span>
|
|
271
|
-
</label>
|
|
272
|
-
))}
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
))}
|
|
276
|
-
</AccordionContent>
|
|
277
|
-
</AccordionItem>
|
|
278
|
-
</Accordion>
|
|
279
|
-
</div>
|
|
280
|
-
</div>
|
|
281
|
-
|
|
282
|
-
<Button
|
|
283
|
-
type="submit"
|
|
284
|
-
disabled={loading || !name || granularScopes.length === 0}
|
|
285
|
-
className="w-full h-11 gap-2 font-bold uppercase tracking-widest text-[10px]"
|
|
286
|
-
>
|
|
287
|
-
{loading ? (
|
|
288
|
-
<Loader2Icon className="size-3 animate-spin" />
|
|
289
|
-
) : (
|
|
290
|
-
<PlusIcon className="size-4" />
|
|
291
|
-
)}
|
|
292
|
-
Generate Access Secret
|
|
293
|
-
</Button>
|
|
294
|
-
|
|
295
|
-
{newToken && (
|
|
296
|
-
<div className="mt-8 p-6 bg-primary/5 border border-primary/20 rounded-xl">
|
|
297
|
-
<div className="flex items-center gap-2 text-primary mb-3">
|
|
298
|
-
<ShieldIcon className="size-4" />
|
|
299
|
-
<p className="text-[11px] font-black uppercase tracking-widest">
|
|
300
|
-
Secret Vaulted Successfully
|
|
301
|
-
</p>
|
|
302
|
-
</div>
|
|
303
|
-
<div className="relative group">
|
|
304
|
-
<code className="text-xs break-all text-foreground bg-background p-4 rounded-lg block border font-mono">
|
|
305
|
-
{newToken}
|
|
306
|
-
</code>
|
|
307
|
-
<Button
|
|
308
|
-
variant="ghost"
|
|
309
|
-
size="icon"
|
|
310
|
-
className="absolute top-2 right-2 size-8 text-muted-foreground hover:text-primary"
|
|
311
|
-
onClick={() => navigator.clipboard.writeText(newToken)}
|
|
312
|
-
>
|
|
313
|
-
<CopyIcon className="size-3.5" />
|
|
314
|
-
</Button>
|
|
315
|
-
</div>
|
|
316
|
-
<p className="text-[10px] text-primary/70 font-bold uppercase mt-4 tracking-tighter italic">
|
|
317
|
-
Warning: This secret will not be displayed again. Store it
|
|
318
|
-
securely.
|
|
319
|
-
</p>
|
|
320
|
-
</div>
|
|
321
|
-
)}
|
|
322
|
-
</form>
|
|
323
|
-
</div>
|
|
324
|
-
</div>
|
|
325
|
-
|
|
326
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 border-t pt-12">
|
|
327
|
-
<div>
|
|
328
|
-
<h3 className="text-sm font-bold uppercase tracking-widest text-foreground">
|
|
329
|
-
Active Ledger
|
|
330
|
-
</h3>
|
|
331
|
-
<p className="text-xs text-muted-foreground mt-2 leading-relaxed">
|
|
332
|
-
Review and manage active credentials for your account.
|
|
333
|
-
</p>
|
|
334
|
-
</div>
|
|
335
|
-
<div className="md:col-span-2 space-y-4">
|
|
336
|
-
{tokens.map((t: any) => (
|
|
337
|
-
<div
|
|
338
|
-
key={t.id}
|
|
339
|
-
className="bg-card border rounded-xl p-6 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-6 group hover:border-primary/20 transition-colors"
|
|
340
|
-
>
|
|
341
|
-
<div className="space-y-2">
|
|
342
|
-
<div className="flex items-center gap-3">
|
|
343
|
-
<div className="size-8 bg-primary/5 rounded flex items-center justify-center text-primary border border-primary/10">
|
|
344
|
-
<KeyIcon className="size-4" />
|
|
345
|
-
</div>
|
|
346
|
-
<p className="text-sm font-bold text-foreground tracking-tight">
|
|
347
|
-
{t.name}
|
|
348
|
-
</p>
|
|
349
|
-
</div>
|
|
350
|
-
<div className="flex flex-wrap items-center gap-2 pl-1">
|
|
351
|
-
<span className="text-[10px] font-mono text-muted-foreground uppercase opacity-50 px-1 border rounded">
|
|
352
|
-
{t.id}
|
|
353
|
-
</span>
|
|
354
|
-
{Array.isArray(t.scopes) &&
|
|
355
|
-
t.scopes.map((s: any, idx: number) => {
|
|
356
|
-
const label =
|
|
357
|
-
typeof s === 'string'
|
|
358
|
-
? s
|
|
359
|
-
: `${s.resource}:${s.actions.join(',')}`;
|
|
360
|
-
return (
|
|
361
|
-
<Badge
|
|
362
|
-
key={idx}
|
|
363
|
-
variant="outline"
|
|
364
|
-
className="text-[8px] uppercase font-bold tracking-widest px-1.5 h-4 border-primary/20 text-primary/60"
|
|
365
|
-
>
|
|
366
|
-
{label}
|
|
367
|
-
</Badge>
|
|
368
|
-
);
|
|
369
|
-
})}
|
|
370
|
-
</div>
|
|
371
|
-
<div className="flex items-center gap-3 pl-1 pt-1 opacity-50">
|
|
372
|
-
<div className="flex items-center gap-1.5 text-[10px] font-semibold uppercase tracking-tighter text-muted-foreground">
|
|
373
|
-
<CalendarIcon className="size-3" />
|
|
374
|
-
Created: {new Date(t.created_at).toLocaleDateString()}
|
|
375
|
-
</div>
|
|
376
|
-
</div>
|
|
377
|
-
</div>
|
|
378
|
-
<Button
|
|
379
|
-
variant="ghost"
|
|
380
|
-
size="sm"
|
|
381
|
-
onClick={() => handleRevoke(t.id)}
|
|
382
|
-
className="text-[10px] font-black uppercase tracking-widest h-8 px-3 text-muted-foreground hover:bg-destructive/5 hover:text-destructive"
|
|
383
|
-
>
|
|
384
|
-
Revoke Access
|
|
385
|
-
</Button>
|
|
386
|
-
</div>
|
|
387
|
-
))}
|
|
388
|
-
|
|
389
|
-
{tokens.length === 0 && (
|
|
390
|
-
<div className="text-center py-12 border-2 border-dashed rounded-xl opacity-30">
|
|
391
|
-
<p className="text-xs font-bold uppercase tracking-widest">
|
|
392
|
-
No active credentials recorded
|
|
393
|
-
</p>
|
|
394
|
-
</div>
|
|
395
|
-
)}
|
|
396
|
-
</div>
|
|
397
|
-
</div>
|
|
398
|
-
</div>
|
|
399
|
-
);
|
|
400
|
-
}
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import { api } from '../../lib/api';
|
|
3
|
-
import { Button } from '../ui/button';
|
|
4
|
-
import { Input } from '../ui/input';
|
|
5
|
-
import { Label } from '../ui/label';
|
|
6
|
-
import { Switch } from '../ui/switch';
|
|
7
|
-
import {
|
|
8
|
-
Loader2Icon,
|
|
9
|
-
SaveIcon,
|
|
10
|
-
LayoutIcon,
|
|
11
|
-
GlobeIcon,
|
|
12
|
-
SettingsIcon,
|
|
13
|
-
ShieldAlertIcon,
|
|
14
|
-
} from 'lucide-react';
|
|
15
|
-
import { updateSettingsLocally } from '../../store/settings';
|
|
16
|
-
|
|
17
|
-
export function GeneralSection() {
|
|
18
|
-
const [settings, setSettings] = useState<any>({});
|
|
19
|
-
const [loading, setLoading] = useState(true);
|
|
20
|
-
const [saving, setSaving] = useState(false);
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
const fetchSettings = async () => {
|
|
24
|
-
try {
|
|
25
|
-
const data = await api.get('/settings').json<any>();
|
|
26
|
-
setSettings(data);
|
|
27
|
-
} catch (err) {}
|
|
28
|
-
setLoading(false);
|
|
29
|
-
};
|
|
30
|
-
fetchSettings();
|
|
31
|
-
}, []);
|
|
32
|
-
|
|
33
|
-
const handleSave = async (e?: React.FormEvent) => {
|
|
34
|
-
if (e) e.preventDefault();
|
|
35
|
-
setSaving(true);
|
|
36
|
-
try {
|
|
37
|
-
await api.patch('/api/settings', { json: settings });
|
|
38
|
-
updateSettingsLocally(settings);
|
|
39
|
-
} catch (err) {}
|
|
40
|
-
setSaving(false);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const update = (key: string, value: string) => {
|
|
44
|
-
setSettings((prev: any) => ({ ...prev, [key]: value }));
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
if (loading)
|
|
48
|
-
return (
|
|
49
|
-
<div className="flex items-center gap-2 text-muted-foreground">
|
|
50
|
-
<Loader2Icon className="size-4 animate-spin" /> Retrieving site
|
|
51
|
-
identity...
|
|
52
|
-
</div>
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div className="space-y-12">
|
|
57
|
-
<form onSubmit={handleSave} className="space-y-12">
|
|
58
|
-
{/* Site Identity */}
|
|
59
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
|
60
|
-
<div>
|
|
61
|
-
<h3 className="text-sm font-bold uppercase tracking-widest text-foreground">
|
|
62
|
-
Site Identity
|
|
63
|
-
</h3>
|
|
64
|
-
<p className="text-xs text-muted-foreground mt-2 leading-relaxed">
|
|
65
|
-
Fundamental information used in headers, metadata, and browser
|
|
66
|
-
tabs.
|
|
67
|
-
</p>
|
|
68
|
-
</div>
|
|
69
|
-
<div className="md:col-span-2 space-y-6 bg-card border rounded-xl p-8 shadow-sm">
|
|
70
|
-
<div className="grid gap-6 sm:grid-cols-2">
|
|
71
|
-
<div className="grid gap-2">
|
|
72
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
73
|
-
Site Title
|
|
74
|
-
</Label>
|
|
75
|
-
<Input
|
|
76
|
-
value={settings['flare:site_title'] || ''}
|
|
77
|
-
onChange={(e) => update('flare:site_title', e.target.value)}
|
|
78
|
-
placeholder="My Awesome Blog"
|
|
79
|
-
/>
|
|
80
|
-
</div>
|
|
81
|
-
<div className="grid gap-2">
|
|
82
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
83
|
-
Tagline
|
|
84
|
-
</Label>
|
|
85
|
-
<Input
|
|
86
|
-
value={settings['flare:site_tagline'] || ''}
|
|
87
|
-
onChange={(e) => update('flare:site_tagline', e.target.value)}
|
|
88
|
-
placeholder="Thoughts on the digital world"
|
|
89
|
-
/>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
<div className="grid gap-2">
|
|
93
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
94
|
-
Base URL
|
|
95
|
-
</Label>
|
|
96
|
-
<Input
|
|
97
|
-
value={settings['flare:site_url'] || ''}
|
|
98
|
-
onChange={(e) => update('flare:site_url', e.target.value)}
|
|
99
|
-
placeholder="https://flarecms.dev"
|
|
100
|
-
/>
|
|
101
|
-
<p className="text-[9px] text-muted-foreground italic">
|
|
102
|
-
Important for canonical links and XML sitemaps.
|
|
103
|
-
</p>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
{/* Branding & Assets */}
|
|
109
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 border-t pt-12">
|
|
110
|
-
<div>
|
|
111
|
-
<h3 className="text-sm font-bold uppercase tracking-widest text-foreground">
|
|
112
|
-
Design Assets
|
|
113
|
-
</h3>
|
|
114
|
-
<p className="text-xs text-muted-foreground mt-2 leading-relaxed">
|
|
115
|
-
Visual identity markers for your public site and administrative
|
|
116
|
-
panel.
|
|
117
|
-
</p>
|
|
118
|
-
</div>
|
|
119
|
-
<div className="md:col-span-2 space-y-6 bg-card border rounded-xl p-8 shadow-sm">
|
|
120
|
-
<div className="grid gap-6">
|
|
121
|
-
<div className="grid gap-2">
|
|
122
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
123
|
-
Public Logo URL
|
|
124
|
-
</Label>
|
|
125
|
-
<Input
|
|
126
|
-
value={settings['flare:site_logo'] || ''}
|
|
127
|
-
onChange={(e) => update('flare:site_logo', e.target.value)}
|
|
128
|
-
placeholder="https://cdn.com/logo.png"
|
|
129
|
-
/>
|
|
130
|
-
</div>
|
|
131
|
-
<div className="grid gap-2">
|
|
132
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
133
|
-
Admin Panel Logo URL
|
|
134
|
-
</Label>
|
|
135
|
-
<Input
|
|
136
|
-
value={settings['flare:admin_logo'] || ''}
|
|
137
|
-
onChange={(e) => update('flare:admin_logo', e.target.value)}
|
|
138
|
-
placeholder="https://cdn.com/admin-logo.png"
|
|
139
|
-
/>
|
|
140
|
-
</div>
|
|
141
|
-
<div className="grid gap-2">
|
|
142
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
143
|
-
Favicon Shortcut URL
|
|
144
|
-
</Label>
|
|
145
|
-
<Input
|
|
146
|
-
value={settings['flare:site_favicon'] || ''}
|
|
147
|
-
onChange={(e) => update('flare:site_favicon', e.target.value)}
|
|
148
|
-
placeholder="https://cdn.com/favicon.ico"
|
|
149
|
-
/>
|
|
150
|
-
</div>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
{/* System Operations */}
|
|
156
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 border-t pt-12">
|
|
157
|
-
<div>
|
|
158
|
-
<h3 className="text-sm font-bold uppercase tracking-widest text-foreground">
|
|
159
|
-
Operations
|
|
160
|
-
</h3>
|
|
161
|
-
<p className="text-xs text-muted-foreground mt-2 leading-relaxed">
|
|
162
|
-
Site-wide status and SEO architecture configuration.
|
|
163
|
-
</p>
|
|
164
|
-
</div>
|
|
165
|
-
<div className="md:col-span-2 space-y-6">
|
|
166
|
-
<div className="bg-card border rounded-xl p-8 shadow-sm space-y-6">
|
|
167
|
-
<div className="flex items-center justify-between">
|
|
168
|
-
<div className="space-y-1">
|
|
169
|
-
<p className="text-sm font-bold text-foreground">
|
|
170
|
-
Maintenance Mode
|
|
171
|
-
</p>
|
|
172
|
-
<p className="text-[10px] text-muted-foreground/60">
|
|
173
|
-
Take your site offline for users while you work on it.
|
|
174
|
-
Admins still have access.
|
|
175
|
-
</p>
|
|
176
|
-
</div>
|
|
177
|
-
<Switch
|
|
178
|
-
checked={settings['flare:signup_enabled'] === 'true'}
|
|
179
|
-
onCheckedChange={(checked: boolean) =>
|
|
180
|
-
update('flare:signup_enabled', String(checked))
|
|
181
|
-
}
|
|
182
|
-
/>
|
|
183
|
-
</div>
|
|
184
|
-
|
|
185
|
-
<div className="pt-6 border-t space-y-3">
|
|
186
|
-
<Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
|
|
187
|
-
Site Title SEO Pattern
|
|
188
|
-
</Label>
|
|
189
|
-
<Input
|
|
190
|
-
value={
|
|
191
|
-
settings['flare:site_title_pattern'] ||
|
|
192
|
-
'%title% | %site_name%'
|
|
193
|
-
}
|
|
194
|
-
onChange={(e) =>
|
|
195
|
-
update('flare:site_title_pattern', e.target.value)
|
|
196
|
-
}
|
|
197
|
-
placeholder="%title% | %site_name%"
|
|
198
|
-
/>
|
|
199
|
-
<p className="text-[9px] text-muted-foreground/60">
|
|
200
|
-
Supported vars: %title%, %site_name%, %tagline%
|
|
201
|
-
</p>
|
|
202
|
-
</div>
|
|
203
|
-
</div>
|
|
204
|
-
</div>
|
|
205
|
-
</div>
|
|
206
|
-
|
|
207
|
-
<div className="flex justify-end sticky bottom-0 py-4 bg-background/80 backdrop-blur-sm border-t mt-12">
|
|
208
|
-
<Button
|
|
209
|
-
disabled={saving}
|
|
210
|
-
size="sm"
|
|
211
|
-
className="gap-2 font-bold uppercase tracking-widest text-[10px] h-10 px-6"
|
|
212
|
-
>
|
|
213
|
-
{saving ? (
|
|
214
|
-
<Loader2Icon className="size-3 animate-spin" />
|
|
215
|
-
) : (
|
|
216
|
-
<SaveIcon className="size-3" />
|
|
217
|
-
)}
|
|
218
|
-
Sync Site Identity
|
|
219
|
-
</Button>
|
|
220
|
-
</div>
|
|
221
|
-
</form>
|
|
222
|
-
</div>
|
|
223
|
-
);
|
|
224
|
-
}
|