@lumerahq/cli 0.7.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 +118 -0
- package/dist/auth-7RGL7GXU.js +311 -0
- package/dist/chunk-2CR762KB.js +18 -0
- package/dist/chunk-AVKPM7C4.js +199 -0
- package/dist/chunk-D2BLSEGR.js +59 -0
- package/dist/chunk-NDLYGKS6.js +77 -0
- package/dist/chunk-V2XXMMEI.js +147 -0
- package/dist/dev-UTZC4ZJ7.js +87 -0
- package/dist/index.js +157 -0
- package/dist/init-OQCIET53.js +363 -0
- package/dist/migrate-2DZ6RQ5K.js +190 -0
- package/dist/resources-PNK3NESI.js +1350 -0
- package/dist/run-4NDI2CN4.js +257 -0
- package/dist/skills-56EUKHGY.js +414 -0
- package/dist/status-BEVUV6RY.js +131 -0
- package/package.json +37 -0
- package/templates/default/CLAUDE.md +245 -0
- package/templates/default/README.md +59 -0
- package/templates/default/biome.json +33 -0
- package/templates/default/index.html +13 -0
- package/templates/default/package.json.hbs +46 -0
- package/templates/default/platform/automations/.gitkeep +0 -0
- package/templates/default/platform/collections/example_items.json +28 -0
- package/templates/default/platform/hooks/.gitkeep +0 -0
- package/templates/default/pyproject.toml.hbs +14 -0
- package/templates/default/scripts/seed-demo.py +35 -0
- package/templates/default/src/components/Sidebar.tsx +84 -0
- package/templates/default/src/components/StatCard.tsx +31 -0
- package/templates/default/src/components/layout.tsx +13 -0
- package/templates/default/src/lib/queries.ts +27 -0
- package/templates/default/src/main.tsx +137 -0
- package/templates/default/src/routes/__root.tsx +10 -0
- package/templates/default/src/routes/index.tsx +90 -0
- package/templates/default/src/routes/settings.tsx +25 -0
- package/templates/default/src/styles.css +40 -0
- package/templates/default/tsconfig.json +23 -0
- package/templates/default/vite.config.ts +27 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Link, useRouterState } from '@tanstack/react-router';
|
|
2
|
+
import { LayoutDashboard, Settings, ChevronLeft, ChevronRight } from 'lucide-react';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { cn } from '@lumerahq/ui/lib';
|
|
5
|
+
|
|
6
|
+
type NavItem = {
|
|
7
|
+
to: string;
|
|
8
|
+
label: string;
|
|
9
|
+
icon: React.ElementType;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const navItems: NavItem[] = [
|
|
13
|
+
{ to: '/', label: 'Dashboard', icon: LayoutDashboard },
|
|
14
|
+
{ to: '/settings', label: 'Settings', icon: Settings },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function Sidebar() {
|
|
18
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
19
|
+
const routerState = useRouterState();
|
|
20
|
+
const currentPath = routerState.location.pathname;
|
|
21
|
+
|
|
22
|
+
const isActive = (path: string) => {
|
|
23
|
+
if (path === '/') return currentPath === '/';
|
|
24
|
+
return currentPath.startsWith(path);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<aside
|
|
29
|
+
className={cn(
|
|
30
|
+
'flex flex-col bg-slate-900 text-white border-r border-slate-800 transition-all duration-200',
|
|
31
|
+
collapsed ? 'w-16' : 'w-56'
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
{/* Logo */}
|
|
35
|
+
<div className="flex items-center h-14 px-4 border-b border-slate-800">
|
|
36
|
+
<Link to="/" className="flex items-center gap-2">
|
|
37
|
+
<div className="size-8 rounded-lg bg-primary flex items-center justify-center text-primary-foreground font-bold text-sm">
|
|
38
|
+
{{projectInitial}}
|
|
39
|
+
</div>
|
|
40
|
+
{!collapsed && (
|
|
41
|
+
<span className="font-semibold text-sm">{{projectTitle}}</span>
|
|
42
|
+
)}
|
|
43
|
+
</Link>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{/* Navigation */}
|
|
47
|
+
<nav className="flex-1 p-2 space-y-1">
|
|
48
|
+
{navItems.map((item) => {
|
|
49
|
+
const active = isActive(item.to);
|
|
50
|
+
return (
|
|
51
|
+
<Link
|
|
52
|
+
key={item.to}
|
|
53
|
+
to={item.to}
|
|
54
|
+
className={cn(
|
|
55
|
+
'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors',
|
|
56
|
+
active
|
|
57
|
+
? 'bg-slate-800 text-white'
|
|
58
|
+
: 'text-slate-400 hover:bg-slate-800/50 hover:text-white'
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
<item.icon className="size-4 shrink-0" />
|
|
62
|
+
{!collapsed && <span>{item.label}</span>}
|
|
63
|
+
</Link>
|
|
64
|
+
);
|
|
65
|
+
})}
|
|
66
|
+
</nav>
|
|
67
|
+
|
|
68
|
+
{/* Collapse toggle */}
|
|
69
|
+
<div className="p-2 border-t border-slate-800">
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
onClick={() => setCollapsed(!collapsed)}
|
|
73
|
+
className={cn(
|
|
74
|
+
'flex items-center gap-2 w-full px-3 py-2 rounded-md text-sm text-slate-400 hover:text-white hover:bg-slate-800/50 transition-colors',
|
|
75
|
+
collapsed && 'justify-center'
|
|
76
|
+
)}
|
|
77
|
+
>
|
|
78
|
+
{collapsed ? <ChevronRight className="size-4" /> : <ChevronLeft className="size-4" />}
|
|
79
|
+
{!collapsed && <span>Collapse</span>}
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
</aside>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { cn } from '@lumerahq/ui/lib';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface StatCardProps {
|
|
5
|
+
title: string;
|
|
6
|
+
value: string | number;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
icon?: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function StatCard({ title, value, subtitle, icon, className }: StatCardProps) {
|
|
13
|
+
return (
|
|
14
|
+
<div className={cn('rounded-xl border bg-card p-5', className)}>
|
|
15
|
+
<div className="flex items-start justify-between">
|
|
16
|
+
<div className="space-y-1">
|
|
17
|
+
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
|
18
|
+
<p className="text-2xl font-semibold tracking-tight">{value}</p>
|
|
19
|
+
{subtitle && (
|
|
20
|
+
<p className="text-sm text-muted-foreground">{subtitle}</p>
|
|
21
|
+
)}
|
|
22
|
+
</div>
|
|
23
|
+
{icon && (
|
|
24
|
+
<div className="rounded-lg bg-muted p-2.5 text-muted-foreground">
|
|
25
|
+
{icon}
|
|
26
|
+
</div>
|
|
27
|
+
)}
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Sidebar } from './Sidebar';
|
|
3
|
+
|
|
4
|
+
export function AppLayout({ children }: { children: ReactNode }) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="flex h-screen bg-background">
|
|
7
|
+
<Sidebar />
|
|
8
|
+
<main className="flex-1 overflow-auto">
|
|
9
|
+
<div className="p-6">{children}</div>
|
|
10
|
+
</main>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { pbList, type PbRecord } from '@lumerahq/ui/lib';
|
|
2
|
+
|
|
3
|
+
export type ExampleRecord = PbRecord & {
|
|
4
|
+
name: string;
|
|
5
|
+
status: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export async function getExampleData() {
|
|
9
|
+
// Replace with your actual data fetching logic
|
|
10
|
+
return {
|
|
11
|
+
message: 'Replace this with your data fetching logic',
|
|
12
|
+
timestamp: new Date().toISOString(),
|
|
13
|
+
instructions: [
|
|
14
|
+
'1. Edit src/lib/queries.ts',
|
|
15
|
+
'2. Use pbSql or pbList from @lumerahq/ui/lib',
|
|
16
|
+
'3. Create query functions for your data',
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function listExampleRecords(page = 1) {
|
|
22
|
+
return pbList<ExampleRecord>('your_collection', {
|
|
23
|
+
page,
|
|
24
|
+
perPage: 20,
|
|
25
|
+
sort: '-created',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { StrictMode, createContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
import { RouterProvider, createRouter, createHashHistory } from '@tanstack/react-router';
|
|
5
|
+
import { Toaster } from 'sonner';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
isEmbedded,
|
|
9
|
+
onInitMessage,
|
|
10
|
+
postReadyMessage,
|
|
11
|
+
type HostPayload,
|
|
12
|
+
} from '@lumerahq/ui/lib';
|
|
13
|
+
|
|
14
|
+
import { routeTree } from './routeTree.gen';
|
|
15
|
+
import '@lumerahq/ui/styles.css';
|
|
16
|
+
import './styles.css';
|
|
17
|
+
|
|
18
|
+
const queryClient = new QueryClient({
|
|
19
|
+
defaultOptions: {
|
|
20
|
+
queries: { staleTime: 30_000, retry: 1 },
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Use hash history so route persists across iframe refreshes
|
|
25
|
+
const hashHistory = createHashHistory();
|
|
26
|
+
|
|
27
|
+
const router = createRouter({
|
|
28
|
+
routeTree,
|
|
29
|
+
history: hashHistory,
|
|
30
|
+
context: {},
|
|
31
|
+
defaultPreload: 'intent',
|
|
32
|
+
scrollRestoration: true,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
declare module '@tanstack/react-router' {
|
|
36
|
+
interface Register {
|
|
37
|
+
router: typeof router;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const ROUTE_STORAGE_KEY = '{{projectName}}-route';
|
|
42
|
+
|
|
43
|
+
function RouteRestorer() {
|
|
44
|
+
const isFirstLoad = useRef(true);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (isFirstLoad.current) {
|
|
48
|
+
isFirstLoad.current = false;
|
|
49
|
+
const savedRoute = localStorage.getItem(ROUTE_STORAGE_KEY);
|
|
50
|
+
if (savedRoute && savedRoute !== '/' && router.state.location.pathname === '/') {
|
|
51
|
+
router.navigate({ to: savedRoute });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return router.subscribe('onResolved', ({ toLocation }) => {
|
|
56
|
+
localStorage.setItem(ROUTE_STORAGE_KEY, toLocation.pathname);
|
|
57
|
+
});
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
type AuthContextValue = {
|
|
64
|
+
company?: { id: string; name?: string; apiName?: string };
|
|
65
|
+
user?: { id: string; name: string; email: string; role?: string };
|
|
66
|
+
sessionToken?: string;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const AuthContext = createContext<AuthContextValue | null>(null);
|
|
70
|
+
|
|
71
|
+
const App = () => {
|
|
72
|
+
const [hostContext, setHostContext] = useState<AuthContextValue | null>(null);
|
|
73
|
+
const [status, setStatus] = useState<'pending' | 'ready' | 'denied'>('pending');
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (typeof window === 'undefined') return;
|
|
77
|
+
|
|
78
|
+
const standaloneDevMode =
|
|
79
|
+
import.meta.env.DEV && !isEmbedded() && !!import.meta.env.VITE_DEV_API_BASE_URL;
|
|
80
|
+
|
|
81
|
+
if (!isEmbedded() && !standaloneDevMode) {
|
|
82
|
+
setStatus('denied');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const cleanup = onInitMessage((payload?: HostPayload) => {
|
|
87
|
+
setHostContext({
|
|
88
|
+
company: payload?.company,
|
|
89
|
+
user: payload?.user,
|
|
90
|
+
sessionToken: payload?.session?.token,
|
|
91
|
+
});
|
|
92
|
+
setStatus('ready');
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
postReadyMessage();
|
|
96
|
+
return cleanup;
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
if (status === 'pending') {
|
|
100
|
+
return (
|
|
101
|
+
<main className="flex min-h-screen items-center justify-center bg-slate-900 text-white">
|
|
102
|
+
<p>Authenticating...</p>
|
|
103
|
+
</main>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (status === 'denied' || !hostContext) {
|
|
108
|
+
return (
|
|
109
|
+
<main className="flex min-h-screen items-center justify-center bg-slate-900 text-white">
|
|
110
|
+
<div className="text-center space-y-4 rounded-xl border border-slate-700/60 bg-slate-800/70 px-8 py-10">
|
|
111
|
+
<p className="text-sm uppercase tracking-widest text-slate-400">Error</p>
|
|
112
|
+
<p className="text-6xl font-semibold">403</p>
|
|
113
|
+
<p className="text-sm text-slate-300">Access Denied</p>
|
|
114
|
+
</div>
|
|
115
|
+
</main>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<AuthContext.Provider value={hostContext}>
|
|
121
|
+
<QueryClientProvider client={queryClient}>
|
|
122
|
+
<RouterProvider router={router} />
|
|
123
|
+
<RouteRestorer />
|
|
124
|
+
<Toaster position="top-right" richColors />
|
|
125
|
+
</QueryClientProvider>
|
|
126
|
+
</AuthContext.Provider>
|
|
127
|
+
);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const rootElement = document.getElementById('app');
|
|
131
|
+
if (rootElement && !rootElement.innerHTML) {
|
|
132
|
+
ReactDOM.createRoot(rootElement).render(
|
|
133
|
+
<StrictMode>
|
|
134
|
+
<App />
|
|
135
|
+
</StrictMode>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { Users, FileText, Activity, TrendingUp } from 'lucide-react';
|
|
4
|
+
import { AuthContext } from '../main';
|
|
5
|
+
import { StatCard } from '../components/StatCard';
|
|
6
|
+
|
|
7
|
+
export const Route = createFileRoute('/')({
|
|
8
|
+
component: HomePage,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
function HomePage() {
|
|
12
|
+
const auth = useContext(AuthContext);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="space-y-6">
|
|
16
|
+
{/* Header */}
|
|
17
|
+
<div>
|
|
18
|
+
<h1 className="text-2xl font-semibold">Dashboard</h1>
|
|
19
|
+
<p className="text-muted-foreground mt-1">
|
|
20
|
+
Welcome back, {auth?.user?.name ?? 'User'}
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
{/* Stats Grid */}
|
|
25
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
26
|
+
<StatCard
|
|
27
|
+
title="Total Users"
|
|
28
|
+
value="1,234"
|
|
29
|
+
subtitle="+12% from last month"
|
|
30
|
+
icon={<Users className="size-5" />}
|
|
31
|
+
/>
|
|
32
|
+
<StatCard
|
|
33
|
+
title="Documents"
|
|
34
|
+
value="856"
|
|
35
|
+
subtitle="23 added today"
|
|
36
|
+
icon={<FileText className="size-5" />}
|
|
37
|
+
/>
|
|
38
|
+
<StatCard
|
|
39
|
+
title="Active Sessions"
|
|
40
|
+
value="42"
|
|
41
|
+
subtitle="Real-time"
|
|
42
|
+
icon={<Activity className="size-5" />}
|
|
43
|
+
/>
|
|
44
|
+
<StatCard
|
|
45
|
+
title="Growth Rate"
|
|
46
|
+
value="+24.5%"
|
|
47
|
+
subtitle="vs last quarter"
|
|
48
|
+
icon={<TrendingUp className="size-5" />}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{/* Content Area */}
|
|
53
|
+
<div className="grid gap-6 lg:grid-cols-2">
|
|
54
|
+
<div className="rounded-xl border bg-card p-6">
|
|
55
|
+
<h2 className="font-semibold mb-4">Recent Activity</h2>
|
|
56
|
+
<div className="space-y-3">
|
|
57
|
+
{[1, 2, 3].map((i) => (
|
|
58
|
+
<div key={i} className="flex items-center gap-3 text-sm">
|
|
59
|
+
<div className="size-2 rounded-full bg-primary" />
|
|
60
|
+
<span className="text-muted-foreground">Activity item {i}</span>
|
|
61
|
+
<span className="ml-auto text-xs text-muted-foreground">2h ago</span>
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
<p className="text-sm text-muted-foreground mt-4 pt-4 border-t">
|
|
66
|
+
Replace with your actual activity data
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="rounded-xl border bg-card p-6">
|
|
71
|
+
<h2 className="font-semibold mb-4">Quick Actions</h2>
|
|
72
|
+
<div className="grid grid-cols-2 gap-3">
|
|
73
|
+
{['Create Report', 'Add User', 'View Analytics', 'Settings'].map((action) => (
|
|
74
|
+
<button
|
|
75
|
+
key={action}
|
|
76
|
+
type="button"
|
|
77
|
+
className="p-3 text-sm font-medium rounded-lg border bg-background hover:bg-muted transition-colors text-left"
|
|
78
|
+
>
|
|
79
|
+
{action}
|
|
80
|
+
</button>
|
|
81
|
+
))}
|
|
82
|
+
</div>
|
|
83
|
+
<p className="text-sm text-muted-foreground mt-4 pt-4 border-t">
|
|
84
|
+
Customize these actions for your app
|
|
85
|
+
</p>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router';
|
|
2
|
+
|
|
3
|
+
export const Route = createFileRoute('/settings')({
|
|
4
|
+
component: SettingsPage,
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
function SettingsPage() {
|
|
8
|
+
return (
|
|
9
|
+
<div className="space-y-6">
|
|
10
|
+
<div>
|
|
11
|
+
<h1 className="text-2xl font-semibold">Settings</h1>
|
|
12
|
+
<p className="text-muted-foreground mt-1">
|
|
13
|
+
Manage your application settings
|
|
14
|
+
</p>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div className="rounded-xl border bg-card p-6">
|
|
18
|
+
<h2 className="font-semibold mb-4">General</h2>
|
|
19
|
+
<p className="text-sm text-muted-foreground">
|
|
20
|
+
Add your settings UI here
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
@custom-variant dark (&:is(.dark *));
|
|
4
|
+
|
|
5
|
+
body {
|
|
6
|
+
@apply m-0;
|
|
7
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
8
|
+
-webkit-font-smoothing: antialiased;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
--background: oklch(1 0 0);
|
|
13
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
14
|
+
--card: oklch(1 0 0);
|
|
15
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
16
|
+
--primary: oklch(0.32 0.05 200);
|
|
17
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
18
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
19
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
20
|
+
--border: oklch(0.92 0.004 286.32);
|
|
21
|
+
--radius: 0.625rem;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@theme inline {
|
|
25
|
+
--color-background: var(--background);
|
|
26
|
+
--color-foreground: var(--foreground);
|
|
27
|
+
--color-card: var(--card);
|
|
28
|
+
--color-card-foreground: var(--card-foreground);
|
|
29
|
+
--color-primary: var(--primary);
|
|
30
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
31
|
+
--color-muted: var(--muted);
|
|
32
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
33
|
+
--color-border: var(--border);
|
|
34
|
+
--radius-lg: var(--radius);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@layer base {
|
|
38
|
+
* { @apply border-border; }
|
|
39
|
+
body { @apply bg-background text-foreground; }
|
|
40
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"jsx": "react-jsx",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
8
|
+
"types": ["vite/client"],
|
|
9
|
+
"moduleResolution": "bundler",
|
|
10
|
+
"allowImportingTsExtensions": true,
|
|
11
|
+
"verbatimModuleSyntax": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"baseUrl": ".",
|
|
19
|
+
"paths": {
|
|
20
|
+
"@/*": ["./src/*"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import viteReact from '@vitejs/plugin-react';
|
|
3
|
+
import tailwindcss from '@tailwindcss/vite';
|
|
4
|
+
import { tanstackRouter } from '@tanstack/router-plugin/vite';
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
base: './', // Use relative paths for S3 hosting at subpaths
|
|
8
|
+
plugins: [
|
|
9
|
+
tanstackRouter({
|
|
10
|
+
target: 'react',
|
|
11
|
+
autoCodeSplitting: true,
|
|
12
|
+
}),
|
|
13
|
+
viteReact(),
|
|
14
|
+
tailwindcss(),
|
|
15
|
+
],
|
|
16
|
+
resolve: {
|
|
17
|
+
alias: {
|
|
18
|
+
'@': new URL('./src', import.meta.url).pathname,
|
|
19
|
+
},
|
|
20
|
+
dedupe: ['react', 'react-dom'],
|
|
21
|
+
},
|
|
22
|
+
server: {
|
|
23
|
+
allowedHosts: [
|
|
24
|
+
'mac.lumerahq.com',
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
});
|