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.
- package/README.md +73 -0
- package/dist/auth/index.js +40 -0
- package/dist/cli/commands.js +389 -0
- package/dist/cli/index.js +403 -0
- package/dist/cli/mcp.js +209 -0
- package/dist/db/index.js +164 -0
- package/dist/index.js +17626 -0
- package/package.json +105 -0
- package/scripts/fix-api-paths.mjs +32 -0
- package/scripts/fix-imports.mjs +38 -0
- package/scripts/prefix-css.mjs +45 -0
- package/src/api/lib/cache.ts +45 -0
- package/src/api/lib/response.ts +40 -0
- package/src/api/middlewares/auth.ts +186 -0
- package/src/api/middlewares/cors.ts +10 -0
- package/src/api/middlewares/rbac.ts +85 -0
- package/src/api/routes/auth.ts +377 -0
- package/src/api/routes/collections.ts +205 -0
- package/src/api/routes/content.ts +175 -0
- package/src/api/routes/device.ts +160 -0
- package/src/api/routes/magic.ts +150 -0
- package/src/api/routes/mcp.ts +273 -0
- package/src/api/routes/oauth.ts +160 -0
- package/src/api/routes/settings.ts +43 -0
- package/src/api/routes/setup.ts +307 -0
- package/src/api/routes/tokens.ts +80 -0
- package/src/api/schemas/auth.ts +15 -0
- package/src/api/schemas/index.ts +51 -0
- package/src/api/schemas/tokens.ts +24 -0
- package/src/auth/index.ts +28 -0
- package/src/cli/commands.ts +217 -0
- package/src/cli/index.ts +21 -0
- package/src/cli/mcp.ts +210 -0
- package/src/cli/tests/cli.test.ts +40 -0
- package/src/cli/tests/create.test.ts +87 -0
- package/src/client/FlareAdminRouter.tsx +47 -0
- package/src/client/app.tsx +175 -0
- package/src/client/components/app-sidebar.tsx +227 -0
- package/src/client/components/collection-modal.tsx +215 -0
- package/src/client/components/content-list.tsx +247 -0
- package/src/client/components/dynamic-form.tsx +190 -0
- package/src/client/components/field-modal.tsx +221 -0
- package/src/client/components/settings/api-token-section.tsx +400 -0
- package/src/client/components/settings/general-section.tsx +224 -0
- package/src/client/components/settings/security-section.tsx +154 -0
- package/src/client/components/settings/seo-section.tsx +200 -0
- package/src/client/components/settings/signup-section.tsx +257 -0
- package/src/client/components/ui/accordion.tsx +78 -0
- package/src/client/components/ui/avatar.tsx +107 -0
- package/src/client/components/ui/badge.tsx +52 -0
- package/src/client/components/ui/button.tsx +60 -0
- package/src/client/components/ui/card.tsx +103 -0
- package/src/client/components/ui/checkbox.tsx +27 -0
- package/src/client/components/ui/collapsible.tsx +19 -0
- package/src/client/components/ui/dialog.tsx +162 -0
- package/src/client/components/ui/icon-picker.tsx +485 -0
- package/src/client/components/ui/icons-data.ts +8476 -0
- package/src/client/components/ui/input.tsx +20 -0
- package/src/client/components/ui/label.tsx +20 -0
- package/src/client/components/ui/popover.tsx +91 -0
- package/src/client/components/ui/select.tsx +204 -0
- package/src/client/components/ui/separator.tsx +23 -0
- package/src/client/components/ui/sheet.tsx +141 -0
- package/src/client/components/ui/sidebar.tsx +722 -0
- package/src/client/components/ui/skeleton.tsx +13 -0
- package/src/client/components/ui/sonner.tsx +47 -0
- package/src/client/components/ui/switch.tsx +30 -0
- package/src/client/components/ui/table.tsx +116 -0
- package/src/client/components/ui/tabs.tsx +80 -0
- package/src/client/components/ui/textarea.tsx +18 -0
- package/src/client/components/ui/tooltip.tsx +68 -0
- package/src/client/hooks/use-mobile.ts +19 -0
- package/src/client/index.css +149 -0
- package/src/client/index.ts +7 -0
- package/src/client/layouts/admin-layout.tsx +93 -0
- package/src/client/layouts/settings-layout.tsx +104 -0
- package/src/client/lib/api.ts +72 -0
- package/src/client/lib/utils.ts +6 -0
- package/src/client/main.tsx +10 -0
- package/src/client/pages/collection-detail.tsx +634 -0
- package/src/client/pages/collections.tsx +180 -0
- package/src/client/pages/dashboard.tsx +133 -0
- package/src/client/pages/device.tsx +66 -0
- package/src/client/pages/document-detail-page.tsx +139 -0
- package/src/client/pages/documents-page.tsx +103 -0
- package/src/client/pages/login.tsx +345 -0
- package/src/client/pages/settings.tsx +65 -0
- package/src/client/pages/setup.tsx +129 -0
- package/src/client/pages/signup.tsx +188 -0
- package/src/client/store/auth.ts +30 -0
- package/src/client/store/collections.ts +13 -0
- package/src/client/store/config.ts +12 -0
- package/src/client/store/fetcher.ts +30 -0
- package/src/client/store/router.ts +95 -0
- package/src/client/store/schema.ts +39 -0
- package/src/client/store/settings.ts +31 -0
- package/src/client/types.ts +34 -0
- package/src/db/dynamic.ts +70 -0
- package/src/db/index.ts +16 -0
- package/src/db/migrations/001_initial_schema.ts +57 -0
- package/src/db/migrations/002_auth_tables.ts +84 -0
- package/src/db/migrator.ts +61 -0
- package/src/db/schema.ts +142 -0
- package/src/index.ts +12 -0
- package/src/server/index.ts +66 -0
- package/src/types.ts +20 -0
- package/style.css.d.ts +8 -0
- package/tests/css.test.ts +21 -0
- package/tests/modular.test.ts +29 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils"
|
|
2
|
+
|
|
3
|
+
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
data-slot="skeleton"
|
|
7
|
+
className={cn("animate-pulse rounded-md bg-muted", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { Skeleton }
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useTheme } from "next-themes"
|
|
2
|
+
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
|
3
|
+
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
|
|
4
|
+
|
|
5
|
+
const Toaster = ({ ...props }: ToasterProps) => {
|
|
6
|
+
const { theme = "system" } = useTheme()
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<Sonner
|
|
10
|
+
theme={theme as ToasterProps["theme"]}
|
|
11
|
+
className="toaster group"
|
|
12
|
+
icons={{
|
|
13
|
+
success: (
|
|
14
|
+
<CircleCheckIcon className="size-4" />
|
|
15
|
+
),
|
|
16
|
+
info: (
|
|
17
|
+
<InfoIcon className="size-4" />
|
|
18
|
+
),
|
|
19
|
+
warning: (
|
|
20
|
+
<TriangleAlertIcon className="size-4" />
|
|
21
|
+
),
|
|
22
|
+
error: (
|
|
23
|
+
<OctagonXIcon className="size-4" />
|
|
24
|
+
),
|
|
25
|
+
loading: (
|
|
26
|
+
<Loader2Icon className="size-4 animate-spin" />
|
|
27
|
+
),
|
|
28
|
+
}}
|
|
29
|
+
style={
|
|
30
|
+
{
|
|
31
|
+
"--normal-bg": "var(--popover)",
|
|
32
|
+
"--normal-text": "var(--popover-foreground)",
|
|
33
|
+
"--normal-border": "var(--border)",
|
|
34
|
+
"--border-radius": "var(--radius)",
|
|
35
|
+
} as React.CSSProperties
|
|
36
|
+
}
|
|
37
|
+
toastOptions={{
|
|
38
|
+
classNames: {
|
|
39
|
+
toast: "cn-toast",
|
|
40
|
+
},
|
|
41
|
+
}}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export { Toaster }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Switch as SwitchPrimitive } from '@base-ui/react/switch';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils';
|
|
4
|
+
|
|
5
|
+
function Switch({
|
|
6
|
+
className,
|
|
7
|
+
size = 'default',
|
|
8
|
+
...props
|
|
9
|
+
}: SwitchPrimitive.Root.Props & {
|
|
10
|
+
size?: 'sm' | 'default';
|
|
11
|
+
}) {
|
|
12
|
+
return (
|
|
13
|
+
<SwitchPrimitive.Root
|
|
14
|
+
data-slot="switch"
|
|
15
|
+
data-size={size}
|
|
16
|
+
className={cn(
|
|
17
|
+
'peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50',
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
<SwitchPrimitive.Thumb
|
|
23
|
+
data-slot="switch-thumb"
|
|
24
|
+
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
|
25
|
+
/>
|
|
26
|
+
</SwitchPrimitive.Root>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { Switch };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
8
|
+
return (
|
|
9
|
+
<div
|
|
10
|
+
data-slot="table-container"
|
|
11
|
+
className="relative w-full overflow-x-auto"
|
|
12
|
+
>
|
|
13
|
+
<table
|
|
14
|
+
data-slot="table"
|
|
15
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
</div>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
|
23
|
+
return (
|
|
24
|
+
<thead
|
|
25
|
+
data-slot="table-header"
|
|
26
|
+
className={cn("[&_tr]:border-b", className)}
|
|
27
|
+
{...props}
|
|
28
|
+
/>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
|
33
|
+
return (
|
|
34
|
+
<tbody
|
|
35
|
+
data-slot="table-body"
|
|
36
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
37
|
+
{...props}
|
|
38
|
+
/>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
|
43
|
+
return (
|
|
44
|
+
<tfoot
|
|
45
|
+
data-slot="table-footer"
|
|
46
|
+
className={cn(
|
|
47
|
+
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
56
|
+
return (
|
|
57
|
+
<tr
|
|
58
|
+
data-slot="table-row"
|
|
59
|
+
className={cn(
|
|
60
|
+
"border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
|
|
61
|
+
className
|
|
62
|
+
)}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
69
|
+
return (
|
|
70
|
+
<th
|
|
71
|
+
data-slot="table-head"
|
|
72
|
+
className={cn(
|
|
73
|
+
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0",
|
|
74
|
+
className
|
|
75
|
+
)}
|
|
76
|
+
{...props}
|
|
77
|
+
/>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
82
|
+
return (
|
|
83
|
+
<td
|
|
84
|
+
data-slot="table-cell"
|
|
85
|
+
className={cn(
|
|
86
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0",
|
|
87
|
+
className
|
|
88
|
+
)}
|
|
89
|
+
{...props}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function TableCaption({
|
|
95
|
+
className,
|
|
96
|
+
...props
|
|
97
|
+
}: React.ComponentProps<"caption">) {
|
|
98
|
+
return (
|
|
99
|
+
<caption
|
|
100
|
+
data-slot="table-caption"
|
|
101
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export {
|
|
108
|
+
Table,
|
|
109
|
+
TableHeader,
|
|
110
|
+
TableBody,
|
|
111
|
+
TableFooter,
|
|
112
|
+
TableHead,
|
|
113
|
+
TableRow,
|
|
114
|
+
TableCell,
|
|
115
|
+
TableCaption,
|
|
116
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
|
|
6
|
+
function Tabs({
|
|
7
|
+
className,
|
|
8
|
+
orientation = "horizontal",
|
|
9
|
+
...props
|
|
10
|
+
}: TabsPrimitive.Root.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<TabsPrimitive.Root
|
|
13
|
+
data-slot="tabs"
|
|
14
|
+
data-orientation={orientation}
|
|
15
|
+
className={cn(
|
|
16
|
+
"group/tabs flex gap-2 data-horizontal:flex-col",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const tabsListVariants = cva(
|
|
25
|
+
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
|
|
26
|
+
{
|
|
27
|
+
variants: {
|
|
28
|
+
variant: {
|
|
29
|
+
default: "bg-muted",
|
|
30
|
+
line: "gap-1 bg-transparent",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: {
|
|
34
|
+
variant: "default",
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
function TabsList({
|
|
40
|
+
className,
|
|
41
|
+
variant = "default",
|
|
42
|
+
...props
|
|
43
|
+
}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
|
|
44
|
+
return (
|
|
45
|
+
<TabsPrimitive.List
|
|
46
|
+
data-slot="tabs-list"
|
|
47
|
+
data-variant={variant}
|
|
48
|
+
className={cn(tabsListVariants({ variant }), className)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
|
|
55
|
+
return (
|
|
56
|
+
<TabsPrimitive.Tab
|
|
57
|
+
data-slot="tabs-trigger"
|
|
58
|
+
className={cn(
|
|
59
|
+
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
60
|
+
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
|
61
|
+
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
|
62
|
+
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
|
63
|
+
className
|
|
64
|
+
)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
|
|
71
|
+
return (
|
|
72
|
+
<TabsPrimitive.Panel
|
|
73
|
+
data-slot="tabs-content"
|
|
74
|
+
className={cn("flex-1 text-sm outline-none", className)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../lib/utils';
|
|
4
|
+
|
|
5
|
+
function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {
|
|
6
|
+
return (
|
|
7
|
+
<textarea
|
|
8
|
+
data-slot="textarea"
|
|
9
|
+
className={cn(
|
|
10
|
+
'flex field-sizing-content min-h-16 w-full rounded-lg border border-input bg-transparent px-2.5 py-2 text-base transition-colors outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40',
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { Textarea };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils"
|
|
6
|
+
|
|
7
|
+
function TooltipProvider({
|
|
8
|
+
delay = 0,
|
|
9
|
+
...props
|
|
10
|
+
}: TooltipPrimitive.Provider.Props) {
|
|
11
|
+
return (
|
|
12
|
+
<TooltipPrimitive.Provider
|
|
13
|
+
data-slot="tooltip-provider"
|
|
14
|
+
delay={delay}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
|
|
21
|
+
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function TooltipTrigger({ ...props }: TooltipPrimitive.Trigger.Props) {
|
|
25
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function TooltipContent({
|
|
29
|
+
className,
|
|
30
|
+
side = "top",
|
|
31
|
+
sideOffset = 4,
|
|
32
|
+
align = "center",
|
|
33
|
+
alignOffset = 0,
|
|
34
|
+
children,
|
|
35
|
+
...props
|
|
36
|
+
}: TooltipPrimitive.Popup.Props &
|
|
37
|
+
Pick<
|
|
38
|
+
TooltipPrimitive.Positioner.Props,
|
|
39
|
+
"align" | "alignOffset" | "side" | "sideOffset"
|
|
40
|
+
>) {
|
|
41
|
+
return (
|
|
42
|
+
<TooltipPrimitive.Portal>
|
|
43
|
+
<div className="flare-admin text-foreground">
|
|
44
|
+
<TooltipPrimitive.Positioner
|
|
45
|
+
align={align}
|
|
46
|
+
alignOffset={alignOffset}
|
|
47
|
+
side={side}
|
|
48
|
+
sideOffset={sideOffset}
|
|
49
|
+
className="isolate z-50"
|
|
50
|
+
>
|
|
51
|
+
<TooltipPrimitive.Popup
|
|
52
|
+
data-slot="tooltip-content"
|
|
53
|
+
className={cn(
|
|
54
|
+
"z-50 inline-flex w-fit max-w-xs origin-(--transform-origin) items-center gap-1.5 rounded-md bg-foreground px-3 py-1.5 text-xs text-background has-data-[slot=kbd]:pr-1.5 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 **:data-[slot=kbd]:relative **:data-[slot=kbd]:isolate **:data-[slot=kbd]:z-50 **:data-[slot=kbd]:rounded-sm data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
55
|
+
className
|
|
56
|
+
)}
|
|
57
|
+
{...props}
|
|
58
|
+
>
|
|
59
|
+
{children}
|
|
60
|
+
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%-2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground data-[side=bottom]:top-1 data-[side=inline-end]:top-1/2! data-[side=inline-end]:-left-1 data-[side=inline-end]:-translate-y-1/2 data-[side=inline-start]:top-1/2! data-[side=inline-start]:-right-1 data-[side=inline-start]:-translate-y-1/2 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
|
|
61
|
+
</TooltipPrimitive.Popup>
|
|
62
|
+
</TooltipPrimitive.Positioner>
|
|
63
|
+
</div>
|
|
64
|
+
</TooltipPrimitive.Portal>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
const MOBILE_BREAKPOINT = 768
|
|
4
|
+
|
|
5
|
+
export function useIsMobile() {
|
|
6
|
+
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
|
7
|
+
|
|
8
|
+
React.useEffect(() => {
|
|
9
|
+
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
|
10
|
+
const onChange = () => {
|
|
11
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
12
|
+
}
|
|
13
|
+
mql.addEventListener("change", onChange)
|
|
14
|
+
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
|
15
|
+
return () => mql.removeEventListener("change", onChange)
|
|
16
|
+
}, [])
|
|
17
|
+
|
|
18
|
+
return !!isMobile
|
|
19
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "shadcn/tailwind.css";
|
|
4
|
+
@import "@fontsource-variable/geist";
|
|
5
|
+
|
|
6
|
+
@custom-variant dark (&:is(.dark *));
|
|
7
|
+
|
|
8
|
+
@theme inline {
|
|
9
|
+
--font-heading: var(--font-sans);
|
|
10
|
+
--font-sans: 'Geist Variable', sans-serif;
|
|
11
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
12
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
13
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
14
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
15
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
16
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
17
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
18
|
+
--color-sidebar: var(--sidebar);
|
|
19
|
+
--color-chart-5: var(--chart-5);
|
|
20
|
+
--color-chart-4: var(--chart-4);
|
|
21
|
+
--color-chart-3: var(--chart-3);
|
|
22
|
+
--color-chart-2: var(--chart-2);
|
|
23
|
+
--color-chart-1: var(--chart-1);
|
|
24
|
+
--color-ring: var(--ring);
|
|
25
|
+
--color-input: var(--input);
|
|
26
|
+
--color-border: var(--border);
|
|
27
|
+
--color-destructive: var(--destructive);
|
|
28
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
29
|
+
--color-accent: var(--accent);
|
|
30
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
31
|
+
--color-muted: var(--muted);
|
|
32
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
33
|
+
--color-secondary: var(--secondary);
|
|
34
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
35
|
+
--color-primary: var(--primary);
|
|
36
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
37
|
+
--color-popover: var(--popover);
|
|
38
|
+
--color-card-foreground: var(--card-foreground);
|
|
39
|
+
--color-card: var(--card);
|
|
40
|
+
--color-foreground: var(--foreground);
|
|
41
|
+
--color-background: var(--background);
|
|
42
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
43
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
44
|
+
--radius-lg: var(--radius);
|
|
45
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
46
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
47
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
48
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
:root {
|
|
52
|
+
--background: oklch(1 0 0);
|
|
53
|
+
--foreground: oklch(0.145 0 0);
|
|
54
|
+
--card: oklch(1 0 0);
|
|
55
|
+
--card-foreground: oklch(0.145 0 0);
|
|
56
|
+
--popover: oklch(1 0 0);
|
|
57
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
58
|
+
--primary: oklch(0.205 0 0);
|
|
59
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
60
|
+
--secondary: oklch(0.97 0 0);
|
|
61
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
62
|
+
--muted: oklch(0.97 0 0);
|
|
63
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
64
|
+
--accent: oklch(0.97 0 0);
|
|
65
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
66
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
67
|
+
--border: oklch(0.922 0 0);
|
|
68
|
+
--input: oklch(0.922 0 0);
|
|
69
|
+
--ring: oklch(0.708 0 0);
|
|
70
|
+
--chart-1: oklch(0.87 0 0);
|
|
71
|
+
--chart-2: oklch(0.556 0 0);
|
|
72
|
+
--chart-3: oklch(0.439 0 0);
|
|
73
|
+
--chart-4: oklch(0.371 0 0);
|
|
74
|
+
--chart-5: oklch(0.269 0 0);
|
|
75
|
+
--radius: 0.625rem;
|
|
76
|
+
--sidebar: oklch(0.985 0 0);
|
|
77
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
78
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
79
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
80
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
81
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
82
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
83
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.dark {
|
|
87
|
+
--background: oklch(0.145 0 0);
|
|
88
|
+
--foreground: oklch(0.985 0 0);
|
|
89
|
+
--card: oklch(0.205 0 0);
|
|
90
|
+
--card-foreground: oklch(0.985 0 0);
|
|
91
|
+
--popover: oklch(0.205 0 0);
|
|
92
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
93
|
+
--primary: oklch(0.922 0 0);
|
|
94
|
+
--primary-foreground: oklch(0.205 0 0);
|
|
95
|
+
--secondary: oklch(0.269 0 0);
|
|
96
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
97
|
+
--muted: oklch(0.269 0 0);
|
|
98
|
+
--muted-foreground: oklch(0.708 0 0);
|
|
99
|
+
--accent: oklch(0.269 0 0);
|
|
100
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
101
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
102
|
+
--border: oklch(1 0 0 / 10%);
|
|
103
|
+
--input: oklch(1 0 0 / 15%);
|
|
104
|
+
--ring: oklch(0.556 0 0);
|
|
105
|
+
--chart-1: oklch(0.87 0 0);
|
|
106
|
+
--chart-2: oklch(0.556 0 0);
|
|
107
|
+
--chart-3: oklch(0.439 0 0);
|
|
108
|
+
--chart-4: oklch(0.371 0 0);
|
|
109
|
+
--chart-5: oklch(0.269 0 0);
|
|
110
|
+
--sidebar: oklch(0.205 0 0);
|
|
111
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
112
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
113
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
114
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
115
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
116
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
117
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@layer base {
|
|
121
|
+
* {
|
|
122
|
+
@apply border-border outline-ring/50;
|
|
123
|
+
}
|
|
124
|
+
body {
|
|
125
|
+
@apply bg-background text-foreground;
|
|
126
|
+
}
|
|
127
|
+
html {
|
|
128
|
+
@apply font-sans;
|
|
129
|
+
}
|
|
130
|
+
button:not(:disabled),
|
|
131
|
+
[role="button"]:not(:disabled) {
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@layer utilities {
|
|
137
|
+
[data-slot="collapsible-content"] {
|
|
138
|
+
overflow: hidden;
|
|
139
|
+
transition: height 0.2s ease-out, opacity 0.2s ease-out;
|
|
140
|
+
}
|
|
141
|
+
[data-slot="collapsible-content"][data-state="closed"] {
|
|
142
|
+
height: 0;
|
|
143
|
+
opacity: 0;
|
|
144
|
+
}
|
|
145
|
+
[data-slot="collapsible-content"][data-state="open"] {
|
|
146
|
+
height: var(--base-ui-collapsible-panel-height);
|
|
147
|
+
opacity: 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './FlareAdminRouter';
|
|
2
|
+
export { default as FlareAdminApp } from './app';
|
|
3
|
+
// Add other useful exports for users who want to build custom admin parts
|
|
4
|
+
export * from './store/auth';
|
|
5
|
+
export * from './store/collections';
|
|
6
|
+
export * from './store/router';
|
|
7
|
+
export * from './lib/api';
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useStore } from '@nanostores/react';
|
|
3
|
+
import { $router } from '../store/router';
|
|
4
|
+
import { AppSidebar } from '../components/app-sidebar';
|
|
5
|
+
import {
|
|
6
|
+
SidebarProvider,
|
|
7
|
+
SidebarInset,
|
|
8
|
+
SidebarTrigger,
|
|
9
|
+
} from '../components/ui/sidebar';
|
|
10
|
+
import { Button } from '../components/ui/button';
|
|
11
|
+
import { Separator } from '../components/ui/separator';
|
|
12
|
+
import {
|
|
13
|
+
Search as SearchIcon,
|
|
14
|
+
Bell as BellIcon,
|
|
15
|
+
ExternalLink as ExternalLinkIcon,
|
|
16
|
+
} from 'lucide-react';
|
|
17
|
+
|
|
18
|
+
interface AdminLayoutProps {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function AdminLayout({ children }: AdminLayoutProps) {
|
|
23
|
+
const page = useStore($router);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<SidebarProvider>
|
|
27
|
+
<AppSidebar variant="inset" />
|
|
28
|
+
<SidebarInset className="flex flex-col h-screen overflow-hidden">
|
|
29
|
+
{/* Site Header */}
|
|
30
|
+
<header className="flex h-14 shrink-0 items-center gap-2 border-b">
|
|
31
|
+
<div className="flex w-full items-center gap-4 px-6">
|
|
32
|
+
<SidebarTrigger className="size-8 text-muted-foreground hover:text-foreground transition-colors" />
|
|
33
|
+
|
|
34
|
+
<Separator orientation="vertical" className="h-4 bg-border" />
|
|
35
|
+
|
|
36
|
+
<div className="flex flex-col">
|
|
37
|
+
<div className="flex items-center gap-2 text-muted-foreground text-[10px] font-medium uppercase tracking-wider leading-none mb-0.5">
|
|
38
|
+
{page?.route === 'home'
|
|
39
|
+
? 'Administration'
|
|
40
|
+
: page?.route === 'collections'
|
|
41
|
+
? 'Infrastructure'
|
|
42
|
+
: 'Resource'}
|
|
43
|
+
</div>
|
|
44
|
+
<h2 className="text-foreground font-semibold text-sm tracking-tight capitalize leading-none">
|
|
45
|
+
{page?.route === 'home'
|
|
46
|
+
? 'Dashboard'
|
|
47
|
+
: page?.route === 'collections'
|
|
48
|
+
? 'Collections Manager'
|
|
49
|
+
: page?.route?.replace(/-/g, ' ')}
|
|
50
|
+
</h2>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div className="ml-auto flex items-center gap-4">
|
|
54
|
+
<div className="relative hidden lg:block group">
|
|
55
|
+
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 size-3.5 text-muted-foreground transition-colors" />
|
|
56
|
+
<input
|
|
57
|
+
type="text"
|
|
58
|
+
placeholder="Universal search..."
|
|
59
|
+
className="bg-muted/50 border h-9 w-64 rounded-md pl-9 pr-4 text-xs focus:bg-background transition-all outline-none"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="flex items-center gap-1">
|
|
64
|
+
<Button
|
|
65
|
+
variant="ghost"
|
|
66
|
+
size="icon"
|
|
67
|
+
className="text-muted-foreground relative hover:text-primary transition-colors size-8"
|
|
68
|
+
>
|
|
69
|
+
<BellIcon className="size-4" />
|
|
70
|
+
<span className="absolute top-2 right-2 size-2 bg-primary rounded-full border-2 border-background"></span>
|
|
71
|
+
</Button>
|
|
72
|
+
<Separator orientation="vertical" className="h-4 mx-1" />
|
|
73
|
+
<Button
|
|
74
|
+
variant="ghost"
|
|
75
|
+
size="sm"
|
|
76
|
+
className="gap-2 text-[10px] font-semibold uppercase tracking-wider text-primary hover:bg-primary/5 h-8 px-4"
|
|
77
|
+
>
|
|
78
|
+
Live Preview
|
|
79
|
+
<ExternalLinkIcon className="size-3.5" />
|
|
80
|
+
</Button>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</header>
|
|
85
|
+
|
|
86
|
+
{/* Content Area */}
|
|
87
|
+
<main className="flex-1 overflow-y-auto w-full bg-muted/20">
|
|
88
|
+
{children}
|
|
89
|
+
</main>
|
|
90
|
+
</SidebarInset>
|
|
91
|
+
</SidebarProvider>
|
|
92
|
+
);
|
|
93
|
+
}
|