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
package/src/client/app.tsx
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { useStore } from '@nanostores/react';
|
|
3
|
-
import { $router, navigate } from './store/router';
|
|
4
|
-
import { $auth } from './store/auth';
|
|
5
|
-
import { $basePath } from './store/config';
|
|
6
|
-
import { $collections } from './store/collections';
|
|
7
|
-
import { loadSettings } from './store/settings';
|
|
8
|
-
import { apiFetch } from './lib/api';
|
|
9
|
-
|
|
10
|
-
import { AdminLayout } from './layouts/admin-layout';
|
|
11
|
-
import { LoginPage } from './pages/login';
|
|
12
|
-
import { SetupPage } from './pages/setup';
|
|
13
|
-
import { SignupPage } from './pages/signup';
|
|
14
|
-
import { DashboardPage } from './pages/dashboard';
|
|
15
|
-
import { CollectionsPage } from './pages/collections';
|
|
16
|
-
import { CollectionDetailPage } from './pages/collection-detail';
|
|
17
|
-
import { DevicePage } from './pages/device';
|
|
18
|
-
import { SettingsPage } from './pages/settings';
|
|
19
|
-
import { DocumentsPage } from './pages/documents-page';
|
|
20
|
-
import { DocumentDetailPage } from './pages/document-detail-page';
|
|
21
|
-
import { TooltipProvider } from './components/ui/tooltip';
|
|
22
|
-
|
|
23
|
-
export default function App() {
|
|
24
|
-
const page = useStore($router);
|
|
25
|
-
const auth = useStore($auth);
|
|
26
|
-
const collections = useStore($collections);
|
|
27
|
-
const [booting, setBooting] = useState(true);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
async function checkSetup() {
|
|
31
|
-
try {
|
|
32
|
-
// Initial check to trigger Zero-Config redirect if needed
|
|
33
|
-
await apiFetch('/health');
|
|
34
|
-
// Load global settings
|
|
35
|
-
await loadSettings();
|
|
36
|
-
} catch (err) {
|
|
37
|
-
console.error('Initial setup check failed:', err);
|
|
38
|
-
} finally {
|
|
39
|
-
setBooting(false);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
checkSetup();
|
|
43
|
-
}, []);
|
|
44
|
-
|
|
45
|
-
if (booting) {
|
|
46
|
-
return (
|
|
47
|
-
<div className="min-h-screen bg-background flex flex-col items-center justify-center p-6 antialiased">
|
|
48
|
-
<div className="size-16 bg-muted/30 rounded-2xl flex items-center justify-center mb-8 border border-border/50 shadow-sm relative overflow-hidden group">
|
|
49
|
-
<div className="absolute inset-0 bg-gradient-to-br from-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity" />
|
|
50
|
-
<div className="size-8 bg-primary/10 rounded-xl flex items-center justify-center border border-primary/20 shadow-inner animate-pulse">
|
|
51
|
-
<svg
|
|
52
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
-
width="16"
|
|
54
|
-
height="16"
|
|
55
|
-
viewBox="0 0 24 24"
|
|
56
|
-
fill="none"
|
|
57
|
-
stroke="currentColor"
|
|
58
|
-
strokeWidth="2.5"
|
|
59
|
-
strokeLinecap="round"
|
|
60
|
-
strokeLinejoin="round"
|
|
61
|
-
className="text-primary opacity-60"
|
|
62
|
-
>
|
|
63
|
-
<path d="M12 2v20" />
|
|
64
|
-
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
|
65
|
-
</svg>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
<div className="text-center space-y-2">
|
|
69
|
-
<h2 className="text-[10px] font-black uppercase tracking-[0.4em] text-foreground/40 animate-pulse">
|
|
70
|
-
System Synchronizing
|
|
71
|
-
</h2>
|
|
72
|
-
<p className="text-[9px] font-semibold text-muted-foreground/30 uppercase tracking-[0.2em]">
|
|
73
|
-
Establishing Secure Link
|
|
74
|
-
</p>
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// If setup route is active, bypass Auth Guard
|
|
81
|
-
if (page?.route === 'setup') {
|
|
82
|
-
return (
|
|
83
|
-
<TooltipProvider>
|
|
84
|
-
<SetupPage />
|
|
85
|
-
</TooltipProvider>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Auth Guard: If no token, show Login or Signup based on route
|
|
90
|
-
if (!auth.token) {
|
|
91
|
-
return (
|
|
92
|
-
<TooltipProvider>
|
|
93
|
-
{page?.route === 'signup' ? <SignupPage /> : <LoginPage />}
|
|
94
|
-
</TooltipProvider>
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Router Logic for authenticated users
|
|
99
|
-
if (!page) {
|
|
100
|
-
return (
|
|
101
|
-
<TooltipProvider>
|
|
102
|
-
<div className="min-h-screen bg-background flex items-center justify-center text-muted-foreground p-6">
|
|
103
|
-
<div className="text-center max-w-sm">
|
|
104
|
-
<h1 className="text-6xl font-black mb-4 tracking-tighter text-foreground opacity-10">
|
|
105
|
-
404
|
|
106
|
-
</h1>
|
|
107
|
-
<p className="text-[10px] font-semibold uppercase tracking-[0.2em] mb-8">
|
|
108
|
-
Document Module Not Found
|
|
109
|
-
</p>
|
|
110
|
-
<button
|
|
111
|
-
onClick={() => navigate('home')}
|
|
112
|
-
className="px-4 py-2 border rounded-md text-xs font-semibold uppercase tracking-widest hover:bg-accent transition-colors"
|
|
113
|
-
>
|
|
114
|
-
Return to Dashboard
|
|
115
|
-
</button>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</TooltipProvider>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Admin pages wrapped in shared Layout
|
|
123
|
-
return (
|
|
124
|
-
<TooltipProvider>
|
|
125
|
-
<AdminLayout>
|
|
126
|
-
{page.route === 'home' && <DashboardPage />}
|
|
127
|
-
{page.route === 'collections' && <CollectionsPage />}
|
|
128
|
-
{page.route === 'device' && <DevicePage />}
|
|
129
|
-
{page.route?.startsWith?.('settings') && <SettingsPage />}
|
|
130
|
-
|
|
131
|
-
{page.route === 'collection' && (
|
|
132
|
-
<CollectionDetailPage
|
|
133
|
-
id={page.params.id}
|
|
134
|
-
slug={page.params.slug}
|
|
135
|
-
onBack={() => navigate('collections')}
|
|
136
|
-
/>
|
|
137
|
-
)}
|
|
138
|
-
|
|
139
|
-
{page.route === 'document_list' && <DocumentsPage />}
|
|
140
|
-
{page.route === 'document_edit' && <DocumentDetailPage />}
|
|
141
|
-
|
|
142
|
-
{/* Login route while already authenticated -> redirect to home via layout logic or just show dashboard */}
|
|
143
|
-
{page.route === 'login' && <DashboardPage />}
|
|
144
|
-
|
|
145
|
-
{![
|
|
146
|
-
'home',
|
|
147
|
-
'collections',
|
|
148
|
-
'collection',
|
|
149
|
-
'document_list',
|
|
150
|
-
'document_edit',
|
|
151
|
-
'login',
|
|
152
|
-
'setup',
|
|
153
|
-
'device',
|
|
154
|
-
'settings',
|
|
155
|
-
'settings_general',
|
|
156
|
-
'settings_seo',
|
|
157
|
-
'settings_security',
|
|
158
|
-
'settings_signup',
|
|
159
|
-
].includes(page.route) && (
|
|
160
|
-
<div className="flex flex-col items-center justify-center py-40">
|
|
161
|
-
<div className="size-16 bg-muted rounded-xl flex items-center justify-center mb-8 border shadow-sm">
|
|
162
|
-
<div className="size-6 bg-primary/20 rounded-lg animate-pulse" />
|
|
163
|
-
</div>
|
|
164
|
-
<h2 className="text-2xl font-bold text-foreground tracking-tight">
|
|
165
|
-
Module Isolation
|
|
166
|
-
</h2>
|
|
167
|
-
<p className="text-[10px] font-semibold text-muted-foreground uppercase tracking-[0.3em] mt-3">
|
|
168
|
-
Feature currently offline
|
|
169
|
-
</p>
|
|
170
|
-
</div>
|
|
171
|
-
)}
|
|
172
|
-
</AdminLayout>
|
|
173
|
-
</TooltipProvider>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
LayoutDashboard as LayoutDashboardIcon,
|
|
3
|
-
Database as DatabaseIcon,
|
|
4
|
-
Users as UsersIcon,
|
|
5
|
-
Settings as SettingsIcon,
|
|
6
|
-
FileText as FileTextIcon,
|
|
7
|
-
Layers as LayersIcon,
|
|
8
|
-
MessageSquare as MessageSquareIcon,
|
|
9
|
-
Menu as MenuIcon,
|
|
10
|
-
LogOut as LogOutIcon,
|
|
11
|
-
Sparkles as SparklesIcon,
|
|
12
|
-
} from 'lucide-react';
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
Sidebar,
|
|
16
|
-
SidebarContent,
|
|
17
|
-
SidebarFooter,
|
|
18
|
-
SidebarHeader,
|
|
19
|
-
SidebarGroup,
|
|
20
|
-
SidebarGroupLabel,
|
|
21
|
-
SidebarGroupContent,
|
|
22
|
-
SidebarMenu,
|
|
23
|
-
SidebarMenuItem,
|
|
24
|
-
SidebarMenuButton,
|
|
25
|
-
SidebarRail,
|
|
26
|
-
} from './ui/sidebar';
|
|
27
|
-
import { $router, navigate } from '../store/router';
|
|
28
|
-
import { useStore } from '@nanostores/react';
|
|
29
|
-
import { $auth, logout } from '../store/auth';
|
|
30
|
-
import { Avatar, AvatarFallback } from './ui/avatar';
|
|
31
|
-
import {
|
|
32
|
-
Tooltip,
|
|
33
|
-
TooltipContent,
|
|
34
|
-
TooltipTrigger
|
|
35
|
-
} from './ui/tooltip';
|
|
36
|
-
import { useSidebar } from './ui/sidebar';
|
|
37
|
-
|
|
38
|
-
import { $settings } from '../store/settings';
|
|
39
|
-
|
|
40
|
-
import {
|
|
41
|
-
Collapsible,
|
|
42
|
-
|
|
43
|
-
CollapsibleContent,
|
|
44
|
-
CollapsibleTrigger,
|
|
45
|
-
} from './ui/collapsible';
|
|
46
|
-
import { ChevronRight } from 'lucide-react';
|
|
47
|
-
|
|
48
|
-
import { $collections } from '../store/collections';
|
|
49
|
-
|
|
50
|
-
import { Icon } from './ui/icon-picker';
|
|
51
|
-
|
|
52
|
-
interface AppSidebarProps {
|
|
53
|
-
variant?: 'sidebar' | 'floating' | 'inset';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function AppSidebar({ variant = 'sidebar' }: AppSidebarProps) {
|
|
57
|
-
const page = useStore($router);
|
|
58
|
-
const auth = useStore($auth);
|
|
59
|
-
const settings = useStore($settings);
|
|
60
|
-
const { data: collections } = useStore($collections);
|
|
61
|
-
|
|
62
|
-
const menuGroups = [
|
|
63
|
-
{
|
|
64
|
-
label: 'Content',
|
|
65
|
-
items: (collections || []).map((col) => ({
|
|
66
|
-
label: col.label,
|
|
67
|
-
icon: col.slug === 'pages' ? FileTextIcon : () => <Icon name={col.icon as any} />,
|
|
68
|
-
routeName: 'document_list',
|
|
69
|
-
params: { slug: col.slug },
|
|
70
|
-
})),
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
label: 'Manage',
|
|
74
|
-
items: [
|
|
75
|
-
{
|
|
76
|
-
label: 'Collections',
|
|
77
|
-
icon: DatabaseIcon,
|
|
78
|
-
routeName: 'collections',
|
|
79
|
-
active: page?.route === 'collections',
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
label: 'Admin',
|
|
85
|
-
items: [
|
|
86
|
-
{
|
|
87
|
-
label: 'Users',
|
|
88
|
-
icon: UsersIcon,
|
|
89
|
-
routeName: 'users',
|
|
90
|
-
active: page?.route === 'users',
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
label: 'Settings',
|
|
94
|
-
icon: SettingsIcon,
|
|
95
|
-
routeName: 'settings',
|
|
96
|
-
active: page?.route === 'settings',
|
|
97
|
-
},
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
];
|
|
101
|
-
|
|
102
|
-
return (
|
|
103
|
-
<Sidebar collapsible="icon" variant={variant}>
|
|
104
|
-
<SidebarHeader className="flex flex-row items-center px-4 py-6 gap-3 group-data-[collapsible=icon]:px-0 group-data-[collapsible=icon]:justify-center transition-[padding,justify-content]">
|
|
105
|
-
<Tooltip>
|
|
106
|
-
<TooltipTrigger render={<div className="flex size-8 items-center justify-center rounded-md bg-primary text-primary-foreground shrink-0 shadow-sm cursor-pointer" />}>
|
|
107
|
-
<SparklesIcon className="size-4" />
|
|
108
|
-
</TooltipTrigger>
|
|
109
|
-
<TooltipContent side="right" className="group-data-[collapsible=expanded]:hidden">
|
|
110
|
-
{settings['flare:site_title'] || 'FlareCMS'}
|
|
111
|
-
</TooltipContent>
|
|
112
|
-
</Tooltip>
|
|
113
|
-
<div className="flex flex-col gap-0.5 group-data-[collapsible=icon]:hidden">
|
|
114
|
-
<span className="font-semibold text-sidebar-foreground tracking-tight leading-none">
|
|
115
|
-
{settings['flare:site_title'] || 'FlareCMS'}
|
|
116
|
-
</span>
|
|
117
|
-
<span className="text-[10px] text-muted-foreground font-medium uppercase tracking-wider">
|
|
118
|
-
{settings['flare:site_tagline'] || 'Management'}
|
|
119
|
-
</span>
|
|
120
|
-
</div>
|
|
121
|
-
</SidebarHeader>
|
|
122
|
-
|
|
123
|
-
<SidebarContent className="px-2">
|
|
124
|
-
<SidebarGroup>
|
|
125
|
-
<SidebarMenu>
|
|
126
|
-
<SidebarMenuItem>
|
|
127
|
-
<SidebarMenuButton
|
|
128
|
-
isActive={page?.route === 'home'}
|
|
129
|
-
onClick={() => navigate('home')}
|
|
130
|
-
tooltip="Dashboard"
|
|
131
|
-
>
|
|
132
|
-
<LayoutDashboardIcon />
|
|
133
|
-
<span>Dashboard</span>
|
|
134
|
-
</SidebarMenuButton>
|
|
135
|
-
</SidebarMenuItem>
|
|
136
|
-
</SidebarMenu>
|
|
137
|
-
</SidebarGroup>
|
|
138
|
-
|
|
139
|
-
{menuGroups.map((group) => (
|
|
140
|
-
<Collapsible
|
|
141
|
-
key={group.label}
|
|
142
|
-
defaultOpen
|
|
143
|
-
className="group/collapsible"
|
|
144
|
-
>
|
|
145
|
-
<SidebarGroup>
|
|
146
|
-
<SidebarGroupLabel
|
|
147
|
-
render={
|
|
148
|
-
<CollapsibleTrigger className="group/trigger flex w-full items-center">
|
|
149
|
-
{group.label}
|
|
150
|
-
<ChevronRight className="ml-auto size-3.5 transition-transform duration-200 group-data-[state=open]/trigger:rotate-90" />
|
|
151
|
-
</CollapsibleTrigger>
|
|
152
|
-
}
|
|
153
|
-
className="group/label text-xs font-semibold uppercase tracking-wider text-muted-foreground/70 hover:text-foreground transition-colors cursor-pointer group-data-[collapsible=icon]:hidden"
|
|
154
|
-
/>
|
|
155
|
-
<CollapsibleContent>
|
|
156
|
-
<SidebarGroupContent>
|
|
157
|
-
<SidebarMenu>
|
|
158
|
-
{group.items.map((item) => (
|
|
159
|
-
<SidebarMenuItem key={item.label}>
|
|
160
|
-
<SidebarMenuButton
|
|
161
|
-
isActive={
|
|
162
|
-
(item as any).active || page?.route === item.routeName
|
|
163
|
-
}
|
|
164
|
-
onClick={() => navigate(item.routeName, (item as any).params)}
|
|
165
|
-
tooltip={item.label}
|
|
166
|
-
>
|
|
167
|
-
<item.icon />
|
|
168
|
-
<span>{item.label}</span>
|
|
169
|
-
</SidebarMenuButton>
|
|
170
|
-
</SidebarMenuItem>
|
|
171
|
-
))}
|
|
172
|
-
</SidebarMenu>
|
|
173
|
-
</SidebarGroupContent>
|
|
174
|
-
</CollapsibleContent>
|
|
175
|
-
</SidebarGroup>
|
|
176
|
-
</Collapsible>
|
|
177
|
-
))}
|
|
178
|
-
</SidebarContent>
|
|
179
|
-
|
|
180
|
-
<SidebarFooter className="p-4 group-data-[collapsible=icon]:px-0 group-data-[collapsible=icon]:py-4 transition-[padding]">
|
|
181
|
-
<div className="flex items-center gap-3 group-data-[collapsible=icon]:flex-col group-data-[collapsible=icon]:gap-4">
|
|
182
|
-
<Tooltip>
|
|
183
|
-
<TooltipTrigger render={<Avatar className="size-8 rounded-lg shrink-0 cursor-pointer" />}>
|
|
184
|
-
<AvatarFallback className="text-[10px] font-semibold">
|
|
185
|
-
{auth.user?.email.substring(0, 2).toUpperCase()}
|
|
186
|
-
</AvatarFallback>
|
|
187
|
-
</TooltipTrigger>
|
|
188
|
-
<TooltipContent side="right" className="group-data-[collapsible=expanded]:hidden">
|
|
189
|
-
{auth.user?.email}
|
|
190
|
-
</TooltipContent>
|
|
191
|
-
</Tooltip>
|
|
192
|
-
|
|
193
|
-
<div className="flex-1 min-w-0 overflow-hidden group-data-[collapsible=icon]:hidden">
|
|
194
|
-
<p className="text-xs font-medium truncate leading-none mb-1 text-sidebar-foreground">
|
|
195
|
-
{auth.user?.email.split('@')[0]}
|
|
196
|
-
</p>
|
|
197
|
-
<button
|
|
198
|
-
onClick={() => logout()}
|
|
199
|
-
className="text-[10px] text-muted-foreground hover:text-primary transition-colors font-medium"
|
|
200
|
-
>
|
|
201
|
-
Sign out
|
|
202
|
-
</button>
|
|
203
|
-
</div>
|
|
204
|
-
|
|
205
|
-
<SidebarMenu className="w-fit group-data-[collapsible=expanded]:block hidden">
|
|
206
|
-
<SidebarMenuItem>
|
|
207
|
-
<SidebarMenuButton size="sm" onClick={() => logout()} tooltip="Sign out" className="text-muted-foreground/50 hover:text-destructive">
|
|
208
|
-
<LogOutIcon />
|
|
209
|
-
</SidebarMenuButton>
|
|
210
|
-
</SidebarMenuItem>
|
|
211
|
-
</SidebarMenu>
|
|
212
|
-
|
|
213
|
-
<div className="group-data-[collapsible=icon]:block hidden">
|
|
214
|
-
<SidebarMenu>
|
|
215
|
-
<SidebarMenuItem>
|
|
216
|
-
<SidebarMenuButton size="sm" onClick={() => logout()} tooltip="Sign out" className="text-muted-foreground/50 hover:text-destructive">
|
|
217
|
-
<LogOutIcon />
|
|
218
|
-
</SidebarMenuButton>
|
|
219
|
-
</SidebarMenuItem>
|
|
220
|
-
</SidebarMenu>
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
</SidebarFooter>
|
|
224
|
-
<SidebarRail />
|
|
225
|
-
</Sidebar>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Loader2Icon, ChevronRightIcon, SparklesIcon } from 'lucide-react';
|
|
3
|
-
import { api } from '../lib/api';
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
Dialog,
|
|
7
|
-
DialogContent,
|
|
8
|
-
DialogHeader,
|
|
9
|
-
DialogTitle,
|
|
10
|
-
DialogTrigger,
|
|
11
|
-
} from './ui/dialog';
|
|
12
|
-
import { Button } from './ui/button';
|
|
13
|
-
import { Input } from './ui/input';
|
|
14
|
-
import { Label } from './ui/label';
|
|
15
|
-
import { IconPicker, Icon } from './ui/icon-picker';
|
|
16
|
-
import { Switch } from './ui/switch';
|
|
17
|
-
import { cn } from '../lib/utils';
|
|
18
|
-
|
|
19
|
-
interface CollectionModalProps {
|
|
20
|
-
children: React.ReactElement;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function CollectionModal({ children }: CollectionModalProps) {
|
|
24
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
25
|
-
const [loading, setLoading] = useState(false);
|
|
26
|
-
const [data, setData] = useState({
|
|
27
|
-
slug: '',
|
|
28
|
-
label: '',
|
|
29
|
-
labelSingular: '',
|
|
30
|
-
description: '',
|
|
31
|
-
icon: 'package' as any,
|
|
32
|
-
isPublic: false,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const [isSlugDirty, setIsSlugDirty] = useState(false);
|
|
36
|
-
const [isSingularDirty, setIsSingularDirty] = useState(false);
|
|
37
|
-
|
|
38
|
-
const handleSubmit = async (e: React.FormEvent) => {
|
|
39
|
-
e.preventDefault();
|
|
40
|
-
setLoading(true);
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
await api.post('/collections', { json: data });
|
|
44
|
-
setIsOpen(false);
|
|
45
|
-
setData({
|
|
46
|
-
slug: '',
|
|
47
|
-
label: '',
|
|
48
|
-
labelSingular: '',
|
|
49
|
-
description: '',
|
|
50
|
-
icon: 'package',
|
|
51
|
-
isPublic: false,
|
|
52
|
-
});
|
|
53
|
-
window.location.reload();
|
|
54
|
-
} catch (err) {
|
|
55
|
-
console.error('Failed to create collection:', err);
|
|
56
|
-
} finally {
|
|
57
|
-
setLoading(false);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const update = (key: string, value: any) => {
|
|
62
|
-
setData((prev) => {
|
|
63
|
-
const newData = { ...prev, [key]: value };
|
|
64
|
-
|
|
65
|
-
if (key === 'label') {
|
|
66
|
-
if (!isSlugDirty) {
|
|
67
|
-
newData.slug = value.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (!isSingularDirty) {
|
|
71
|
-
newData.labelSingular = value.replace(/s$/i, '') || value;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return newData;
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
return (
|
|
80
|
-
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
81
|
-
<DialogTrigger render={children} />
|
|
82
|
-
<DialogContent className="sm:max-w-[700px] p-0 overflow-hidden border-none shadow-2xl">
|
|
83
|
-
<form onSubmit={handleSubmit}>
|
|
84
|
-
{/* Header Section */}
|
|
85
|
-
<div className="bg-linear-to-b from-muted/50 to-background px-8 pt-8 pb-6 border-b">
|
|
86
|
-
<div className="flex items-center gap-2 mb-3">
|
|
87
|
-
<div className="bg-primary/10 text-primary px-2 py-0.5 rounded text-[10px] font-bold uppercase tracking-widest flex items-center gap-1.5">
|
|
88
|
-
<SparklesIcon className="size-3" />
|
|
89
|
-
Infrastructure
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
|
-
<DialogTitle className="text-2xl font-semibold tracking-tight">
|
|
93
|
-
Create Collection
|
|
94
|
-
</DialogTitle>
|
|
95
|
-
<p className="text-muted-foreground text-xs mt-1.5">
|
|
96
|
-
Define the blueprint for your new high-performance data module.
|
|
97
|
-
</p>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<div className="p-8 space-y-6">
|
|
101
|
-
<div className="grid grid-cols-2 gap-4">
|
|
102
|
-
<div className="space-y-2.5">
|
|
103
|
-
<Label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70">
|
|
104
|
-
Display Label
|
|
105
|
-
</Label>
|
|
106
|
-
<Input
|
|
107
|
-
value={data.label}
|
|
108
|
-
onChange={(e) => update('label', e.target.value)}
|
|
109
|
-
placeholder="Blog Posts"
|
|
110
|
-
required
|
|
111
|
-
className="h-10 font-medium"
|
|
112
|
-
/>
|
|
113
|
-
</div>
|
|
114
|
-
<div className="space-y-2.5">
|
|
115
|
-
<Label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70">
|
|
116
|
-
Singular Label
|
|
117
|
-
</Label>
|
|
118
|
-
<Input
|
|
119
|
-
value={data.labelSingular}
|
|
120
|
-
onChange={(e) => {
|
|
121
|
-
setIsSingularDirty(true);
|
|
122
|
-
update('labelSingular', e.target.value);
|
|
123
|
-
}}
|
|
124
|
-
placeholder="Post"
|
|
125
|
-
className="h-10 font-medium"
|
|
126
|
-
/>
|
|
127
|
-
</div>
|
|
128
|
-
</div>
|
|
129
|
-
|
|
130
|
-
<div className="space-y-2.5">
|
|
131
|
-
<Label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70">
|
|
132
|
-
Identification Slug
|
|
133
|
-
</Label>
|
|
134
|
-
<div className="relative group">
|
|
135
|
-
<Input
|
|
136
|
-
value={data.slug}
|
|
137
|
-
onChange={(e) => {
|
|
138
|
-
setIsSlugDirty(true);
|
|
139
|
-
update('slug', e.target.value);
|
|
140
|
-
}}
|
|
141
|
-
placeholder="blog-posts"
|
|
142
|
-
required
|
|
143
|
-
className="h-10 font-mono text-xs pl-8 border-dashed group-focus-within:border-solid transition-all"
|
|
144
|
-
/>
|
|
145
|
-
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground/40 font-mono text-xs">
|
|
146
|
-
/
|
|
147
|
-
</span>
|
|
148
|
-
</div>
|
|
149
|
-
</div>
|
|
150
|
-
|
|
151
|
-
<div className="grid grid-cols-2 gap-8 pt-2">
|
|
152
|
-
<div className="space-y-2.5">
|
|
153
|
-
<Label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70">
|
|
154
|
-
Module Icon
|
|
155
|
-
</Label>
|
|
156
|
-
<IconPicker
|
|
157
|
-
value={data.icon}
|
|
158
|
-
onValueChange={(v) => update('icon', v)}
|
|
159
|
-
/>
|
|
160
|
-
</div>
|
|
161
|
-
<div className="space-y-2.5 flex flex-col justify-center">
|
|
162
|
-
<Label className="text-[10px] font-bold uppercase tracking-wider text-muted-foreground/70 mb-1.5">
|
|
163
|
-
Access Policy
|
|
164
|
-
</Label>
|
|
165
|
-
<div
|
|
166
|
-
className={cn(
|
|
167
|
-
'flex items-center justify-between gap-3 px-3 py-2 rounded-lg border transition-all duration-300',
|
|
168
|
-
data.isPublic
|
|
169
|
-
? 'bg-primary/5 border-primary/20'
|
|
170
|
-
: 'bg-muted/30 border-border',
|
|
171
|
-
)}
|
|
172
|
-
>
|
|
173
|
-
<div className="flex flex-col">
|
|
174
|
-
<span className="text-[10px] font-bold uppercase tracking-tighter">
|
|
175
|
-
{data.isPublic ? 'Public API' : 'Private API'}
|
|
176
|
-
</span>
|
|
177
|
-
<span className="text-[9px] text-muted-foreground leading-none">
|
|
178
|
-
{data.isPublic ? 'Open to everyone' : 'Requires key'}
|
|
179
|
-
</span>
|
|
180
|
-
</div>
|
|
181
|
-
<Switch
|
|
182
|
-
checked={data.isPublic}
|
|
183
|
-
onCheckedChange={(checked) => update('isPublic', checked)}
|
|
184
|
-
/>
|
|
185
|
-
</div>
|
|
186
|
-
</div>
|
|
187
|
-
</div>
|
|
188
|
-
</div>
|
|
189
|
-
|
|
190
|
-
{/* Footer */}
|
|
191
|
-
<div className="px-8 py-6 bg-background/80 backdrop-blur-md border-t flex justify-end gap-3 sticky bottom-0">
|
|
192
|
-
<Button
|
|
193
|
-
type="button"
|
|
194
|
-
variant="ghost"
|
|
195
|
-
className="font-semibold text-xs h-10 px-6"
|
|
196
|
-
onClick={() => setIsOpen(false)}
|
|
197
|
-
>
|
|
198
|
-
Discard
|
|
199
|
-
</Button>
|
|
200
|
-
<Button
|
|
201
|
-
type="submit"
|
|
202
|
-
disabled={loading}
|
|
203
|
-
className="font-bold text-xs h-10 px-8 shadow-lg shadow-primary/20"
|
|
204
|
-
>
|
|
205
|
-
{loading ? (
|
|
206
|
-
<Loader2Icon className="size-4 animate-spin mr-2" />
|
|
207
|
-
) : null}
|
|
208
|
-
Initialize Collection
|
|
209
|
-
</Button>
|
|
210
|
-
</div>
|
|
211
|
-
</form>
|
|
212
|
-
</DialogContent>
|
|
213
|
-
</Dialog>
|
|
214
|
-
);
|
|
215
|
-
}
|