flarecms 0.1.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/README.md +73 -0
  2. package/dist/auth/index.js +40 -0
  3. package/dist/cli/commands.js +389 -0
  4. package/dist/cli/index.js +403 -0
  5. package/dist/cli/mcp.js +209 -0
  6. package/dist/db/index.js +164 -0
  7. package/dist/index.js +17626 -0
  8. package/package.json +105 -0
  9. package/scripts/fix-api-paths.mjs +32 -0
  10. package/scripts/fix-imports.mjs +38 -0
  11. package/scripts/prefix-css.mjs +45 -0
  12. package/src/api/lib/cache.ts +45 -0
  13. package/src/api/lib/response.ts +40 -0
  14. package/src/api/middlewares/auth.ts +186 -0
  15. package/src/api/middlewares/cors.ts +10 -0
  16. package/src/api/middlewares/rbac.ts +85 -0
  17. package/src/api/routes/auth.ts +377 -0
  18. package/src/api/routes/collections.ts +205 -0
  19. package/src/api/routes/content.ts +175 -0
  20. package/src/api/routes/device.ts +160 -0
  21. package/src/api/routes/magic.ts +150 -0
  22. package/src/api/routes/mcp.ts +273 -0
  23. package/src/api/routes/oauth.ts +160 -0
  24. package/src/api/routes/settings.ts +43 -0
  25. package/src/api/routes/setup.ts +307 -0
  26. package/src/api/routes/tokens.ts +80 -0
  27. package/src/api/schemas/auth.ts +15 -0
  28. package/src/api/schemas/index.ts +51 -0
  29. package/src/api/schemas/tokens.ts +24 -0
  30. package/src/auth/index.ts +28 -0
  31. package/src/cli/commands.ts +217 -0
  32. package/src/cli/index.ts +21 -0
  33. package/src/cli/mcp.ts +210 -0
  34. package/src/cli/tests/cli.test.ts +40 -0
  35. package/src/cli/tests/create.test.ts +87 -0
  36. package/src/client/FlareAdminRouter.tsx +47 -0
  37. package/src/client/app.tsx +175 -0
  38. package/src/client/components/app-sidebar.tsx +227 -0
  39. package/src/client/components/collection-modal.tsx +215 -0
  40. package/src/client/components/content-list.tsx +247 -0
  41. package/src/client/components/dynamic-form.tsx +190 -0
  42. package/src/client/components/field-modal.tsx +221 -0
  43. package/src/client/components/settings/api-token-section.tsx +400 -0
  44. package/src/client/components/settings/general-section.tsx +224 -0
  45. package/src/client/components/settings/security-section.tsx +154 -0
  46. package/src/client/components/settings/seo-section.tsx +200 -0
  47. package/src/client/components/settings/signup-section.tsx +257 -0
  48. package/src/client/components/ui/accordion.tsx +78 -0
  49. package/src/client/components/ui/avatar.tsx +107 -0
  50. package/src/client/components/ui/badge.tsx +52 -0
  51. package/src/client/components/ui/button.tsx +60 -0
  52. package/src/client/components/ui/card.tsx +103 -0
  53. package/src/client/components/ui/checkbox.tsx +27 -0
  54. package/src/client/components/ui/collapsible.tsx +19 -0
  55. package/src/client/components/ui/dialog.tsx +162 -0
  56. package/src/client/components/ui/icon-picker.tsx +485 -0
  57. package/src/client/components/ui/icons-data.ts +8476 -0
  58. package/src/client/components/ui/input.tsx +20 -0
  59. package/src/client/components/ui/label.tsx +20 -0
  60. package/src/client/components/ui/popover.tsx +91 -0
  61. package/src/client/components/ui/select.tsx +204 -0
  62. package/src/client/components/ui/separator.tsx +23 -0
  63. package/src/client/components/ui/sheet.tsx +141 -0
  64. package/src/client/components/ui/sidebar.tsx +722 -0
  65. package/src/client/components/ui/skeleton.tsx +13 -0
  66. package/src/client/components/ui/sonner.tsx +47 -0
  67. package/src/client/components/ui/switch.tsx +30 -0
  68. package/src/client/components/ui/table.tsx +116 -0
  69. package/src/client/components/ui/tabs.tsx +80 -0
  70. package/src/client/components/ui/textarea.tsx +18 -0
  71. package/src/client/components/ui/tooltip.tsx +68 -0
  72. package/src/client/hooks/use-mobile.ts +19 -0
  73. package/src/client/index.css +149 -0
  74. package/src/client/index.ts +7 -0
  75. package/src/client/layouts/admin-layout.tsx +93 -0
  76. package/src/client/layouts/settings-layout.tsx +104 -0
  77. package/src/client/lib/api.ts +72 -0
  78. package/src/client/lib/utils.ts +6 -0
  79. package/src/client/main.tsx +10 -0
  80. package/src/client/pages/collection-detail.tsx +634 -0
  81. package/src/client/pages/collections.tsx +180 -0
  82. package/src/client/pages/dashboard.tsx +133 -0
  83. package/src/client/pages/device.tsx +66 -0
  84. package/src/client/pages/document-detail-page.tsx +139 -0
  85. package/src/client/pages/documents-page.tsx +103 -0
  86. package/src/client/pages/login.tsx +345 -0
  87. package/src/client/pages/settings.tsx +65 -0
  88. package/src/client/pages/setup.tsx +129 -0
  89. package/src/client/pages/signup.tsx +188 -0
  90. package/src/client/store/auth.ts +30 -0
  91. package/src/client/store/collections.ts +13 -0
  92. package/src/client/store/config.ts +12 -0
  93. package/src/client/store/fetcher.ts +30 -0
  94. package/src/client/store/router.ts +95 -0
  95. package/src/client/store/schema.ts +39 -0
  96. package/src/client/store/settings.ts +31 -0
  97. package/src/client/types.ts +34 -0
  98. package/src/db/dynamic.ts +70 -0
  99. package/src/db/index.ts +16 -0
  100. package/src/db/migrations/001_initial_schema.ts +57 -0
  101. package/src/db/migrations/002_auth_tables.ts +84 -0
  102. package/src/db/migrator.ts +61 -0
  103. package/src/db/schema.ts +142 -0
  104. package/src/index.ts +12 -0
  105. package/src/server/index.ts +66 -0
  106. package/src/types.ts +20 -0
  107. package/style.css.d.ts +8 -0
  108. package/tests/css.test.ts +21 -0
  109. package/tests/modular.test.ts +29 -0
  110. package/tsconfig.json +10 -0
@@ -0,0 +1,634 @@
1
+ import { FieldModal } from '../components/field-modal';
2
+ import { api } from '../lib/api';
3
+ import { $activeSlug, $reloadSchema, $schema } from '../store/schema';
4
+ import { useStore } from '@nanostores/react';
5
+ import {
6
+ ActivityIcon,
7
+ ArrowLeftIcon,
8
+ CalendarIcon,
9
+ CheckIcon,
10
+ CodeIcon,
11
+ CopyIcon,
12
+ DatabaseIcon,
13
+ ExternalLinkIcon,
14
+ FileTextIcon,
15
+ GlobeIcon,
16
+ HashIcon,
17
+ ListIcon,
18
+ Loader2Icon,
19
+ PlusIcon,
20
+ SaveIcon,
21
+ SearchIcon,
22
+ Settings2Icon,
23
+ TerminalIcon,
24
+ TypeIcon,
25
+ } from 'lucide-react';
26
+ import { useEffect, useState } from 'react';
27
+
28
+ import { Button } from '../components/ui/button';
29
+ import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/card';
30
+ import { IconPicker } from '../components/ui/icon-picker';
31
+ import { Input } from '../components/ui/input';
32
+ import { Label } from '../components/ui/label';
33
+ import { Switch } from '../components/ui/switch';
34
+ import {
35
+ Table,
36
+ TableBody,
37
+ TableCell,
38
+ TableHead,
39
+ TableHeader,
40
+ TableRow,
41
+ } from '../components/ui/table';
42
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '../components/ui/tabs';
43
+
44
+ interface CollectionDetailProps {
45
+ id: string;
46
+ slug: string;
47
+ onBack: () => void;
48
+ }
49
+
50
+ export function CollectionDetailPage({
51
+ id,
52
+ slug,
53
+ onBack,
54
+ }: CollectionDetailProps) {
55
+ const { data: schema, loading, error } = useStore($schema);
56
+ const [saving, setSaving] = useState(false);
57
+ const [settings, setSettings] = useState<any>(null);
58
+ const [activeTab, setActiveTab] = useState('fields');
59
+
60
+ useEffect(() => {
61
+ $activeSlug.set(slug);
62
+ return () => $activeSlug.set(null);
63
+ }, [slug]);
64
+
65
+ useEffect(() => {
66
+ if (schema && !settings) {
67
+ setSettings({
68
+ label: schema.label,
69
+ labelSingular: schema.label_singular || '',
70
+ description: schema.description || '',
71
+ icon: schema.icon || 'Package',
72
+ isPublic: schema.is_public === 1,
73
+ urlPattern: schema.url_pattern || '',
74
+ features: schema.features || [],
75
+ });
76
+ }
77
+ }, [schema]);
78
+
79
+ const handleSaveSettings = async () => {
80
+ setSaving(true);
81
+ try {
82
+ await api.patch(`/collections/${id}`, { json: settings });
83
+ $reloadSchema();
84
+ } catch (err) {
85
+ console.error('Failed to save settings:', err);
86
+ } finally {
87
+ setSaving(false);
88
+ }
89
+ };
90
+
91
+ const toggleFeature = (feature: string) => {
92
+ setSettings((prev: any) => ({
93
+ ...prev,
94
+ features: prev.features.includes(feature)
95
+ ? prev.features.filter((f: string) => f !== feature)
96
+ : [...prev.features, feature],
97
+ }));
98
+ };
99
+
100
+ const getFieldIcon = (type: string) => {
101
+ switch (type) {
102
+ case 'text':
103
+ return <TypeIcon className="size-3.5" />;
104
+ case 'richtext':
105
+ return <CodeIcon className="size-3.5" />;
106
+ case 'number':
107
+ return <HashIcon className="size-3.5" />;
108
+ case 'boolean':
109
+ return <CheckIcon className="size-3.5" />;
110
+ case 'date':
111
+ return <CalendarIcon className="size-3.5" />;
112
+ default:
113
+ return <DatabaseIcon className="size-3.5" />;
114
+ }
115
+ };
116
+
117
+ if (loading) {
118
+ return (
119
+ <div className="flex flex-col items-center justify-center h-full py-20 text-muted-foreground gap-6">
120
+ <Loader2Icon className="size-10 animate-spin text-primary" />
121
+ <p className="font-semibold text-[10px] uppercase tracking-wider">
122
+ Accessing Model...
123
+ </p>
124
+ </div>
125
+ );
126
+ }
127
+
128
+ if (!schema || error) {
129
+ return (
130
+ <div className="p-6 max-w-container mx-auto">
131
+ <div className="flex items-center gap-4 mb-8">
132
+ <Button
133
+ variant="outline"
134
+ size="icon"
135
+ onClick={onBack}
136
+ className="size-9 rounded-md shrink-0"
137
+ >
138
+ <ArrowLeftIcon className="size-4" />
139
+ </Button>
140
+ <h1 className="text-3xl font-bold tracking-tight">System Notice</h1>
141
+ </div>
142
+ <Card className="py-20 text-center border-dashed">
143
+ <DatabaseIcon className="size-12 mx-auto mb-4 opacity-5" />
144
+ <p className="text-sm font-bold uppercase tracking-[0.2em] text-muted-foreground/30">
145
+ No collection found
146
+ </p>
147
+ <p className="text-[10px] mt-1 font-medium text-muted-foreground/50">
148
+ The requested data structure does not exist or has been
149
+ decommissioned.
150
+ </p>
151
+ </Card>
152
+ </div>
153
+ );
154
+ }
155
+
156
+ return (
157
+ <div className="p-6 max-w-container mx-auto space-y-8">
158
+ <header className="flex flex-col md:flex-row md:items-center justify-between gap-6 px-1">
159
+ <div className="flex items-center gap-4">
160
+ <Button
161
+ variant="outline"
162
+ size="icon"
163
+ onClick={onBack}
164
+ className="size-9 rounded-md shrink-0 transition-transform active:scale-95"
165
+ >
166
+ <ArrowLeftIcon className="size-4" />
167
+ </Button>
168
+ <div>
169
+ <div className="flex items-center gap-2 mb-1 text-muted-foreground">
170
+ <Settings2Icon className="size-3" />
171
+ <span className="text-[10px] font-semibold uppercase tracking-wider leading-none">
172
+ Structure Engine
173
+ </span>
174
+ </div>
175
+ <h1 className="text-3xl font-bold tracking-tight text-foreground leading-none">
176
+ {schema?.label || slug}
177
+ </h1>
178
+ </div>
179
+ </div>
180
+
181
+ <Tabs
182
+ value={activeTab}
183
+ onValueChange={setActiveTab}
184
+ className="w-[400px]"
185
+ >
186
+ <TabsList className="grid w-full grid-cols-3 bg-muted/50 p-1 border">
187
+ <TabsTrigger
188
+ value="fields"
189
+ className="text-[10px] font-black uppercase tracking-widest gap-2"
190
+ >
191
+ <ListIcon className="size-3" />
192
+ Fields
193
+ </TabsTrigger>
194
+ <TabsTrigger
195
+ value="settings"
196
+ className="text-[10px] font-black uppercase tracking-widest gap-2"
197
+ >
198
+ <Settings2Icon className="size-3" />
199
+ Settings
200
+ </TabsTrigger>
201
+ <TabsTrigger
202
+ value="explorer"
203
+ className="text-[10px] font-black uppercase tracking-widest gap-2"
204
+ >
205
+ <SearchIcon className="size-3" />
206
+ Explorer
207
+ </TabsTrigger>
208
+ </TabsList>
209
+ </Tabs>
210
+ </header>
211
+
212
+ <Tabs
213
+ value={activeTab}
214
+ onValueChange={setActiveTab}
215
+ className="space-y-8"
216
+ >
217
+ <TabsList className="hidden">
218
+ <TabsTrigger value="fields">Fields</TabsTrigger>
219
+ <TabsTrigger value="settings">Settings</TabsTrigger>
220
+ <TabsTrigger value="explorer">Explorer</TabsTrigger>
221
+ </TabsList>
222
+
223
+ <TabsContent
224
+ value="fields"
225
+ className="space-y-8 mt-0 focus-visible:outline-none"
226
+ >
227
+ <div className="flex justify-end px-1">
228
+ <FieldModal collectionId={id} collectionSlug={slug}>
229
+ <Button size="sm" className="font-semibold h-9 px-6 text-xs">
230
+ <PlusIcon className="size-3.5 mr-2" />
231
+ Add Field
232
+ </Button>
233
+ </FieldModal>
234
+ </div>
235
+
236
+ <Card className="py-0 shadow-none border-border overflow-hidden">
237
+ <CardHeader className="bg-muted/10 border-b py-4 px-6 flex flex-row items-center justify-between space-y-0">
238
+ <CardTitle className="text-xs font-semibold uppercase tracking-widest text-muted-foreground">
239
+ Model Definitions
240
+ </CardTitle>
241
+ <div className="text-[10px] font-mono font-medium text-muted-foreground bg-muted/50 border px-3 py-1 rounded-full">
242
+ REF: {id}
243
+ </div>
244
+ </CardHeader>
245
+ <CardContent className="p-0">
246
+ <Table>
247
+ <TableHeader>
248
+ <TableRow className="hover:bg-transparent text-muted-foreground uppercase text-[10px] font-bold tracking-wider bg-muted/20">
249
+ <TableHead className="w-[40%] pl-6 py-3">
250
+ Property
251
+ </TableHead>
252
+ <TableHead>Type</TableHead>
253
+ <TableHead>Endpoint Key</TableHead>
254
+ <TableHead className="text-right pr-6">
255
+ Validation
256
+ </TableHead>
257
+ </TableRow>
258
+ </TableHeader>
259
+ <TableBody>
260
+ {schema?.fields?.map((field: any) => (
261
+ <TableRow
262
+ key={field.id}
263
+ className="group border-border/50 transition-colors"
264
+ >
265
+ <TableCell className="pl-6 py-4">
266
+ <div className="flex items-center gap-4">
267
+ <div className="size-9 rounded bg-muted flex items-center justify-center text-muted-foreground/50 group-hover:text-primary transition-colors border">
268
+ {getFieldIcon(field.type)}
269
+ </div>
270
+ <div className="flex flex-col gap-0.5">
271
+ <span className="font-semibold text-primary group-hover:underline cursor-pointer text-sm leading-none">
272
+ {field.label}
273
+ </span>
274
+ <span className="text-[10px] text-muted-foreground font-medium uppercase tracking-tighter opacity-70">
275
+ Field entry
276
+ </span>
277
+ </div>
278
+ </div>
279
+ </TableCell>
280
+ <TableCell className="text-[12px] font-bold text-foreground/80 capitalize tracking-tight">
281
+ {field.type}
282
+ </TableCell>
283
+ <TableCell className="px-0">
284
+ <code className="px-2 py-0.5 rounded border bg-muted/50 font-mono text-[10px] font-bold text-muted-foreground uppercase">
285
+ {field.slug}
286
+ </code>
287
+ </TableCell>
288
+ <TableCell className="text-right pr-6">
289
+ {field.required && (
290
+ <div className="inline-flex items-center bg-primary/5 text-primary border border-primary/10 px-2 py-0.5 rounded text-[9px] font-bold uppercase tracking-wider">
291
+ Required
292
+ </div>
293
+ )}
294
+ </TableCell>
295
+ </TableRow>
296
+ ))}
297
+ {(!schema?.fields || schema.fields.length === 0) && (
298
+ <TableRow>
299
+ <TableCell
300
+ colSpan={4}
301
+ className="h-60 text-center text-muted-foreground bg-muted/5"
302
+ >
303
+ <DatabaseIcon className="size-12 mx-auto mb-4 opacity-5" />
304
+ <p className="font-bold text-sm uppercase tracking-wider">
305
+ Schema Empty
306
+ </p>
307
+ <p className="text-[10px] mt-1 font-medium opacity-60 uppercase tracking-widest">
308
+ Initialize your model by adding the first field
309
+ definition.
310
+ </p>
311
+ </TableCell>
312
+ </TableRow>
313
+ )}
314
+ </TableBody>
315
+ </Table>
316
+ </CardContent>
317
+ </Card>
318
+ </TabsContent>
319
+
320
+ <TabsContent
321
+ value="settings"
322
+ className="space-y-8 mt-0 focus-visible:outline-none"
323
+ >
324
+ {settings && (
325
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
326
+ <div className="lg:col-span-2 space-y-8">
327
+ <Card>
328
+ <CardHeader>
329
+ <CardTitle className="text-sm font-bold uppercase tracking-widest">
330
+ General Identity
331
+ </CardTitle>
332
+ </CardHeader>
333
+ <CardContent className="space-y-6">
334
+ <div className="grid grid-cols-2 gap-4">
335
+ <div className="space-y-2">
336
+ <Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
337
+ Label (Plural)
338
+ </Label>
339
+ <Input
340
+ value={settings.label}
341
+ onChange={(e) =>
342
+ setSettings({ ...settings, label: e.target.value })
343
+ }
344
+ className="h-10"
345
+ />
346
+ </div>
347
+ <div className="space-y-2">
348
+ <Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
349
+ Label (Singular)
350
+ </Label>
351
+ <Input
352
+ value={settings.labelSingular}
353
+ onChange={(e) =>
354
+ setSettings({
355
+ ...settings,
356
+ labelSingular: e.target.value,
357
+ })
358
+ }
359
+ className="h-10"
360
+ />
361
+ </div>
362
+ </div>
363
+ <div className="space-y-2">
364
+ <Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
365
+ Visual Identifier
366
+ </Label>
367
+ <IconPicker
368
+ value={settings.icon}
369
+ onValueChange={(v) =>
370
+ setSettings({ ...settings, icon: v })
371
+ }
372
+ />
373
+ </div>
374
+ </CardContent>
375
+ </Card>
376
+
377
+ <Card>
378
+ <CardHeader>
379
+ <CardTitle className="text-sm font-bold uppercase tracking-widest">
380
+ Routing & Access
381
+ </CardTitle>
382
+ </CardHeader>
383
+ <CardContent className="space-y-6">
384
+ <div className="space-y-2">
385
+ <Label className="text-[10px] font-black uppercase tracking-widest opacity-50">
386
+ URL Pattern
387
+ </Label>
388
+ <Input
389
+ value={settings.urlPattern}
390
+ onChange={(e) =>
391
+ setSettings({
392
+ ...settings,
393
+ urlPattern: e.target.value,
394
+ })
395
+ }
396
+ placeholder="/blog/{slug}"
397
+ className="h-10 font-mono text-xs"
398
+ />
399
+ <p className="text-[10px] text-muted-foreground mt-1">
400
+ Defines the frontend URL structure for documents in this
401
+ collection.
402
+ </p>
403
+ </div>
404
+
405
+ <div className="flex items-center justify-between p-4 bg-primary/5 border border-primary/10 rounded-xl">
406
+ <div className="space-y-0.5">
407
+ <div className="flex items-center gap-2">
408
+ <GlobeIcon className="size-3.5 text-primary" />
409
+ <p className="text-xs font-bold text-primary">
410
+ Public API Visibility
411
+ </p>
412
+ </div>
413
+ <p className="text-[10px] text-muted-foreground leading-relaxed">
414
+ If enabled, this collection will be accessible without
415
+ an API token for read-only operations.
416
+ </p>
417
+ </div>
418
+ <Switch
419
+ checked={settings.isPublic}
420
+ onCheckedChange={(v) =>
421
+ setSettings({ ...settings, isPublic: v })
422
+ }
423
+ />
424
+ </div>
425
+ </CardContent>
426
+ </Card>
427
+ </div>
428
+
429
+ <div className="space-y-8">
430
+ <Card>
431
+ <CardHeader>
432
+ <CardTitle className="text-sm font-bold uppercase tracking-widest text-primary">
433
+ Feature Toggles
434
+ </CardTitle>
435
+ </CardHeader>
436
+ <CardContent className="space-y-4">
437
+ {[
438
+ {
439
+ id: 'searchable',
440
+ label: 'Searchable',
441
+ desc: 'Index content for global search',
442
+ icon: SearchIcon,
443
+ },
444
+ {
445
+ id: 'seo',
446
+ label: 'SEO Metadata',
447
+ desc: 'Enable meta title/desc support',
448
+ icon: GlobeIcon,
449
+ },
450
+ {
451
+ id: 'drafts',
452
+ label: 'Draft Mode',
453
+ desc: 'Enable publish/unpublish workflow',
454
+ icon: FileTextIcon,
455
+ },
456
+ ].map((feat) => (
457
+ <div
458
+ key={feat.id}
459
+ className="flex items-start gap-4 p-3 rounded-lg border bg-muted/20 group hover:border-primary/20 transition-colors"
460
+ >
461
+ <div className="size-8 rounded bg-background border flex items-center justify-center text-muted-foreground group-hover:text-primary transition-colors">
462
+ <feat.icon className="size-4" />
463
+ </div>
464
+ <div className="flex-1 space-y-1">
465
+ <div className="flex items-center justify-between">
466
+ <p className="text-xs font-bold leading-none">
467
+ {feat.label}
468
+ </p>
469
+ <Switch
470
+ checked={settings.features.includes(feat.id)}
471
+ onCheckedChange={() => toggleFeature(feat.id)}
472
+ className="scale-75"
473
+ />
474
+ </div>
475
+ <p className="text-[9px] text-muted-foreground leading-tight">
476
+ {feat.desc}
477
+ </p>
478
+ </div>
479
+ </div>
480
+ ))}
481
+ </CardContent>
482
+ </Card>
483
+
484
+ <Button
485
+ onClick={handleSaveSettings}
486
+ disabled={saving}
487
+ className="w-full h-12 font-black uppercase tracking-widest text-[10px] gap-2"
488
+ >
489
+ {saving ? (
490
+ <Loader2Icon className="size-4 animate-spin" />
491
+ ) : (
492
+ <SaveIcon className="size-4" />
493
+ )}
494
+ Update Configuration
495
+ </Button>
496
+ </div>
497
+ </div>
498
+ )}
499
+ </TabsContent>
500
+
501
+ <TabsContent
502
+ value="explorer"
503
+ className="mt-0 focus-visible:outline-none space-y-8"
504
+ >
505
+ <div className="flex items-center gap-2 px-1">
506
+ <ActivityIcon className="size-3.5 text-primary" />
507
+ <h2 className="text-[10px] font-bold uppercase tracking-[0.2em] text-foreground">
508
+ Collection Intelligence
509
+ </h2>
510
+ </div>
511
+
512
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
513
+ {/* Activity Stream */}
514
+ <Card className="bg-muted/5 border shadow-none overflow-hidden">
515
+ <CardHeader className="py-4 px-6 border-b bg-muted/10 flex flex-row items-center justify-between space-y-0">
516
+ <CardTitle className="text-[10px] font-bold uppercase tracking-widest text-muted-foreground">
517
+ System Activity Log
518
+ </CardTitle>
519
+ <div className="flex items-center gap-2">
520
+ <span className="size-1.5 bg-green-500 rounded-full animate-pulse" />
521
+ <span className="text-[9px] font-bold uppercase tracking-tighter opacity-50 text-green-600">
522
+ Live
523
+ </span>
524
+ </div>
525
+ </CardHeader>
526
+ <CardContent className="p-0">
527
+ <div className="divide-y divide-border/50">
528
+ {[
529
+ {
530
+ event: 'Schema optimized for production',
531
+ time: '2m ago',
532
+ user: 'System',
533
+ },
534
+ {
535
+ event: `Structure '${schema.label}' successfully initialized`,
536
+ time: '1h ago',
537
+ user: 'Admin',
538
+ },
539
+ {
540
+ event: 'Public API permissions finalized',
541
+ time: '3h ago',
542
+ user: 'Admin',
543
+ },
544
+ {
545
+ event: 'PostgreSQL table isolation complete',
546
+ time: 'Yesterday',
547
+ user: 'System',
548
+ },
549
+ ].map((log, i) => (
550
+ <div
551
+ key={i}
552
+ className="flex items-center justify-between px-6 py-4 hover:bg-muted/30 transition-colors group"
553
+ >
554
+ <div className="flex flex-col gap-1">
555
+ <p className="text-xs font-semibold text-foreground/80 group-hover:text-primary transition-colors cursor-default">
556
+ {log.event}
557
+ </p>
558
+ <p className="text-[10px] text-muted-foreground/50 font-medium">
559
+ Mapped by {log.user}
560
+ </p>
561
+ </div>
562
+ <span className="text-[10px] font-mono text-muted-foreground/30 font-bold">
563
+ {log.time}
564
+ </span>
565
+ </div>
566
+ ))}
567
+ </div>
568
+ </CardContent>
569
+ </Card>
570
+
571
+ {/* API Reference */}
572
+ <Card className="py-0 bg-primary/5 border-primary/20 shadow-none overflow-hidden h-fit">
573
+ <CardHeader className="py-4 px-6 border-b border-primary/10 bg-primary/10">
574
+ <CardTitle className="text-[10px] font-bold uppercase tracking-widest text-primary">
575
+ Primary Endpoint Reference
576
+ </CardTitle>
577
+ </CardHeader>
578
+ <CardContent className="p-6 space-y-6">
579
+ <div className="space-y-3">
580
+ <p className="text-xs text-muted-foreground leading-relaxed font-medium">
581
+ Use this endpoint to interface with the data module
582
+ programmatically from your external applications.
583
+ </p>
584
+
585
+ <div className="flex items-center gap-2 p-2 rounded-lg bg-background border shadow-sm group">
586
+ <TerminalIcon className="size-4 text-primary ml-1 shrink-0" />
587
+ <code className="flex-1 text-[11px] font-mono font-bold text-foreground overflow-hidden truncate px-1">
588
+ GET {window.location.origin}/api/content/{slug}
589
+ </code>
590
+ <Button
591
+ variant="ghost"
592
+ size="icon"
593
+ className="size-8 text-muted-foreground hover:text-primary transition-colors"
594
+ onClick={() => {
595
+ navigator.clipboard.writeText(
596
+ `${window.location.origin}/api/content/${slug}`,
597
+ );
598
+ }}
599
+ >
600
+ <CopyIcon className="size-3.5" />
601
+ </Button>
602
+ </div>
603
+ </div>
604
+
605
+ <div className="grid grid-cols-2 gap-4">
606
+ <Button
607
+ variant="outline"
608
+ className="h-10 text-[10px] font-bold uppercase tracking-widest gap-2"
609
+ onClick={() =>
610
+ window.open(
611
+ `${window.location.origin}/api/content/${slug}`,
612
+ '_blank',
613
+ )
614
+ }
615
+ >
616
+ <ExternalLinkIcon className="size-3.5" />
617
+ Test Request
618
+ </Button>
619
+ <Button
620
+ variant="outline"
621
+ className="h-10 text-[10px] font-bold uppercase tracking-widest gap-2 opacity-50 cursor-not-allowed"
622
+ >
623
+ <TerminalIcon className="size-3.5" />
624
+ SDK Snippets
625
+ </Button>
626
+ </div>
627
+ </CardContent>
628
+ </Card>
629
+ </div>
630
+ </TabsContent>
631
+ </Tabs>
632
+ </div>
633
+ );
634
+ }