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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/dist/auth/index.js +201 -1
  3. package/dist/cli/commands.js +5554 -55
  4. package/dist/cli/index.js +5554 -55
  5. package/dist/cli/mcp.js +30 -0
  6. package/dist/client/index.js +23576 -0
  7. package/dist/db/index.js +10392 -25
  8. package/dist/index.js +56776 -7582
  9. package/dist/server/index.js +43280 -0
  10. package/dist/style.css +5536 -0
  11. package/package.json +33 -30
  12. package/scripts/fix-api-paths.mjs +0 -32
  13. package/scripts/fix-imports.mjs +0 -38
  14. package/scripts/prefix-css.mjs +0 -45
  15. package/src/api/lib/cache.ts +0 -45
  16. package/src/api/lib/response.ts +0 -40
  17. package/src/api/middlewares/auth.ts +0 -186
  18. package/src/api/middlewares/cors.ts +0 -10
  19. package/src/api/middlewares/rbac.ts +0 -85
  20. package/src/api/routes/auth.ts +0 -377
  21. package/src/api/routes/collections.ts +0 -205
  22. package/src/api/routes/content.ts +0 -175
  23. package/src/api/routes/device.ts +0 -160
  24. package/src/api/routes/magic.ts +0 -150
  25. package/src/api/routes/mcp.ts +0 -273
  26. package/src/api/routes/oauth.ts +0 -160
  27. package/src/api/routes/settings.ts +0 -43
  28. package/src/api/routes/setup.ts +0 -307
  29. package/src/api/routes/tokens.ts +0 -80
  30. package/src/api/schemas/auth.ts +0 -15
  31. package/src/api/schemas/index.ts +0 -51
  32. package/src/api/schemas/tokens.ts +0 -24
  33. package/src/auth/index.ts +0 -28
  34. package/src/cli/commands.ts +0 -217
  35. package/src/cli/index.ts +0 -21
  36. package/src/cli/mcp.ts +0 -210
  37. package/src/cli/tests/cli.test.ts +0 -40
  38. package/src/cli/tests/create.test.ts +0 -87
  39. package/src/client/FlareAdminRouter.tsx +0 -47
  40. package/src/client/app.tsx +0 -175
  41. package/src/client/components/app-sidebar.tsx +0 -227
  42. package/src/client/components/collection-modal.tsx +0 -215
  43. package/src/client/components/content-list.tsx +0 -247
  44. package/src/client/components/dynamic-form.tsx +0 -190
  45. package/src/client/components/field-modal.tsx +0 -221
  46. package/src/client/components/settings/api-token-section.tsx +0 -400
  47. package/src/client/components/settings/general-section.tsx +0 -224
  48. package/src/client/components/settings/security-section.tsx +0 -154
  49. package/src/client/components/settings/seo-section.tsx +0 -200
  50. package/src/client/components/settings/signup-section.tsx +0 -257
  51. package/src/client/components/ui/accordion.tsx +0 -78
  52. package/src/client/components/ui/avatar.tsx +0 -107
  53. package/src/client/components/ui/badge.tsx +0 -52
  54. package/src/client/components/ui/button.tsx +0 -60
  55. package/src/client/components/ui/card.tsx +0 -103
  56. package/src/client/components/ui/checkbox.tsx +0 -27
  57. package/src/client/components/ui/collapsible.tsx +0 -19
  58. package/src/client/components/ui/dialog.tsx +0 -162
  59. package/src/client/components/ui/icon-picker.tsx +0 -485
  60. package/src/client/components/ui/icons-data.ts +0 -8476
  61. package/src/client/components/ui/input.tsx +0 -20
  62. package/src/client/components/ui/label.tsx +0 -20
  63. package/src/client/components/ui/popover.tsx +0 -91
  64. package/src/client/components/ui/select.tsx +0 -204
  65. package/src/client/components/ui/separator.tsx +0 -23
  66. package/src/client/components/ui/sheet.tsx +0 -141
  67. package/src/client/components/ui/sidebar.tsx +0 -722
  68. package/src/client/components/ui/skeleton.tsx +0 -13
  69. package/src/client/components/ui/sonner.tsx +0 -47
  70. package/src/client/components/ui/switch.tsx +0 -30
  71. package/src/client/components/ui/table.tsx +0 -116
  72. package/src/client/components/ui/tabs.tsx +0 -80
  73. package/src/client/components/ui/textarea.tsx +0 -18
  74. package/src/client/components/ui/tooltip.tsx +0 -68
  75. package/src/client/hooks/use-mobile.ts +0 -19
  76. package/src/client/index.css +0 -149
  77. package/src/client/index.ts +0 -7
  78. package/src/client/layouts/admin-layout.tsx +0 -93
  79. package/src/client/layouts/settings-layout.tsx +0 -104
  80. package/src/client/lib/api.ts +0 -72
  81. package/src/client/lib/utils.ts +0 -6
  82. package/src/client/main.tsx +0 -10
  83. package/src/client/pages/collection-detail.tsx +0 -634
  84. package/src/client/pages/collections.tsx +0 -180
  85. package/src/client/pages/dashboard.tsx +0 -133
  86. package/src/client/pages/device.tsx +0 -66
  87. package/src/client/pages/document-detail-page.tsx +0 -139
  88. package/src/client/pages/documents-page.tsx +0 -103
  89. package/src/client/pages/login.tsx +0 -345
  90. package/src/client/pages/settings.tsx +0 -65
  91. package/src/client/pages/setup.tsx +0 -129
  92. package/src/client/pages/signup.tsx +0 -188
  93. package/src/client/store/auth.ts +0 -30
  94. package/src/client/store/collections.ts +0 -13
  95. package/src/client/store/config.ts +0 -12
  96. package/src/client/store/fetcher.ts +0 -30
  97. package/src/client/store/router.ts +0 -95
  98. package/src/client/store/schema.ts +0 -39
  99. package/src/client/store/settings.ts +0 -31
  100. package/src/client/types.ts +0 -34
  101. package/src/db/dynamic.ts +0 -70
  102. package/src/db/index.ts +0 -16
  103. package/src/db/migrations/001_initial_schema.ts +0 -57
  104. package/src/db/migrations/002_auth_tables.ts +0 -84
  105. package/src/db/migrator.ts +0 -61
  106. package/src/db/schema.ts +0 -142
  107. package/src/index.ts +0 -12
  108. package/src/server/index.ts +0 -66
  109. package/src/types.ts +0 -20
  110. package/tests/css.test.ts +0 -21
  111. package/tests/modular.test.ts +0 -29
  112. package/tsconfig.json +0 -10
  113. /package/{style.css.d.ts → dist/style.css.d.ts} +0 -0
@@ -1,180 +0,0 @@
1
- import { useStore } from '@nanostores/react';
2
- import { $collections } from '../store/collections';
3
- import { $router, navigate } from '../store/router';
4
- import { useState } from 'react';
5
- import {
6
- Database as DatabaseIcon,
7
- Plus as PlusIcon,
8
- Loader2 as Loader2Icon,
9
- Search as SearchIcon,
10
- Settings2 as Settings2Icon,
11
- ExternalLink as ExternalLinkIcon,
12
- MoreVertical as MoreVerticalIcon,
13
- } from 'lucide-react';
14
-
15
- import { Button } from '../components/ui/button';
16
- import { Input } from '../components/ui/input';
17
- import { Card, CardHeader, CardTitle, CardContent } from '../components/ui/card';
18
- import {
19
- Table,
20
- TableBody,
21
- TableCell,
22
- TableHead,
23
- TableHeader,
24
- TableRow,
25
- } from '../components/ui/table';
26
- import { CollectionModal } from '../components/collection-modal';
27
- import { Icon } from '../components/ui/icon-picker';
28
-
29
- export function CollectionsPage() {
30
- const { data: collections, loading } = useStore($collections);
31
- const [searchQuery, setSearchQuery] = useState('');
32
-
33
- const filteredCollections = collections?.filter(
34
- (c) =>
35
- c.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
36
- c.slug.toLowerCase().includes(searchQuery.toLowerCase()),
37
- );
38
-
39
- return (
40
- <div className="p-6 max-w-container mx-auto space-y-8">
41
- {/* Header Section */}
42
- <header className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6 px-1">
43
- <div>
44
- <div className="flex items-center gap-2 mb-1 text-muted-foreground">
45
- <DatabaseIcon className="size-3" />
46
- <span className="text-[10px] font-semibold uppercase tracking-wider leading-none">
47
- Infrastructure
48
- </span>
49
- </div>
50
- <h1 className="text-3xl font-bold tracking-tight text-foreground leading-none">
51
- Data Collections
52
- </h1>
53
- </div>
54
-
55
- <CollectionModal>
56
- <Button
57
- size="sm"
58
- className="font-semibold h-9 px-6 text-xs"
59
- >
60
- <PlusIcon className="size-3.5 mr-2" />
61
- New Collection
62
- </Button>
63
- </CollectionModal>
64
- </header>
65
-
66
- {/* Action Bar */}
67
- <div className="flex items-center justify-between gap-4">
68
- <div className="relative flex-1 max-w-md">
69
- <SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground/40" />
70
- <Input
71
- placeholder="Search collections..."
72
- value={searchQuery}
73
- onChange={(e) => setSearchQuery(e.target.value)}
74
- className="pl-9 h-10 bg-muted/20 focus:bg-background border-border rounded-md text-sm"
75
- />
76
- </div>
77
- <div className="text-[10px] font-bold text-muted-foreground/40 uppercase tracking-widest bg-muted/10 px-3 py-2 rounded-md border">
78
- {collections?.length || 0} Registered Modules
79
- </div>
80
- </div>
81
-
82
- {/* Main Table */}
83
- <Card className="py-0 shadow-none border-border overflow-hidden">
84
- <CardContent className="p-0">
85
- <Table>
86
- <TableHeader>
87
- <TableRow className="hover:bg-transparent text-muted-foreground uppercase text-[10px] font-bold tracking-wider bg-muted/20">
88
- <TableHead className="pl-8 py-4">Identity</TableHead>
89
- <TableHead>Type</TableHead>
90
- <TableHead>Slug Identifier</TableHead>
91
- <TableHead className="text-right pr-8">Management</TableHead>
92
- </TableRow>
93
- </TableHeader>
94
- <TableBody>
95
- {loading ? (
96
- <TableRow>
97
- <TableCell colSpan={4} className="h-60 text-center">
98
- <Loader2Icon className="size-6 animate-spin mx-auto text-primary/40" />
99
- </TableCell>
100
- </TableRow>
101
- ) : (
102
- filteredCollections?.map((col) => (
103
- <TableRow key={col.id} className="group border-border/50">
104
- <TableCell className="pl-8 py-5">
105
- <div className="flex items-center gap-4">
106
- <div className="size-10 rounded bg-muted flex items-center justify-center border group-hover:bg-primary/5 group-hover:text-primary transition-colors">
107
- <Icon name={col.icon as any} className="size-5 opacity-40 group-hover:opacity-100" />
108
- </div>
109
- <div className="flex flex-col gap-0.5">
110
- <span
111
- className="font-bold text-foreground group-hover:text-primary transition-colors cursor-pointer text-sm leading-none"
112
- onClick={() => navigate('document_list', { slug: col.slug })}
113
- >
114
- {col.label}
115
- </span>
116
- <span className="text-[10px] text-muted-foreground font-medium uppercase tracking-tight">
117
- Data Collection
118
- </span>
119
- </div>
120
- </div>
121
- </TableCell>
122
- <TableCell className="text-[11px] font-bold text-muted-foreground uppercase tracking-widest">
123
- PostgreSQL
124
- </TableCell>
125
- <TableCell>
126
- <code className="text-[10px] font-mono font-bold bg-muted/50 border px-2 py-0.5 rounded text-muted-foreground uppercase">
127
- {col.slug}
128
- </code>
129
- </TableCell>
130
- <TableCell className="text-right pr-8">
131
- <div className="flex items-center justify-end gap-2">
132
- <Button
133
- variant="ghost"
134
- size="sm"
135
- className="h-8 px-3 text-[10px] font-bold uppercase tracking-wider gap-2 hover:bg-muted"
136
- onClick={() => navigate('document_list', { slug: col.slug })}
137
- >
138
- <ExternalLinkIcon className="size-3" />
139
- Documents
140
- </Button>
141
- <Button
142
- variant="outline"
143
- size="sm"
144
- className="h-8 px-3 text-[10px] font-bold uppercase tracking-wider gap-2"
145
- onClick={() =>
146
- navigate('collection', { id: col.id, slug: col.slug })
147
- }
148
- >
149
- <Settings2Icon className="size-3" />
150
- Structure
151
- </Button>
152
- </div>
153
- </TableCell>
154
- </TableRow>
155
- ))
156
- )}
157
- {filteredCollections?.length === 0 && !loading && (
158
- <TableRow>
159
- <TableCell
160
- colSpan={4}
161
- className="h-60 text-center text-muted-foreground/30 bg-muted/5"
162
- >
163
- <DatabaseIcon className="size-12 mx-auto mb-4 opacity-5" />
164
- <p className="text-sm font-bold uppercase tracking-[0.2em]">
165
- No collection found
166
- </p>
167
- <p className="text-[10px] mt-1 font-medium">
168
- Create your first data collection to begin.
169
- </p>
170
- </TableCell>
171
- </TableRow>
172
- )}
173
- </TableBody>
174
- </Table>
175
- </CardContent>
176
- </Card>
177
-
178
- </div>
179
- );
180
- }
@@ -1,133 +0,0 @@
1
- import { useState } from 'react';
2
- import { useStore } from '@nanostores/react';
3
- import { $collections } from '../store/collections';
4
- import { $router, navigate } from '../store/router';
5
- import { CollectionModal } from '../components/collection-modal';
6
- import { Button } from '../components/ui/button';
7
- import { Card, CardHeader, CardTitle, CardContent } from '../components/ui/card';
8
- import {
9
- DatabaseIcon,
10
- PlusIcon,
11
- Loader2Icon,
12
- ChevronRightIcon,
13
- FileTextIcon,
14
- UploadIcon,
15
- UserIcon,
16
- SparklesIcon,
17
- } from 'lucide-react';
18
-
19
- export function DashboardPage() {
20
- const { data: collections, loading } = useStore($collections);
21
-
22
- return (
23
- <div className="p-6 max-w-container mx-auto space-y-8">
24
- {/* Header Section */}
25
- <header className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
26
- <div>
27
- <h1 className="text-3xl font-bold tracking-tight text-foreground">
28
- Dashboard
29
- </h1>
30
- </div>
31
- <div className="flex items-center gap-2">
32
- <CollectionModal>
33
- <Button
34
- variant="outline"
35
- size="sm"
36
- className="h-9 gap-2 text-xs font-semibold"
37
- >
38
- <PlusIcon className="size-3.5" />
39
- Collection
40
- </Button>
41
- </CollectionModal>
42
-
43
- <Button
44
- variant="outline"
45
- size="sm"
46
- className="h-9 gap-2 text-xs font-semibold"
47
- >
48
- <UploadIcon className="size-3.5" />
49
- Upload Media
50
- </Button>
51
- </div>
52
- </header>
53
-
54
- {/* Summary Bar */}
55
- <div className="flex items-center gap-6 px-4 py-2 bg-muted/30 border border-border/50 rounded-md text-[11px] font-medium text-muted-foreground/60">
56
- <div className="flex items-center gap-2">
57
- <DatabaseIcon className="size-3 text-blue-400" />
58
- <span>{collections?.length || 0} collections</span>
59
- </div>
60
- <div className="flex items-center gap-2 border-l border-border/50 pl-6">
61
- <UserIcon className="size-3 text-green-400" />
62
- <span>1 user</span>
63
- </div>
64
- </div>
65
-
66
- {/* Main Grid */}
67
- <div className="grid grid-cols-1 lg:grid-cols-12 gap-8">
68
- {/* Left Column: Content */}
69
- <Card className="py-0 lg:col-span-12 xl:col-span-7 shadow-none border-border overflow-hidden">
70
- <CardHeader className="bg-muted/10 border-b py-4 px-6">
71
- <CardTitle className="text-sm font-semibold">Content</CardTitle>
72
- </CardHeader>
73
- <CardContent className="p-0">
74
- <div className="divide-y divide-border/50">
75
- {collections?.map((col) => (
76
- <div
77
- key={col.id}
78
- className="flex items-center justify-between p-6 hover:bg-muted/30 transition-colors cursor-pointer group"
79
- onClick={() => navigate('document_list', { slug: col.slug })}
80
- >
81
- <div className="flex items-center gap-4">
82
- <div className="size-10 rounded bg-muted flex items-center justify-center">
83
- {col.slug === 'pages' ? (
84
- <FileTextIcon className="size-5 text-muted-foreground/50 group-hover:text-primary transition-colors" />
85
- ) : (
86
- <DatabaseIcon className="size-5 text-muted-foreground/50 group-hover:text-primary transition-colors" />
87
- )}
88
- </div>
89
- <span className="font-semibold text-foreground/80 group-hover:text-primary transition-colors">
90
- {col.label}
91
- </span>
92
- </div>
93
- <div className="flex items-center gap-2 text-muted-foreground/30">
94
- <ChevronRightIcon className="size-4" />
95
- </div>
96
- </div>
97
- ))}
98
- {(!collections || collections.length === 0) && !loading && (
99
- <div className="flex flex-col items-center justify-center py-20 text-muted-foreground/40">
100
- <DatabaseIcon className="size-10 mb-4 opacity-10" />
101
- <p className="text-sm font-medium">No collections found</p>
102
- </div>
103
- )}
104
- </div>
105
- {loading && (
106
- <div className="flex items-center justify-center py-20">
107
- <Loader2Icon className="size-6 animate-spin text-primary/50" />
108
- </div>
109
- )}
110
- </CardContent>
111
- </Card>
112
-
113
- {/* Right Column: Activity */}
114
- <Card className="py-0 lg:col-span-12 xl:col-span-5 shadow-none border-border overflow-hidden">
115
- <CardHeader className="bg-muted/10 border-b py-4 px-6">
116
- <CardTitle className="text-sm font-semibold">
117
- Recent Activity
118
- </CardTitle>
119
- </CardHeader>
120
- <CardContent className="px-6 py-4 text-center">
121
- <div className="py-20 text-muted-foreground/30">
122
- <SparklesIcon className="size-8 mx-auto mb-4 opacity-10" />
123
- <p className="text-[10px] font-bold uppercase tracking-widest">
124
- System Initialized
125
- </p>
126
- </div>
127
- </CardContent>
128
- </Card>
129
- </div>
130
-
131
- </div>
132
- );
133
- }
@@ -1,66 +0,0 @@
1
- import React, { useState } from 'react';
2
- import { apiFetch } from '../lib/api';
3
- import { Button } from '../components/ui/button';
4
- import { Input } from '../components/ui/input';
5
-
6
- export function DevicePage() {
7
- const [userCode, setUserCode] = useState('');
8
- const [loading, setLoading] = useState(false);
9
- const [message, setMessage] = useState('');
10
- const [error, setError] = useState('');
11
-
12
- const handleApprove = async (e: React.FormEvent) => {
13
- e.preventDefault();
14
- setLoading(true);
15
- setError('');
16
- setMessage('');
17
-
18
- try {
19
- const res = await apiFetch('/device/verify', {
20
- method: 'POST',
21
- body: JSON.stringify({ user_code: userCode }),
22
- });
23
- const data = await res.json();
24
-
25
- if (!res.ok) throw new Error(data.error);
26
-
27
- setMessage('Device successfully authorized! You may return to your CLI.');
28
- setUserCode('');
29
- } catch (err: any) {
30
- setError(err.message);
31
- } finally {
32
- setLoading(false);
33
- }
34
- };
35
-
36
- return (
37
- <div className="max-w-md w-full mx-auto p-6 space-y-6 mt-12">
38
- <div className="text-center">
39
- <h1 className="text-xl font-bold tracking-tight text-foreground">Device Authorization</h1>
40
- <p className="text-[11px] font-semibold tracking-widest text-muted-foreground uppercase mt-2">
41
- Approve CLI / Agent Requests
42
- </p>
43
- </div>
44
-
45
- <form onSubmit={handleApprove} className="space-y-4">
46
- <div className="space-y-2">
47
- <label className="text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">User Code</label>
48
- <Input
49
- value={userCode}
50
- onChange={e => setUserCode(e.target.value.toUpperCase())}
51
- placeholder="ABCD-1234"
52
- className="text-center text-lg tracking-widest uppercase font-mono"
53
- required
54
- />
55
- </div>
56
-
57
- {error && <div className="text-destructive text-xs font-semibold text-center">{error}</div>}
58
- {message && <div className="text-primary text-xs font-semibold text-center">{message}</div>}
59
-
60
- <Button type="submit" className="w-full h-10 font-bold uppercase tracking-widest" disabled={loading}>
61
- {loading ? 'Verifying...' : 'Authorize Device'}
62
- </Button>
63
- </form>
64
- </div>
65
- );
66
- }
@@ -1,139 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { useStore } from '@nanostores/react';
3
- import { $schema, $activeSlug } from '../store/schema';
4
- import { $router, navigate } from '../store/router';
5
- import { apiFetch } from '../lib/api';
6
- import { DynamicForm } from '../components/dynamic-form';
7
- import {
8
- ArrowLeft as ArrowLeftIcon,
9
- Database as DatabaseIcon,
10
- Loader2 as Loader2Icon,
11
- Sparkles as SparklesIcon,
12
- } from 'lucide-react';
13
- import { Button } from '../components/ui/button';
14
- import { toast } from 'sonner';
15
-
16
- export function DocumentDetailPage() {
17
- const page = useStore($router);
18
- const { data: schema, loading: schemaLoading } = useStore($schema);
19
- const [document, setDocument] = useState<any>(null);
20
- const [loading, setLoading] = useState(true);
21
- const [isSubmitting, setIsSubmitting] = useState(false);
22
-
23
- const slug = (page as any).params.slug;
24
- const id = (page as any).params.id;
25
-
26
- useEffect(() => {
27
- if (slug) {
28
- $activeSlug.set(slug);
29
- }
30
- return () => $activeSlug.set(null);
31
- }, [slug]);
32
-
33
- const fetchDocument = async () => {
34
- if (id === 'new') {
35
- setDocument({});
36
- setLoading(false);
37
- return;
38
- }
39
-
40
- setLoading(true);
41
- try {
42
- const response = await apiFetch(`/content/${slug}/${id}`);
43
- if (response.ok) {
44
- const result = await response.json();
45
- setDocument(result.data);
46
- }
47
- } catch (err) {
48
- console.error(err);
49
- toast.error('Failed to load document structure');
50
- } finally {
51
- setLoading(false);
52
- }
53
- };
54
-
55
- useEffect(() => {
56
- fetchDocument();
57
- }, [slug, id]);
58
-
59
- const handleSubmit = async (data: any) => {
60
- setIsSubmitting(true);
61
- try {
62
- const method = id === 'new' ? 'POST' : 'PUT';
63
- const url =
64
- id === 'new' ? `/content/${slug}` : `/content/${slug}/${id}`;
65
-
66
- const response = await apiFetch(url, {
67
- method,
68
- body: JSON.stringify(data),
69
- });
70
-
71
- const result = await response.json();
72
-
73
- if (response.ok) {
74
- toast.success(id === 'new' ? 'Document deployed' : 'Fragment updated');
75
- if (id === 'new' && result.data.id) {
76
- navigate('document_edit', { slug, id: result.data.id });
77
- } else {
78
- navigate('document_list', { slug });
79
- }
80
- } else {
81
- toast.error(result.error || 'Operation failed');
82
- }
83
- } catch (err: any) {
84
- console.error(err);
85
- toast.error(err.message || 'Verification error encountered');
86
- } finally {
87
- setIsSubmitting(false);
88
- }
89
- };
90
-
91
- if (schemaLoading || loading) {
92
- return (
93
- <div className="flex flex-col items-center justify-center h-full py-20 text-muted-foreground gap-6">
94
- <Loader2Icon className="size-10 animate-spin text-primary" />
95
- <p className="font-semibold text-[10px] uppercase tracking-wider">
96
- Initializing Workspace...
97
- </p>
98
- </div>
99
- );
100
- }
101
-
102
- return (
103
- <div className="p-6 max-w-container mx-auto space-y-8">
104
- <header className="flex items-center gap-4 px-1">
105
- <Button
106
- variant="outline"
107
- size="icon"
108
- onClick={() => navigate('document_list', { slug })}
109
- className="size-9 rounded-md shrink-0 transition-transform active:scale-95"
110
- >
111
- <ArrowLeftIcon className="size-4" />
112
- </Button>
113
- <div>
114
- <div className="flex items-center gap-2 mb-1 text-muted-foreground">
115
- <SparklesIcon className="size-3" />
116
- <span className="text-[10px] font-semibold uppercase tracking-wider leading-none">
117
- Document Processing
118
- </span>
119
- </div>
120
- <h2 className="text-sm font-bold tracking-tight text-foreground leading-none">
121
- {!id
122
- ? `New ${schema?.label_singular || 'Document'}`
123
- : `Edit ${schema?.label_singular || 'Document'}`}
124
- </h2>
125
- </div>
126
- </header>
127
-
128
- <div className="bg-background border rounded-lg p-8 shadow-sm">
129
- <DynamicForm
130
- slug={slug}
131
- initialData={document}
132
- onSubmit={handleSubmit}
133
- isSubmitting={isSubmitting}
134
- onCancel={() => navigate('document_list', { slug })}
135
- />
136
- </div>
137
- </div>
138
- );
139
- }
@@ -1,103 +0,0 @@
1
- import { useStore } from '@nanostores/react';
2
- import { $schema, $activeSlug } from '../store/schema';
3
- import { $router, navigate } from '../store/router';
4
- import { useEffect } from 'react';
5
- import { ContentList } from '../components/content-list';
6
- import {
7
- Database as DatabaseIcon,
8
- Plus as PlusIcon,
9
- ChevronRight as ChevronRightIcon,
10
- Settings as SettingsIcon,
11
- } from 'lucide-react';
12
- import { Button } from '../components/ui/button';
13
-
14
- export function DocumentsPage() {
15
- const page = useStore($router);
16
- const { data: schema, loading } = useStore($schema);
17
- const slug = (page as any).params.slug;
18
-
19
- useEffect(() => {
20
- if (slug) {
21
- $activeSlug.set(slug);
22
- }
23
- return () => $activeSlug.set(null);
24
- }, [slug]);
25
-
26
- if (loading) return null;
27
-
28
- const hasFields = schema?.fields && schema.fields.length > 0;
29
-
30
- return (
31
- <div className="p-6 max-w-container mx-auto space-y-8">
32
- <header className="flex flex-col md:flex-row md:items-center justify-between gap-6 px-1">
33
- <div className="flex items-center gap-3">
34
- <div className="flex items-center gap-2 text-muted-foreground">
35
- <DatabaseIcon className="size-4" />
36
- <ChevronRightIcon className="size-3 opacity-30" />
37
- </div>
38
- <div>
39
- <div className="flex items-center gap-2 mb-1 text-muted-foreground">
40
- <span className="text-[10px] font-semibold uppercase tracking-wider leading-none">
41
- Collection Registry
42
- </span>
43
- </div>
44
- <h1 className="text-3xl font-bold tracking-tight text-foreground leading-none">
45
- {schema?.label || slug}
46
- </h1>
47
- </div>
48
- </div>
49
-
50
- <div className="flex items-center gap-2">
51
- <Button
52
- variant="outline"
53
- size="sm"
54
- className="font-semibold h-9 px-4 text-xs gap-2"
55
- onClick={() => navigate('collection', { id: schema?.id, slug })}
56
- >
57
- <SettingsIcon className="size-3.5" />
58
- Config
59
- </Button>
60
- <Button
61
- size="sm"
62
- disabled={!hasFields}
63
- className="font-bold h-9 px-6 text-xs gap-2 shadow-sm"
64
- onClick={() => navigate('document_edit', { slug, id: 'new' })}
65
- >
66
- <PlusIcon className="size-3.5" />
67
- New Document
68
- </Button>
69
- </div>
70
- </header>
71
-
72
- {!hasFields && (
73
- <div className="p-8 border-2 border-dashed border-primary/20 bg-primary/5 rounded-xl animate-in fade-in slide-in-from-top-4 duration-500">
74
- <div className="flex flex-col md:flex-row items-center gap-6 text-center md:text-left">
75
- <div className="size-12 rounded-full bg-primary/10 flex items-center justify-center text-primary border border-primary/20 shrink-0">
76
- <SettingsIcon className="size-6" />
77
- </div>
78
- <div className="flex-1 space-y-1">
79
- <h3 className="text-sm font-bold uppercase tracking-widest text-primary">
80
- Immutable Data Structure
81
- </h3>
82
- <p className="text-xs text-muted-foreground font-medium leading-relaxed">
83
- This collection is currently defined with zero fields. In order
84
- to begin populating data, you must first initialize your schema
85
- definitions in the structural configuration panel.
86
- </p>
87
- </div>
88
- <Button
89
- variant="outline"
90
- size="sm"
91
- className="h-9 px-6 text-[10px] font-black uppercase tracking-widest border-primary/20 hover:bg-primary/10 hover:text-primary transition-colors"
92
- onClick={() => navigate('collection', { id: schema?.id, slug })}
93
- >
94
- Add Fields
95
- </Button>
96
- </div>
97
- </div>
98
- )}
99
-
100
- <ContentList slug={slug} />
101
- </div>
102
- );
103
- }