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,345 +0,0 @@
|
|
|
1
|
-
import { apiFetch } from '../lib/api';
|
|
2
|
-
import { $auth } from '../store/auth';
|
|
3
|
-
import { $router, navigate } from '../store/router';
|
|
4
|
-
import { $basePath } from '../store/config';
|
|
5
|
-
import { startAuthentication } from '@simplewebauthn/browser';
|
|
6
|
-
import {
|
|
7
|
-
Command,
|
|
8
|
-
FingerprintIcon,
|
|
9
|
-
KeyIcon,
|
|
10
|
-
Loader2Icon,
|
|
11
|
-
LockIcon,
|
|
12
|
-
MailIcon,
|
|
13
|
-
ShieldCheckIcon,
|
|
14
|
-
} from 'lucide-react';
|
|
15
|
-
import React, { useEffect, useState } from 'react';
|
|
16
|
-
|
|
17
|
-
import { Button } from '../components/ui/button';
|
|
18
|
-
import { Card, CardContent, CardFooter } from '../components/ui/card';
|
|
19
|
-
import { Input } from '../components/ui/input';
|
|
20
|
-
import { Label } from '../components/ui/label';
|
|
21
|
-
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
|
|
22
|
-
|
|
23
|
-
export function LoginPage() {
|
|
24
|
-
const [email, setEmail] = useState('');
|
|
25
|
-
const [password, setPassword] = useState('');
|
|
26
|
-
const [loading, setLoading] = useState(false);
|
|
27
|
-
const [passkeyLoading, setPasskeyLoading] = useState(false);
|
|
28
|
-
const [error, setError] = useState('');
|
|
29
|
-
const [signupEnabled, setSignupEnabled] = useState(false);
|
|
30
|
-
const [activeTab, setActiveTab] = useState('credentials');
|
|
31
|
-
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
async function checkSignup() {
|
|
34
|
-
try {
|
|
35
|
-
const res = await apiFetch('/auth/registration-settings');
|
|
36
|
-
const { data } = await res.json();
|
|
37
|
-
setSignupEnabled(data.enabled === 'true');
|
|
38
|
-
} catch (err) {
|
|
39
|
-
setSignupEnabled(false);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
checkSignup();
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
const handleLogin = async (e: React.FormEvent) => {
|
|
46
|
-
e.preventDefault();
|
|
47
|
-
setLoading(true);
|
|
48
|
-
setError('');
|
|
49
|
-
|
|
50
|
-
try {
|
|
51
|
-
const response = await apiFetch('/auth/login', {
|
|
52
|
-
method: 'POST',
|
|
53
|
-
headers: { 'Content-Type': 'application/json' },
|
|
54
|
-
body: JSON.stringify({ email, password }),
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const data = await response.json();
|
|
58
|
-
|
|
59
|
-
if (response.ok) {
|
|
60
|
-
$auth.set({
|
|
61
|
-
token: data.data.token || 'cookie',
|
|
62
|
-
user: data.data.user || { email, role: 'admin' },
|
|
63
|
-
});
|
|
64
|
-
} else {
|
|
65
|
-
if (data.code === 'SETUP_REQUIRED') {
|
|
66
|
-
window.location.href = `${$basePath.get()}/setup`;
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
setError(data.error || 'Invalid credentials');
|
|
70
|
-
}
|
|
71
|
-
} catch (err) {
|
|
72
|
-
setError('Connection failed. Is the API running?');
|
|
73
|
-
} finally {
|
|
74
|
-
setLoading(false);
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const handlePasskeyLogin = async () => {
|
|
79
|
-
if (!email) {
|
|
80
|
-
setError('Enter your email to use Passkey login');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
setPasskeyLoading(true);
|
|
85
|
-
setError('');
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const optionsResponse = await apiFetch('/auth/passkey/options', {
|
|
89
|
-
method: 'POST',
|
|
90
|
-
headers: { 'Content-Type': 'application/json' },
|
|
91
|
-
body: JSON.stringify({ email }),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
if (!optionsResponse.ok) {
|
|
95
|
-
const err = await optionsResponse.json();
|
|
96
|
-
throw new Error(err.error || 'Failed to get passkey options');
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const data = await optionsResponse.json();
|
|
100
|
-
const asseResp = await startAuthentication({ optionsJSON: data.data });
|
|
101
|
-
|
|
102
|
-
const verifyResponse = await apiFetch('/auth/passkey/verify', {
|
|
103
|
-
method: 'POST',
|
|
104
|
-
headers: { 'Content-Type': 'application/json' },
|
|
105
|
-
body: JSON.stringify({
|
|
106
|
-
email,
|
|
107
|
-
response: asseResp,
|
|
108
|
-
}),
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
if (verifyResponse.ok) {
|
|
112
|
-
const data = await verifyResponse.json();
|
|
113
|
-
$auth.set({
|
|
114
|
-
token: 'cookie',
|
|
115
|
-
user: data.data.user || { email, role: 'admin' },
|
|
116
|
-
});
|
|
117
|
-
} else {
|
|
118
|
-
const err = await verifyResponse.json();
|
|
119
|
-
setError(err.error || 'Passkey verification failed');
|
|
120
|
-
}
|
|
121
|
-
} catch (err: any) {
|
|
122
|
-
console.error(err);
|
|
123
|
-
setError(err.message || 'Passkey authentication failed');
|
|
124
|
-
} finally {
|
|
125
|
-
setPasskeyLoading(false);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
return (
|
|
130
|
-
<div className="min-h-screen bg-[oklch(0.985_0_0)] flex flex-col items-center justify-center p-6 antialiased selection:bg-primary/10">
|
|
131
|
-
<div className="w-full max-w-[400px] space-y-6">
|
|
132
|
-
<div className="flex flex-col items-center gap-4 mb-2">
|
|
133
|
-
<div className="size-12 rounded-2xl bg-primary text-primary-foreground flex items-center justify-center shadow-2xl shadow-primary/20">
|
|
134
|
-
<ShieldCheckIcon className="size-6" />
|
|
135
|
-
</div>
|
|
136
|
-
<div className="text-center">
|
|
137
|
-
<h1 className="text-xl font-bold tracking-tight">Access Flare</h1>
|
|
138
|
-
<p className="text-[10px] text-muted-foreground uppercase font-bold tracking-widest mt-1 opacity-60">
|
|
139
|
-
Nexus Administrative Portal
|
|
140
|
-
</p>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
|
|
144
|
-
<Card className="py-0 shadow-xl border-border/50 overflow-hidden bg-background/80 backdrop-blur-xl">
|
|
145
|
-
<Tabs
|
|
146
|
-
value={activeTab}
|
|
147
|
-
onValueChange={setActiveTab}
|
|
148
|
-
className="w-full"
|
|
149
|
-
>
|
|
150
|
-
<div className="px-8 pt-8">
|
|
151
|
-
<TabsList className="grid grid-cols-2 w-full h-11 bg-muted/30 p-1 rounded-xl">
|
|
152
|
-
<TabsTrigger
|
|
153
|
-
value="credentials"
|
|
154
|
-
className="text-[10px] font-bold uppercase tracking-wider gap-2"
|
|
155
|
-
>
|
|
156
|
-
<KeyIcon className="size-3" />
|
|
157
|
-
Credentials
|
|
158
|
-
</TabsTrigger>
|
|
159
|
-
<TabsTrigger
|
|
160
|
-
value="passkey"
|
|
161
|
-
className="text-[10px] font-bold uppercase tracking-wider gap-2"
|
|
162
|
-
>
|
|
163
|
-
<FingerprintIcon className="size-3" />
|
|
164
|
-
Secure Key
|
|
165
|
-
</TabsTrigger>
|
|
166
|
-
</TabsList>
|
|
167
|
-
</div>
|
|
168
|
-
|
|
169
|
-
<CardContent className="px-8 pt-6 pb-8 min-h-[220px]">
|
|
170
|
-
<TabsContent
|
|
171
|
-
value="credentials"
|
|
172
|
-
className="space-y-4 focus-visible:outline-none"
|
|
173
|
-
>
|
|
174
|
-
<form onSubmit={handleLogin} className="space-y-4">
|
|
175
|
-
<div className="space-y-2">
|
|
176
|
-
<Label
|
|
177
|
-
htmlFor="email-cred"
|
|
178
|
-
className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/70 ml-1"
|
|
179
|
-
>
|
|
180
|
-
Gateway Email
|
|
181
|
-
</Label>
|
|
182
|
-
<div className="relative group">
|
|
183
|
-
<MailIcon className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground/30 group-focus-within:text-primary transition-colors" />
|
|
184
|
-
<Input
|
|
185
|
-
id="email-cred"
|
|
186
|
-
type="email"
|
|
187
|
-
placeholder="name@nexus.io"
|
|
188
|
-
value={email}
|
|
189
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
190
|
-
className="pl-10 h-11 text-sm bg-muted/5 border-muted-foreground/10 focus:bg-background transition-all"
|
|
191
|
-
required
|
|
192
|
-
/>
|
|
193
|
-
</div>
|
|
194
|
-
</div>
|
|
195
|
-
<div className="space-y-2">
|
|
196
|
-
<div className="flex items-center justify-between ml-1">
|
|
197
|
-
<Label
|
|
198
|
-
htmlFor="password"
|
|
199
|
-
className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/70"
|
|
200
|
-
>
|
|
201
|
-
Authorization Key
|
|
202
|
-
</Label>
|
|
203
|
-
<button
|
|
204
|
-
type="button"
|
|
205
|
-
className="text-[9px] text-primary font-bold uppercase tracking-widest opacity-40 hover:opacity-100 transition-opacity"
|
|
206
|
-
>
|
|
207
|
-
Lost Access?
|
|
208
|
-
</button>
|
|
209
|
-
</div>
|
|
210
|
-
<div className="relative group">
|
|
211
|
-
<LockIcon className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground/30 group-focus-within:text-primary transition-colors" />
|
|
212
|
-
<Input
|
|
213
|
-
id="password"
|
|
214
|
-
type="password"
|
|
215
|
-
placeholder="••••••••"
|
|
216
|
-
value={password}
|
|
217
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
218
|
-
className="pl-10 h-11 text-sm bg-muted/5 border-muted-foreground/10 focus:bg-background transition-all"
|
|
219
|
-
required
|
|
220
|
-
/>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
|
|
224
|
-
{error && activeTab === 'credentials' && (
|
|
225
|
-
<div className="p-3 rounded-lg bg-destructive/5 border border-destructive/10 text-destructive text-[10px] font-bold text-center uppercase tracking-widest">
|
|
226
|
-
{error}
|
|
227
|
-
</div>
|
|
228
|
-
)}
|
|
229
|
-
|
|
230
|
-
<Button
|
|
231
|
-
type="submit"
|
|
232
|
-
className="w-full h-11 font-black text-[10px] uppercase tracking-[0.2em] shadow-lg shadow-primary/20"
|
|
233
|
-
disabled={loading}
|
|
234
|
-
>
|
|
235
|
-
{loading ? (
|
|
236
|
-
<Loader2Icon className="size-4 animate-spin" />
|
|
237
|
-
) : (
|
|
238
|
-
'Authorize Session'
|
|
239
|
-
)}
|
|
240
|
-
</Button>
|
|
241
|
-
</form>
|
|
242
|
-
</TabsContent>
|
|
243
|
-
|
|
244
|
-
<TabsContent
|
|
245
|
-
value="passkey"
|
|
246
|
-
className="space-y-6 focus-visible:outline-none"
|
|
247
|
-
>
|
|
248
|
-
<div className="space-y-4">
|
|
249
|
-
<div className="space-y-2 text-center pb-2">
|
|
250
|
-
<p className="text-[11px] text-muted-foreground font-medium leading-relaxed">
|
|
251
|
-
Authenticate instantly using your device's biometric
|
|
252
|
-
sensors or security key.
|
|
253
|
-
</p>
|
|
254
|
-
</div>
|
|
255
|
-
|
|
256
|
-
<div className="space-y-2">
|
|
257
|
-
<Label
|
|
258
|
-
htmlFor="email-pass"
|
|
259
|
-
className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground/70 ml-1"
|
|
260
|
-
>
|
|
261
|
-
Registered Email
|
|
262
|
-
</Label>
|
|
263
|
-
<div className="relative group">
|
|
264
|
-
<MailIcon className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground/30 group-focus-within:text-primary transition-colors" />
|
|
265
|
-
<Input
|
|
266
|
-
id="email-pass"
|
|
267
|
-
type="email"
|
|
268
|
-
placeholder="name@nexus.io"
|
|
269
|
-
value={email}
|
|
270
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
271
|
-
className="pl-10 h-11 text-sm bg-muted/5 border-muted-foreground/10 focus:bg-background transition-all"
|
|
272
|
-
/>
|
|
273
|
-
</div>
|
|
274
|
-
</div>
|
|
275
|
-
|
|
276
|
-
{error && activeTab === 'passkey' && (
|
|
277
|
-
<div className="p-3 rounded-lg bg-destructive/5 border border-destructive/10 text-destructive text-[10px] font-bold text-center uppercase tracking-widest">
|
|
278
|
-
{error}
|
|
279
|
-
</div>
|
|
280
|
-
)}
|
|
281
|
-
|
|
282
|
-
<div className="grid grid-cols-1 gap-3 pt-2">
|
|
283
|
-
<Button
|
|
284
|
-
type="button"
|
|
285
|
-
className="w-full h-14 font-black text-[11px] uppercase tracking-[0.2em] shadow-lg shadow-primary/10 flex flex-col gap-1 items-center justify-center py-8"
|
|
286
|
-
onClick={handlePasskeyLogin}
|
|
287
|
-
disabled={passkeyLoading}
|
|
288
|
-
>
|
|
289
|
-
{passkeyLoading ? (
|
|
290
|
-
<Loader2Icon className="size-5 animate-spin" />
|
|
291
|
-
) : (
|
|
292
|
-
<>
|
|
293
|
-
<FingerprintIcon className="size-6" />
|
|
294
|
-
<span>Identify with Passkey</span>
|
|
295
|
-
</>
|
|
296
|
-
)}
|
|
297
|
-
</Button>
|
|
298
|
-
</div>
|
|
299
|
-
</div>
|
|
300
|
-
</TabsContent>
|
|
301
|
-
</CardContent>
|
|
302
|
-
</Tabs>
|
|
303
|
-
|
|
304
|
-
<CardFooter className="p-0 flex flex-col gap-6 px-8 pb-8 bg-muted/5 border-t border-muted-foreground/5">
|
|
305
|
-
<div className="relative w-full">
|
|
306
|
-
<div className="absolute inset-0 flex items-center">
|
|
307
|
-
<span className="w-full border-t border-muted-foreground/10" />
|
|
308
|
-
</div>
|
|
309
|
-
<div className="relative flex justify-center text-[9px] uppercase tracking-[0.3em]">
|
|
310
|
-
<span className="bg-background px-4 text-muted-foreground/40 font-bold">
|
|
311
|
-
External Auth
|
|
312
|
-
</span>
|
|
313
|
-
</div>
|
|
314
|
-
</div>
|
|
315
|
-
|
|
316
|
-
<Button
|
|
317
|
-
type="button"
|
|
318
|
-
variant="outline"
|
|
319
|
-
className="w-full h-11 text-[10px] font-bold uppercase tracking-widest flex gap-3 border-muted-foreground/20 hover:bg-background shadow-xs"
|
|
320
|
-
onClick={() => (window.location.href = '/api/oauth/github/login')}
|
|
321
|
-
>
|
|
322
|
-
<Command className="size-3.5" />
|
|
323
|
-
Continue with GitHub
|
|
324
|
-
</Button>
|
|
325
|
-
|
|
326
|
-
{signupEnabled && (
|
|
327
|
-
<div className="flex flex-col items-center gap-3 w-full">
|
|
328
|
-
<p className="text-[9px] text-muted-foreground/40 font-bold uppercase tracking-[0.2em]">
|
|
329
|
-
New Administrator?
|
|
330
|
-
</p>
|
|
331
|
-
<Button
|
|
332
|
-
variant="ghost"
|
|
333
|
-
className="w-full h-11 text-[10px] font-black uppercase tracking-[0.2em] text-primary hover:bg-primary/5 border border-primary/20 border-dashed"
|
|
334
|
-
onClick={() => navigate('signup')}
|
|
335
|
-
>
|
|
336
|
-
Deploy New Identity
|
|
337
|
-
</Button>
|
|
338
|
-
</div>
|
|
339
|
-
)}
|
|
340
|
-
</CardFooter>
|
|
341
|
-
</Card>
|
|
342
|
-
</div>
|
|
343
|
-
</div>
|
|
344
|
-
);
|
|
345
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { useStore } from '@nanostores/react';
|
|
2
|
-
import { $router } from '../store/router';
|
|
3
|
-
import { SettingsLayout } from '../layouts/settings-layout';
|
|
4
|
-
import { GeneralSection } from '../components/settings/general-section';
|
|
5
|
-
import { SEOSection } from '../components/settings/seo-section';
|
|
6
|
-
import { SecuritySection } from '../components/settings/security-section';
|
|
7
|
-
import { SignupSection } from '../components/settings/signup-section';
|
|
8
|
-
import { APITokenSection } from '../components/settings/api-token-section';
|
|
9
|
-
|
|
10
|
-
export function SettingsPage() {
|
|
11
|
-
const page = useStore($router);
|
|
12
|
-
|
|
13
|
-
if (!page) return null;
|
|
14
|
-
|
|
15
|
-
// Map routes to sections
|
|
16
|
-
switch (page.route) {
|
|
17
|
-
case 'settings_general':
|
|
18
|
-
return (
|
|
19
|
-
<SettingsLayout
|
|
20
|
-
title="General Configuration"
|
|
21
|
-
subtitle="Manage your platform's core identity and reading preferences."
|
|
22
|
-
>
|
|
23
|
-
<GeneralSection />
|
|
24
|
-
</SettingsLayout>
|
|
25
|
-
);
|
|
26
|
-
case 'settings_seo':
|
|
27
|
-
return (
|
|
28
|
-
<SettingsLayout
|
|
29
|
-
title="Search Engine Optimization"
|
|
30
|
-
subtitle="Optimize how your platform communicates with global indexers."
|
|
31
|
-
>
|
|
32
|
-
<SEOSection />
|
|
33
|
-
</SettingsLayout>
|
|
34
|
-
);
|
|
35
|
-
case 'settings_security':
|
|
36
|
-
return (
|
|
37
|
-
<SettingsLayout
|
|
38
|
-
title="Security & Access"
|
|
39
|
-
subtitle="Protect your administrative environment with modern credentials."
|
|
40
|
-
>
|
|
41
|
-
<SecuritySection />
|
|
42
|
-
</SettingsLayout>
|
|
43
|
-
);
|
|
44
|
-
case 'settings_signup':
|
|
45
|
-
return (
|
|
46
|
-
<SettingsLayout
|
|
47
|
-
title="Registration Policy"
|
|
48
|
-
subtitle="Control automated user onboarding and domain domain policies."
|
|
49
|
-
>
|
|
50
|
-
<SignupSection />
|
|
51
|
-
</SettingsLayout>
|
|
52
|
-
);
|
|
53
|
-
case 'settings':
|
|
54
|
-
case 'settings_api':
|
|
55
|
-
default:
|
|
56
|
-
return (
|
|
57
|
-
<SettingsLayout
|
|
58
|
-
title="Administrative API"
|
|
59
|
-
subtitle="Manage Personal Access Tokens for secure programmatic integration."
|
|
60
|
-
>
|
|
61
|
-
<APITokenSection />
|
|
62
|
-
</SettingsLayout>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
2
|
-
import type { FormEvent } from 'react';
|
|
3
|
-
import { apiFetch } from '../lib/api';
|
|
4
|
-
import { $router } from '../store/router';
|
|
5
|
-
import { $auth } from '../store/auth';
|
|
6
|
-
import { $basePath } from '../store/config';
|
|
7
|
-
|
|
8
|
-
export function SetupPage() {
|
|
9
|
-
const [loading, setLoading] = useState(false);
|
|
10
|
-
const [error, setError] = useState<string | null>(null);
|
|
11
|
-
|
|
12
|
-
const handleSetup = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
13
|
-
e.preventDefault();
|
|
14
|
-
setLoading(true);
|
|
15
|
-
setError(null);
|
|
16
|
-
|
|
17
|
-
const formData = new FormData(e.currentTarget);
|
|
18
|
-
const title = formData.get('title');
|
|
19
|
-
const email = formData.get('email');
|
|
20
|
-
const password = formData.get('password');
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
// 1. Run Setup (includes Migrations + Admin Creation + Auto-Login via Cookie)
|
|
24
|
-
const res = await apiFetch('/setup', {
|
|
25
|
-
method: 'POST',
|
|
26
|
-
body: JSON.stringify({ title, email, password }),
|
|
27
|
-
headers: { 'Content-Type': 'application/json' },
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
const data = await res.json();
|
|
31
|
-
|
|
32
|
-
if (!res.ok) {
|
|
33
|
-
throw new Error(data.error || 'Failed to complete setup');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// 2. Auth store update (The API already sets the 'session' cookie)
|
|
37
|
-
// We set the email in store for UI purposes
|
|
38
|
-
$auth.set({
|
|
39
|
-
token: data.data.token || 'cookie',
|
|
40
|
-
user: data.data.user,
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
// 3. Mandatory Refresh
|
|
44
|
-
// Using window.location instead of $router.open to ensure a clean application boot
|
|
45
|
-
// after the database and settings have been initialized.
|
|
46
|
-
window.location.href = $basePath.get();
|
|
47
|
-
} catch (err: any) {
|
|
48
|
-
setError(err.message || 'An error occurred during setup');
|
|
49
|
-
} finally {
|
|
50
|
-
setLoading(false);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div className="min-h-screen flex items-center justify-center bg-background p-6">
|
|
56
|
-
<div className="max-w-md w-full border border-border/40 shadow-sm rounded-lg p-8 bg-card relative overflow-hidden">
|
|
57
|
-
{/* Subtle decorative grid/line background */}
|
|
58
|
-
<div className="absolute inset-0 bg-[linear-gradient(to_right,#80808012_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-[size:14px_24px] pointer-events-none opacity-30"></div>
|
|
59
|
-
|
|
60
|
-
<div className="relative z-10">
|
|
61
|
-
<div className="text-center mb-8">
|
|
62
|
-
<h1 className="text-xl font-bold tracking-tight text-foreground">
|
|
63
|
-
Initial Setup
|
|
64
|
-
</h1>
|
|
65
|
-
<p className="text-[11px] font-semibold tracking-widest text-muted-foreground uppercase mt-2">
|
|
66
|
-
Configure FlareCMS Admin
|
|
67
|
-
</p>
|
|
68
|
-
</div>
|
|
69
|
-
|
|
70
|
-
{error && (
|
|
71
|
-
<div className="mb-6 p-3 bg-destructive/10 border border-destructive/20 rounded-md text-destructive text-[11px] uppercase tracking-wider font-semibold text-center">
|
|
72
|
-
{error}
|
|
73
|
-
</div>
|
|
74
|
-
)}
|
|
75
|
-
|
|
76
|
-
<form onSubmit={handleSetup} className="space-y-5">
|
|
77
|
-
<div className="space-y-1.5">
|
|
78
|
-
<label className="text-[10px] uppercase tracking-widest text-muted-foreground font-semibold">
|
|
79
|
-
Site Title
|
|
80
|
-
</label>
|
|
81
|
-
<input
|
|
82
|
-
name="title"
|
|
83
|
-
type="text"
|
|
84
|
-
required
|
|
85
|
-
className="w-full bg-background border border-border/50 text-sm px-3 py-2 rounded-md focus:outline-none focus:border-primary/50 focus:ring-1 focus:ring-primary/50 transition-all font-mono"
|
|
86
|
-
placeholder="My CMS"
|
|
87
|
-
/>
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
<div className="space-y-1.5">
|
|
91
|
-
<label className="text-[10px] uppercase tracking-widest text-muted-foreground font-semibold">
|
|
92
|
-
Admin Email
|
|
93
|
-
</label>
|
|
94
|
-
<input
|
|
95
|
-
name="email"
|
|
96
|
-
type="email"
|
|
97
|
-
required
|
|
98
|
-
className="w-full bg-background border border-border/50 text-sm px-3 py-2 rounded-md focus:outline-none focus:border-primary/50 focus:ring-1 focus:ring-primary/50 transition-all font-mono"
|
|
99
|
-
placeholder="admin@example.com"
|
|
100
|
-
/>
|
|
101
|
-
</div>
|
|
102
|
-
|
|
103
|
-
<div className="space-y-1.5">
|
|
104
|
-
<label className="text-[10px] uppercase tracking-widest text-muted-foreground font-semibold">
|
|
105
|
-
Admin Password
|
|
106
|
-
</label>
|
|
107
|
-
<input
|
|
108
|
-
name="password"
|
|
109
|
-
type="password"
|
|
110
|
-
required
|
|
111
|
-
minLength={6}
|
|
112
|
-
className="w-full bg-background border border-border/50 text-sm px-3 py-2 rounded-md focus:outline-none focus:border-primary/50 focus:ring-1 focus:ring-primary/50 transition-all font-mono"
|
|
113
|
-
placeholder="Min 6 characters"
|
|
114
|
-
/>
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
|
-
<button
|
|
118
|
-
type="submit"
|
|
119
|
-
disabled={loading}
|
|
120
|
-
className="w-full bg-primary text-primary-foreground font-semibold py-2.5 px-4 rounded-md text-xs tracking-wider uppercase hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:ring-offset-2 focus:ring-offset-background transition-all disabled:opacity-50 disabled:cursor-not-allowed mt-2"
|
|
121
|
-
>
|
|
122
|
-
{loading ? 'Configuring...' : 'Complete Setup'}
|
|
123
|
-
</button>
|
|
124
|
-
</form>
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
);
|
|
129
|
-
}
|