mycontext-cli 4.2.7 → 4.2.8
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/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +19 -60
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/init-interactive.d.ts +20 -0
- package/dist/commands/init-interactive.d.ts.map +1 -1
- package/dist/commands/init-interactive.js +168 -5
- package/dist/commands/init-interactive.js.map +1 -1
- package/dist/config/shadcn-catalog.json +93 -0
- package/dist/core/brain/BrainClient.d.ts +1 -1
- package/dist/core/brain/BrainClient.d.ts.map +1 -1
- package/dist/core/brain/BrainClient.js +5 -5
- package/dist/core/brain/BrainClient.js.map +1 -1
- package/dist/doctor/DoctorEngine.d.ts.map +1 -1
- package/dist/doctor/DoctorEngine.js +21 -11
- package/dist/doctor/DoctorEngine.js.map +1 -1
- package/dist/doctor/rules/dead-code-rules.d.ts.map +1 -1
- package/dist/doctor/rules/dead-code-rules.js +33 -0
- package/dist/doctor/rules/dead-code-rules.js.map +1 -1
- package/dist/doctor/rules/instantdb-rules.d.ts.map +1 -1
- package/dist/doctor/rules/instantdb-rules.js +278 -69
- package/dist/doctor/rules/instantdb-rules.js.map +1 -1
- package/dist/doctor/rules/nextjs-rules.d.ts.map +1 -1
- package/dist/doctor/rules/nextjs-rules.js +53 -3
- package/dist/doctor/rules/nextjs-rules.js.map +1 -1
- package/dist/package.json +4 -2
- package/dist/services/ComponentInferenceEngine.d.ts +66 -0
- package/dist/services/ComponentInferenceEngine.d.ts.map +1 -0
- package/dist/services/ComponentInferenceEngine.js +302 -0
- package/dist/services/ComponentInferenceEngine.js.map +1 -0
- package/dist/services/ComponentRegistry.d.ts +61 -0
- package/dist/services/ComponentRegistry.d.ts.map +1 -0
- package/dist/services/ComponentRegistry.js +128 -0
- package/dist/services/ComponentRegistry.js.map +1 -0
- package/dist/services/InferenceEngine.js +1 -1
- package/dist/services/ProjectScanner.d.ts.map +1 -1
- package/dist/services/ProjectScanner.js +15 -1
- package/dist/services/ProjectScanner.js.map +1 -1
- package/dist/services/ScaffoldEngine.d.ts +87 -0
- package/dist/services/ScaffoldEngine.d.ts.map +1 -0
- package/dist/services/ScaffoldEngine.js +409 -0
- package/dist/services/ScaffoldEngine.js.map +1 -0
- package/dist/services/ScaffoldPreview.d.ts +62 -0
- package/dist/services/ScaffoldPreview.d.ts.map +1 -0
- package/dist/services/ScaffoldPreview.js +292 -0
- package/dist/services/ScaffoldPreview.js.map +1 -0
- package/dist/services/TemplateEngine.d.ts +136 -0
- package/dist/services/TemplateEngine.d.ts.map +1 -0
- package/dist/services/TemplateEngine.js +483 -0
- package/dist/services/TemplateEngine.js.map +1 -0
- package/dist/services/TemplateHelpers.d.ts +9 -0
- package/dist/services/TemplateHelpers.d.ts.map +1 -0
- package/dist/services/TemplateHelpers.js +212 -0
- package/dist/services/TemplateHelpers.js.map +1 -0
- package/dist/templates/actions/auth-actions.ts.hbs +140 -0
- package/dist/templates/actions/crud-actions.ts.hbs +113 -0
- package/dist/templates/components/auth/login-form.tsx.hbs +67 -0
- package/dist/templates/components/auth/login-skeleton.tsx.hbs +24 -0
- package/dist/templates/components/auth/register-form.tsx.hbs +116 -0
- package/dist/templates/components/crud/entity-card.tsx.hbs +71 -0
- package/dist/templates/components/crud/entity-form.tsx.hbs +158 -0
- package/dist/templates/components/crud/entity-skeleton.tsx.hbs +90 -0
- package/dist/templates/components/crud/entity-table.tsx.hbs +129 -0
- package/dist/templates/components/ui/button.tsx.hbs +53 -0
- package/dist/templates/components/ui/card.tsx.hbs +68 -0
- package/dist/templates/components/ui/input.tsx.hbs +33 -0
- package/dist/templates/components/ui/label.tsx.hbs +20 -0
- package/dist/templates/components/ui/skeleton.tsx.hbs +15 -0
- package/dist/templates/components/ui/theme-provider.tsx.hbs +66 -0
- package/dist/templates/components/ui/theme-toggle.tsx.hbs +30 -0
- package/dist/templates/config/app.css.hbs +150 -0
- package/dist/templates/layouts/dashboard-layout.tsx.hbs +69 -0
- package/dist/templates/layouts/error.tsx.hbs +51 -0
- package/dist/templates/layouts/loading.tsx.hbs +22 -0
- package/dist/templates/layouts/not-found.tsx.hbs +24 -0
- package/dist/templates/layouts/root-layout.tsx.hbs +40 -0
- package/dist/templates/lib/instant.ts.hbs +19 -0
- package/dist/templates/lib/utils.ts.hbs +24 -0
- package/dist/templates/pages/auth/login-page.tsx.hbs +30 -0
- package/dist/templates/pages/auth/register-page.tsx.hbs +38 -0
- package/dist/templates/pages/crud/create-page.tsx.hbs +42 -0
- package/dist/templates/pages/crud/detail-page.tsx.hbs +90 -0
- package/dist/templates/pages/crud/list-page.tsx.hbs +60 -0
- package/dist/templates/pages/crud/loading.tsx.hbs +13 -0
- package/dist/templates/pages/landing-page.tsx.hbs +111 -0
- package/dist/types/asl.d.ts +1 -1
- package/dist/types/asl.d.ts.map +1 -1
- package/dist/types/living-context.d.ts +1 -1
- package/dist/types/living-context.d.ts.map +1 -1
- package/dist/utils/FileGenerator.js +3 -3
- package/dist/utils/FileGenerator.js.map +1 -1
- package/dist/utils/generateTypesFromSchema.d.ts +47 -0
- package/dist/utils/generateTypesFromSchema.d.ts.map +1 -0
- package/dist/utils/generateTypesFromSchema.js +298 -0
- package/dist/utils/generateTypesFromSchema.js.map +1 -0
- package/package.json +4 -2
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
type Theme = 'dark' | 'light' | 'system'
|
|
6
|
+
|
|
7
|
+
interface ThemeContextType {
|
|
8
|
+
theme: Theme
|
|
9
|
+
setTheme: (theme: Theme) => void
|
|
10
|
+
resolvedTheme: 'dark' | 'light'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
|
14
|
+
|
|
15
|
+
export function ThemeProvider({
|
|
16
|
+
children,
|
|
17
|
+
defaultTheme = 'system',
|
|
18
|
+
storageKey = 'theme',
|
|
19
|
+
}: {
|
|
20
|
+
children: React.ReactNode
|
|
21
|
+
defaultTheme?: Theme
|
|
22
|
+
storageKey?: string
|
|
23
|
+
}) {
|
|
24
|
+
const [theme, setTheme] = useState<Theme>(defaultTheme)
|
|
25
|
+
const [resolvedTheme, setResolvedTheme] = useState<'dark' | 'light'>('light')
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const stored = localStorage.getItem(storageKey) as Theme | null
|
|
29
|
+
if (stored) setTheme(stored)
|
|
30
|
+
}, [storageKey])
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
const root = document.documentElement
|
|
34
|
+
root.classList.remove('light', 'dark')
|
|
35
|
+
|
|
36
|
+
const resolved = theme === 'system'
|
|
37
|
+
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
|
38
|
+
: theme
|
|
39
|
+
|
|
40
|
+
root.classList.add(resolved)
|
|
41
|
+
setResolvedTheme(resolved)
|
|
42
|
+
|
|
43
|
+
// Update meta theme-color for mobile browsers
|
|
44
|
+
const metaThemeColor = document.querySelector('meta[name="theme-color"]')
|
|
45
|
+
if (metaThemeColor) {
|
|
46
|
+
metaThemeColor.setAttribute('content', resolved === 'dark' ? '#09090b' : '#ffffff')
|
|
47
|
+
}
|
|
48
|
+
}, [theme])
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ThemeContext.Provider value={ { theme, setTheme: (newTheme)=> {
|
|
52
|
+
localStorage.setItem(storageKey, newTheme)
|
|
53
|
+
setTheme(newTheme)
|
|
54
|
+
},
|
|
55
|
+
resolvedTheme,
|
|
56
|
+
} }>
|
|
57
|
+
{children}
|
|
58
|
+
</ThemeContext.Provider>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const useTheme = () => {
|
|
63
|
+
const context = useContext(ThemeContext)
|
|
64
|
+
if (!context) throw new Error('useTheme must be used within ThemeProvider')
|
|
65
|
+
return context
|
|
66
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Moon, Sun } from 'lucide-react'
|
|
4
|
+
import { useTheme } from './theme-provider'
|
|
5
|
+
import { Button } from './button'
|
|
6
|
+
|
|
7
|
+
export function ThemeToggle() {
|
|
8
|
+
const { theme, setTheme, resolvedTheme } = useTheme()
|
|
9
|
+
|
|
10
|
+
const toggleTheme = () => {
|
|
11
|
+
if (theme === 'system') {
|
|
12
|
+
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark')
|
|
13
|
+
} else {
|
|
14
|
+
setTheme(theme === 'dark' ? 'light' : 'dark')
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Button
|
|
20
|
+
variant="ghost"
|
|
21
|
+
size="icon"
|
|
22
|
+
onClick={toggleTheme}
|
|
23
|
+
aria-label="Toggle theme"
|
|
24
|
+
>
|
|
25
|
+
<Sun className="size-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
|
26
|
+
<Moon className="absolute size-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
|
27
|
+
<span className="sr-only">Toggle theme</span>
|
|
28
|
+
</Button>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Tailwind v4 CSS-first configuration with design tokens */
|
|
4
|
+
@theme {
|
|
5
|
+
/* Semantic color tokens using OKLCH for better color perception */
|
|
6
|
+
--color-background: oklch(100% 0 0);
|
|
7
|
+
--color-foreground: oklch(14.5% 0.025 264);
|
|
8
|
+
|
|
9
|
+
--color-primary: oklch(14.5% 0.025 264);
|
|
10
|
+
--color-primary-foreground: oklch(98% 0.01 264);
|
|
11
|
+
|
|
12
|
+
--color-secondary: oklch(96% 0.01 264);
|
|
13
|
+
--color-secondary-foreground: oklch(14.5% 0.025 264);
|
|
14
|
+
|
|
15
|
+
--color-muted: oklch(96% 0.01 264);
|
|
16
|
+
--color-muted-foreground: oklch(46% 0.02 264);
|
|
17
|
+
|
|
18
|
+
--color-accent: oklch(96% 0.01 264);
|
|
19
|
+
--color-accent-foreground: oklch(14.5% 0.025 264);
|
|
20
|
+
|
|
21
|
+
--color-destructive: oklch(53% 0.22 27);
|
|
22
|
+
--color-destructive-foreground: oklch(98% 0.01 264);
|
|
23
|
+
|
|
24
|
+
--color-border: oklch(91% 0.01 264);
|
|
25
|
+
--color-ring: oklch(14.5% 0.025 264);
|
|
26
|
+
|
|
27
|
+
--color-card: oklch(100% 0 0);
|
|
28
|
+
--color-card-foreground: oklch(14.5% 0.025 264);
|
|
29
|
+
|
|
30
|
+
--color-ring-offset: oklch(100% 0 0);
|
|
31
|
+
|
|
32
|
+
/* Radius tokens */
|
|
33
|
+
--radius-sm: 0.25rem;
|
|
34
|
+
--radius-md: 0.375rem;
|
|
35
|
+
--radius-lg: 0.5rem;
|
|
36
|
+
--radius-xl: 0.75rem;
|
|
37
|
+
|
|
38
|
+
/* Animation tokens */
|
|
39
|
+
--animate-fade-in: fade-in 0.2s ease-out;
|
|
40
|
+
--animate-fade-out: fade-out 0.2s ease-in;
|
|
41
|
+
--animate-slide-in: slide-in 0.3s ease-out;
|
|
42
|
+
--animate-slide-out: slide-out 0.3s ease-in;
|
|
43
|
+
--animate-dialog-in: dialog-fade-in 0.2s ease-out;
|
|
44
|
+
--animate-dialog-out: dialog-fade-out 0.15s ease-in;
|
|
45
|
+
|
|
46
|
+
@keyframes fade-in {
|
|
47
|
+
from {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
}
|
|
50
|
+
to {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@keyframes fade-out {
|
|
56
|
+
from {
|
|
57
|
+
opacity: 1;
|
|
58
|
+
}
|
|
59
|
+
to {
|
|
60
|
+
opacity: 0;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@keyframes slide-in {
|
|
65
|
+
from {
|
|
66
|
+
transform: translateY(-0.5rem);
|
|
67
|
+
opacity: 0;
|
|
68
|
+
}
|
|
69
|
+
to {
|
|
70
|
+
transform: translateY(0);
|
|
71
|
+
opacity: 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@keyframes slide-out {
|
|
76
|
+
from {
|
|
77
|
+
transform: translateY(0);
|
|
78
|
+
opacity: 1;
|
|
79
|
+
}
|
|
80
|
+
to {
|
|
81
|
+
transform: translateY(-0.5rem);
|
|
82
|
+
opacity: 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@keyframes dialog-fade-in {
|
|
87
|
+
from {
|
|
88
|
+
opacity: 0;
|
|
89
|
+
transform: scale(0.95) translateY(-0.5rem);
|
|
90
|
+
}
|
|
91
|
+
to {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
transform: scale(1) translateY(0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@keyframes dialog-fade-out {
|
|
98
|
+
from {
|
|
99
|
+
opacity: 1;
|
|
100
|
+
transform: scale(1) translateY(0);
|
|
101
|
+
}
|
|
102
|
+
to {
|
|
103
|
+
opacity: 0;
|
|
104
|
+
transform: scale(0.95) translateY(-0.5rem);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Dark mode variant */
|
|
110
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
111
|
+
|
|
112
|
+
/* Dark mode theme overrides */
|
|
113
|
+
.dark {
|
|
114
|
+
--color-background: oklch(14.5% 0.025 264);
|
|
115
|
+
--color-foreground: oklch(98% 0.01 264);
|
|
116
|
+
|
|
117
|
+
--color-primary: oklch(98% 0.01 264);
|
|
118
|
+
--color-primary-foreground: oklch(14.5% 0.025 264);
|
|
119
|
+
|
|
120
|
+
--color-secondary: oklch(22% 0.02 264);
|
|
121
|
+
--color-secondary-foreground: oklch(98% 0.01 264);
|
|
122
|
+
|
|
123
|
+
--color-muted: oklch(22% 0.02 264);
|
|
124
|
+
--color-muted-foreground: oklch(65% 0.02 264);
|
|
125
|
+
|
|
126
|
+
--color-accent: oklch(22% 0.02 264);
|
|
127
|
+
--color-accent-foreground: oklch(98% 0.01 264);
|
|
128
|
+
|
|
129
|
+
--color-destructive: oklch(42% 0.15 27);
|
|
130
|
+
--color-destructive-foreground: oklch(98% 0.01 264);
|
|
131
|
+
|
|
132
|
+
--color-border: oklch(22% 0.02 264);
|
|
133
|
+
--color-ring: oklch(83% 0.02 264);
|
|
134
|
+
|
|
135
|
+
--color-card: oklch(14.5% 0.025 264);
|
|
136
|
+
--color-card-foreground: oklch(98% 0.01 264);
|
|
137
|
+
|
|
138
|
+
--color-ring-offset: oklch(14.5% 0.025 264);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Base styles */
|
|
142
|
+
@layer base {
|
|
143
|
+
* {
|
|
144
|
+
@apply border-border;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
body {
|
|
148
|
+
@apply bg-background text-foreground antialiased;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { redirect } from 'next/navigation'
|
|
2
|
+
import Link from 'next/link'
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
4
|
+
import { ThemeToggle } from '@/components/ui/theme-toggle'
|
|
5
|
+
import { getCurrentUser, logout } from '@/app/actions/auth'
|
|
6
|
+
|
|
7
|
+
export default async function DashboardLayout({
|
|
8
|
+
children,
|
|
9
|
+
}: {
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
}) {
|
|
12
|
+
const { user } = await getCurrentUser()
|
|
13
|
+
|
|
14
|
+
if (!user) {
|
|
15
|
+
redirect('/login')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex min-h-screen flex-col">
|
|
20
|
+
{/* Header */}
|
|
21
|
+
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
22
|
+
<div className="container flex h-14 items-center">
|
|
23
|
+
<div className="mr-4 flex">
|
|
24
|
+
<Link href="/dashboard" className="mr-6 flex items-center space-x-2">
|
|
25
|
+
<span className="font-bold">{{projectName}}</span>
|
|
26
|
+
</Link>
|
|
27
|
+
<nav className="flex items-center space-x-6 text-sm font-medium">
|
|
28
|
+
<Link
|
|
29
|
+
href="/dashboard"
|
|
30
|
+
className="transition-colors hover:text-foreground/80"
|
|
31
|
+
>
|
|
32
|
+
Dashboard
|
|
33
|
+
</Link>
|
|
34
|
+
{{#each entities}}
|
|
35
|
+
<Link
|
|
36
|
+
href="/{{lowercase name}}s"
|
|
37
|
+
className="transition-colors hover:text-foreground/80"
|
|
38
|
+
>
|
|
39
|
+
{{capitalize name}}s
|
|
40
|
+
</Link>
|
|
41
|
+
{{/each}}
|
|
42
|
+
</nav>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="ml-auto flex items-center space-x-4">
|
|
45
|
+
<ThemeToggle />
|
|
46
|
+
<div className="flex items-center gap-2">
|
|
47
|
+
<span className="text-sm text-muted-foreground">{user.name || user.email}</span>
|
|
48
|
+
<form action={logout}>
|
|
49
|
+
<Button type="submit" variant="ghost" size="sm">
|
|
50
|
+
Log out
|
|
51
|
+
</Button>
|
|
52
|
+
</form>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</header>
|
|
57
|
+
|
|
58
|
+
{/* Main Content */}
|
|
59
|
+
<main className="flex-1">{children}</main>
|
|
60
|
+
|
|
61
|
+
{/* Footer */}
|
|
62
|
+
<footer className="border-t py-6">
|
|
63
|
+
<div className="container text-center text-sm text-muted-foreground">
|
|
64
|
+
<p>© {new Date().getFullYear()} {{projectName}}. All rights reserved.</p>
|
|
65
|
+
</div>
|
|
66
|
+
</footer>
|
|
67
|
+
</div>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
6
|
+
|
|
7
|
+
export default function Error({
|
|
8
|
+
error,
|
|
9
|
+
reset,
|
|
10
|
+
}: {
|
|
11
|
+
error: Error & { digest?: string }
|
|
12
|
+
reset: () => void
|
|
13
|
+
}) {
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
// Log the error to an error reporting service
|
|
16
|
+
console.error(error)
|
|
17
|
+
}, [error])
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="container mx-auto flex min-h-screen items-center justify-center px-4">
|
|
21
|
+
<Card className="w-full max-w-md">
|
|
22
|
+
<CardHeader>
|
|
23
|
+
<CardTitle>Something went wrong</CardTitle>
|
|
24
|
+
<CardDescription>
|
|
25
|
+
An error occurred while loading this page
|
|
26
|
+
</CardDescription>
|
|
27
|
+
</CardHeader>
|
|
28
|
+
<CardContent className="space-y-4">
|
|
29
|
+
{error.message && (
|
|
30
|
+
<div className="rounded-md bg-destructive/10 p-3">
|
|
31
|
+
<p className="text-sm text-destructive">{error.message}</p>
|
|
32
|
+
</div>
|
|
33
|
+
)}
|
|
34
|
+
{error.digest && (
|
|
35
|
+
<p className="text-xs text-muted-foreground">
|
|
36
|
+
Error ID: {error.digest}
|
|
37
|
+
</p>
|
|
38
|
+
)}
|
|
39
|
+
<div className="flex gap-2">
|
|
40
|
+
<Button onClick={reset} className="flex-1">
|
|
41
|
+
Try again
|
|
42
|
+
</Button>
|
|
43
|
+
<Button variant="outline" asChild className="flex-1">
|
|
44
|
+
<a href="/">Go home</a>
|
|
45
|
+
</Button>
|
|
46
|
+
</div>
|
|
47
|
+
</CardContent>
|
|
48
|
+
</Card>
|
|
49
|
+
</div>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Skeleton } from '@/components/ui/skeleton'
|
|
2
|
+
|
|
3
|
+
export default function Loading() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="container mx-auto py-10">
|
|
6
|
+
<div className="space-y-8">
|
|
7
|
+
{/* Header skeleton */}
|
|
8
|
+
<div className="space-y-2">
|
|
9
|
+
<Skeleton className="h-10 w-64" />
|
|
10
|
+
<Skeleton className="h-4 w-96" />
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
{/* Content skeleton */}
|
|
14
|
+
<div className="space-y-4">
|
|
15
|
+
<Skeleton className="h-32 w-full" />
|
|
16
|
+
<Skeleton className="h-32 w-full" />
|
|
17
|
+
<Skeleton className="h-32 w-full" />
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import Link from 'next/link'
|
|
2
|
+
import { Button } from '@/components/ui/button'
|
|
3
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
4
|
+
|
|
5
|
+
export default function NotFound() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="container mx-auto flex min-h-screen items-center justify-center px-4">
|
|
8
|
+
<Card className="w-full max-w-md text-center">
|
|
9
|
+
<CardHeader>
|
|
10
|
+
<div className="mb-4 text-6xl font-bold text-muted-foreground">404</div>
|
|
11
|
+
<CardTitle>Page not found</CardTitle>
|
|
12
|
+
<CardDescription>
|
|
13
|
+
Sorry, we couldn't find the page you're looking for
|
|
14
|
+
</CardDescription>
|
|
15
|
+
</CardHeader>
|
|
16
|
+
<CardContent>
|
|
17
|
+
<Button asChild>
|
|
18
|
+
<Link href="/">Go back home</Link>
|
|
19
|
+
</Button>
|
|
20
|
+
</CardContent>
|
|
21
|
+
</Card>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import { Inter } from 'next/font/google'
|
|
3
|
+
import { ThemeProvider } from '@/components/ui/theme-provider'
|
|
4
|
+
import '@/app.css'
|
|
5
|
+
|
|
6
|
+
const inter = Inter({
|
|
7
|
+
subsets: ['latin'],
|
|
8
|
+
display: 'swap',
|
|
9
|
+
variable: '--font-inter',
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const metadata: Metadata = {
|
|
13
|
+
title: {
|
|
14
|
+
default: '{{projectName}}',
|
|
15
|
+
template: '%s | {{projectName}}',
|
|
16
|
+
},
|
|
17
|
+
description: '{{projectDescription}}',
|
|
18
|
+
metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'),
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default function RootLayout({
|
|
22
|
+
children,
|
|
23
|
+
}: {
|
|
24
|
+
children: React.ReactNode
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<html lang="en" suppressHydrationWarning>
|
|
28
|
+
<body className={inter.variable}>
|
|
29
|
+
<ThemeProvider
|
|
30
|
+
attribute="class"
|
|
31
|
+
defaultTheme="system"
|
|
32
|
+
enableSystem
|
|
33
|
+
disableTransitionOnChange
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</ThemeProvider>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { init } from "@instantdb/react"
|
|
2
|
+
import schema from "@/instant.schema"
|
|
3
|
+
|
|
4
|
+
const APP_ID = process.env.NEXT_PUBLIC_INSTANT_APP_ID!
|
|
5
|
+
|
|
6
|
+
if (!APP_ID) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"Missing NEXT_PUBLIC_INSTANT_APP_ID environment variable. " +
|
|
9
|
+
"Get your app ID from https://instantdb.com/dash"
|
|
10
|
+
)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* InstantDB client instance
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const { data, isLoading, error } = db.useQuery({ {{entityName}}s: {} })
|
|
18
|
+
*/
|
|
19
|
+
export const db = init({ appId: APP_ID, schema })
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ClassValue, clsx } from "clsx"
|
|
2
|
+
import { twMerge } from "tailwind-merge"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merge Tailwind CSS classes with proper precedence
|
|
6
|
+
* @param inputs - Class values to merge
|
|
7
|
+
* @returns Merged class string
|
|
8
|
+
*/
|
|
9
|
+
export function cn(...inputs: ClassValue[]) {
|
|
10
|
+
return twMerge(clsx(inputs))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Focus ring utility for consistent focus styles
|
|
15
|
+
*/
|
|
16
|
+
export const focusRing = cn(
|
|
17
|
+
"focus-visible:outline-none focus-visible:ring-2",
|
|
18
|
+
"focus-visible:ring-ring focus-visible:ring-offset-2"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Disabled utility for consistent disabled states
|
|
23
|
+
*/
|
|
24
|
+
export const disabled = "disabled:pointer-events-none disabled:opacity-50"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Metadata } from 'next'
|
|
2
|
+
import { Suspense } from 'react'
|
|
3
|
+
import { LoginForm } from '@/components/auth/LoginForm'
|
|
4
|
+
import { LoginSkeleton } from '@/components/auth/LoginSkeleton'
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: 'Login',
|
|
8
|
+
description: 'Sign in to your account',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function LoginPage() {
|
|
12
|
+
return (
|
|
13
|
+
<div className="container mx-auto flex min-h-screen items-center justify-center px-4">
|
|
14
|
+
<div className="w-full max-w-md space-y-8">
|
|
15
|
+
<div className="text-center">
|
|
16
|
+
<h1 className="text-3xl font-bold tracking-tight">
|
|
17
|
+
Welcome back
|
|
18
|
+
</h1>
|
|
19
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
20
|
+
Sign in to your account to continue
|
|
21
|
+
</p>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<Suspense fallback={<LoginSkeleton />}>
|
|
25
|
+
<LoginForm />
|
|
26
|
+
</Suspense>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Metadata } from 'next'
|
|
2
|
+
import { Suspense } from 'react'
|
|
3
|
+
import Link from 'next/link'
|
|
4
|
+
import { RegisterForm } from '@/components/auth/RegisterForm'
|
|
5
|
+
import { LoginSkeleton } from '@/components/auth/LoginSkeleton'
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: 'Register',
|
|
9
|
+
description: 'Create a new account',
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default function RegisterPage() {
|
|
13
|
+
return (
|
|
14
|
+
<div className="container mx-auto flex min-h-screen items-center justify-center px-4">
|
|
15
|
+
<div className="w-full max-w-md space-y-8">
|
|
16
|
+
<div className="text-center">
|
|
17
|
+
<h1 className="text-3xl font-bold tracking-tight">
|
|
18
|
+
Create an account
|
|
19
|
+
</h1>
|
|
20
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
21
|
+
Sign up to get started
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<Suspense fallback={<LoginSkeleton />}>
|
|
26
|
+
<RegisterForm />
|
|
27
|
+
</Suspense>
|
|
28
|
+
|
|
29
|
+
<p className="text-center text-sm text-muted-foreground">
|
|
30
|
+
Already have an account?{' '}
|
|
31
|
+
<Link href="/login" className="font-medium text-primary hover:underline">
|
|
32
|
+
Sign in
|
|
33
|
+
</Link>
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Metadata } from 'next'
|
|
2
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
3
|
+
import { EntityForm } from '@/components/crud/entity-form'
|
|
4
|
+
import { create{{entityName}} } from '@/app/actions/crud'
|
|
5
|
+
|
|
6
|
+
export const metadata: Metadata = {
|
|
7
|
+
title: 'Create {{entityName}}',
|
|
8
|
+
description: 'Create a new {{entityLower}}',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default function Create{{entityName}}Page() {
|
|
12
|
+
const fields = [
|
|
13
|
+
{{#each fields}}
|
|
14
|
+
{
|
|
15
|
+
name: '{{name}}',
|
|
16
|
+
type: '{{type}}',
|
|
17
|
+
label: '{{capitalize name}}',
|
|
18
|
+
required: {{#if required}}true{{else}}false{{/if}},
|
|
19
|
+
},
|
|
20
|
+
{{/each}}
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="container mx-auto py-10">
|
|
25
|
+
<div className="mb-8">
|
|
26
|
+
<h1 className="text-3xl font-bold tracking-tight">Create {{entityName}}</h1>
|
|
27
|
+
<p className="text-muted-foreground">Add a new {{entityLower}} to your collection</p>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<Card className="mx-auto max-w-2xl">
|
|
31
|
+
<CardHeader>
|
|
32
|
+
<CardTitle>{{entityName}} Information</CardTitle>
|
|
33
|
+
<CardDescription>Enter the details for your new {{entityLower}}</CardDescription>
|
|
34
|
+
</CardHeader>
|
|
35
|
+
<CardContent>
|
|
36
|
+
<EntityForm entityName="{{entityName}}" entityLower="{{entityLower}}" fields={fields} action={create{{entityName}}
|
|
37
|
+
} submitLabel="Create {{entityName}}" />
|
|
38
|
+
</CardContent>
|
|
39
|
+
</Card>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|