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