create-kuckit-app 2.1.0 → 2.2.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/package.json +3 -1
- package/templates/base/apps/server/package.json +3 -3
- package/templates/base/apps/web/package.json +2 -2
- package/templates/base/apps/web/src/components/dashboard/nav-user.tsx +3 -3
- package/templates/base/apps/web/src/components/ui/sidebar.tsx +2 -1
- package/templates/base/apps/web/src/routes/__root.tsx +7 -1
- package/templates/base/apps/web/src/routes/index.tsx +17 -22
- package/templates/base/apps/web/src/routes/login.tsx +81 -50
- package/templates/base/apps/web/tsconfig.json +1 -0
- package/templates/base/drizzle.config.ts +17 -1
- package/templates/base/package.json +6 -6
- package/templates/base/packages/items-module/package.json +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-kuckit-app",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Create a new Kuckit application",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -40,12 +40,14 @@
|
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsdown",
|
|
43
|
+
"test:e2e": "playwright test --config e2e/playwright.config.ts",
|
|
43
44
|
"prepublishOnly": "npm run build && node ../../scripts/resolve-workspace-protocols.cjs && node ../../scripts/check-no-workspace-protocol.cjs"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
46
47
|
"commander": "^13.1.0"
|
|
47
48
|
},
|
|
48
49
|
"devDependencies": {
|
|
50
|
+
"@playwright/test": "^1.52.0",
|
|
49
51
|
"@types/node": "^22.13.13",
|
|
50
52
|
"tsdown": "catalog:"
|
|
51
53
|
}
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"dotenv": "^17.0.0",
|
|
12
|
-
"@kuckit/app-server": "^
|
|
13
|
-
"@kuckit/sdk": "^
|
|
14
|
-
"@kuckit/db": "^
|
|
12
|
+
"@kuckit/app-server": "^5.0.0",
|
|
13
|
+
"@kuckit/sdk": "^5.0.0",
|
|
14
|
+
"@kuckit/db": "^5.0.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"typescript": "^5.8.2",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"check-types": "tsc --noEmit"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@kuckit/app-web": "^
|
|
13
|
-
"@kuckit/sdk-react": "^
|
|
12
|
+
"@kuckit/app-web": "^5.0.0",
|
|
13
|
+
"@kuckit/sdk-react": "^5.0.0",
|
|
14
14
|
"@radix-ui/react-avatar": "^1.1.10",
|
|
15
15
|
"@radix-ui/react-collapsible": "^1.1.11",
|
|
16
16
|
"@radix-ui/react-dialog": "^1.1.14",
|
|
@@ -51,7 +51,7 @@ export function NavUser() {
|
|
|
51
51
|
<span className="truncate font-semibold">{user?.name || 'User'}</span>
|
|
52
52
|
<span className="truncate text-xs text-muted-foreground">{user?.email || ''}</span>
|
|
53
53
|
</div>
|
|
54
|
-
<ChevronsUpDown className="ml-auto size-4" />
|
|
54
|
+
<ChevronsUpDown className="ml-auto size-4" aria-hidden="true" />
|
|
55
55
|
</SidebarMenuButton>
|
|
56
56
|
</DropdownMenuTrigger>
|
|
57
57
|
<DropdownMenuContent
|
|
@@ -61,12 +61,12 @@ export function NavUser() {
|
|
|
61
61
|
sideOffset={4}
|
|
62
62
|
>
|
|
63
63
|
<DropdownMenuItem onClick={() => navigate({ to: '/dashboard/settings' })}>
|
|
64
|
-
<User className="mr-2 h-4 w-4" />
|
|
64
|
+
<User className="mr-2 h-4 w-4" aria-hidden="true" />
|
|
65
65
|
Settings
|
|
66
66
|
</DropdownMenuItem>
|
|
67
67
|
<DropdownMenuSeparator />
|
|
68
68
|
<DropdownMenuItem onClick={handleLogout}>
|
|
69
|
-
<LogOut className="mr-2 h-4 w-4" />
|
|
69
|
+
<LogOut className="mr-2 h-4 w-4" aria-hidden="true" />
|
|
70
70
|
Log out
|
|
71
71
|
</DropdownMenuItem>
|
|
72
72
|
</DropdownMenuContent>
|
|
@@ -254,6 +254,7 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
|
|
|
254
254
|
data-slot="sidebar-trigger"
|
|
255
255
|
variant="ghost"
|
|
256
256
|
size="icon"
|
|
257
|
+
aria-label="Toggle sidebar"
|
|
257
258
|
className={cn('size-7', className)}
|
|
258
259
|
onClick={(event) => {
|
|
259
260
|
onClick?.(event)
|
|
@@ -261,7 +262,7 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
|
|
|
261
262
|
}}
|
|
262
263
|
{...props}
|
|
263
264
|
>
|
|
264
|
-
<PanelLeftIcon />
|
|
265
|
+
<PanelLeftIcon aria-hidden="true" />
|
|
265
266
|
<span className="sr-only">Toggle Sidebar</span>
|
|
266
267
|
</Button>
|
|
267
268
|
)
|
|
@@ -24,7 +24,13 @@ export const Route = createRootRouteWithContext<RouterAppContext>()({
|
|
|
24
24
|
|
|
25
25
|
function RootComponent() {
|
|
26
26
|
return (
|
|
27
|
-
<div
|
|
27
|
+
<div className="min-h-dvh">
|
|
28
|
+
<a
|
|
29
|
+
href="#main-content"
|
|
30
|
+
className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-50 focus:rounded-md focus:bg-background focus:px-3 focus:py-2 focus:text-sm focus:text-foreground focus:shadow"
|
|
31
|
+
>
|
|
32
|
+
Skip to content
|
|
33
|
+
</a>
|
|
28
34
|
<Outlet />
|
|
29
35
|
</div>
|
|
30
36
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
2
|
import { useAuth } from '@kuckit/app-web'
|
|
3
|
+
import { Button } from '@/components/ui/button'
|
|
3
4
|
|
|
4
5
|
export const Route = createFileRoute('/')({
|
|
5
6
|
component: HomePage,
|
|
@@ -11,36 +12,30 @@ function HomePage() {
|
|
|
11
12
|
|
|
12
13
|
if (isPending) {
|
|
13
14
|
return (
|
|
14
|
-
<
|
|
15
|
-
<p>Loading
|
|
16
|
-
</
|
|
15
|
+
<main id="main-content" className="mx-auto max-w-3xl px-6 py-12">
|
|
16
|
+
<p className="text-sm text-muted-foreground">Loading…</p>
|
|
17
|
+
</main>
|
|
17
18
|
)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
return (
|
|
21
|
-
<
|
|
22
|
-
<h1>Welcome to __APP_NAME__</h1>
|
|
22
|
+
<main id="main-content" className="mx-auto max-w-3xl px-6 py-12">
|
|
23
|
+
<h1 className="text-3xl font-semibold tracking-tight text-pretty">Welcome to __APP_NAME__</h1>
|
|
23
24
|
{session?.user ? (
|
|
24
|
-
<div>
|
|
25
|
-
<p
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
padding: '0.5rem 1rem',
|
|
30
|
-
cursor: 'pointer',
|
|
31
|
-
}}
|
|
32
|
-
>
|
|
33
|
-
Sign Out
|
|
34
|
-
</button>
|
|
25
|
+
<div className="mt-6 space-y-4">
|
|
26
|
+
<p className="text-sm text-muted-foreground">
|
|
27
|
+
Hello, {session.user.name || session.user.email}!
|
|
28
|
+
</p>
|
|
29
|
+
<Button onClick={() => authClient.signOut()}>Sign Out</Button>
|
|
35
30
|
</div>
|
|
36
31
|
) : (
|
|
37
|
-
<div>
|
|
38
|
-
<p>Your Kuckit application is ready
|
|
39
|
-
<
|
|
40
|
-
Sign In
|
|
41
|
-
</
|
|
32
|
+
<div className="mt-6 space-y-4">
|
|
33
|
+
<p className="text-sm text-muted-foreground">Your Kuckit application is ready.</p>
|
|
34
|
+
<Button asChild>
|
|
35
|
+
<Link to="/login">Sign In</Link>
|
|
36
|
+
</Button>
|
|
42
37
|
</div>
|
|
43
38
|
)}
|
|
44
|
-
</
|
|
39
|
+
</main>
|
|
45
40
|
)
|
|
46
41
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|
2
|
-
import { useState } from 'react'
|
|
2
|
+
import { useId, useState } from 'react'
|
|
3
3
|
import { useAuth } from '@kuckit/app-web'
|
|
4
|
+
import { Button } from '@/components/ui/button'
|
|
5
|
+
import { Input } from '@/components/ui/input'
|
|
4
6
|
|
|
5
7
|
export const Route = createFileRoute('/login')({
|
|
6
8
|
component: LoginPage,
|
|
@@ -15,6 +17,10 @@ function LoginPage() {
|
|
|
15
17
|
const [name, setName] = useState('')
|
|
16
18
|
const [error, setError] = useState('')
|
|
17
19
|
const [loading, setLoading] = useState(false)
|
|
20
|
+
const emailId = useId()
|
|
21
|
+
const passwordId = useId()
|
|
22
|
+
const nameId = useId()
|
|
23
|
+
const errorId = useId()
|
|
18
24
|
|
|
19
25
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
20
26
|
e.preventDefault()
|
|
@@ -36,73 +42,98 @@ function LoginPage() {
|
|
|
36
42
|
}
|
|
37
43
|
navigate({ to: '/' })
|
|
38
44
|
} catch (err) {
|
|
39
|
-
setError(
|
|
45
|
+
setError(
|
|
46
|
+
err instanceof Error
|
|
47
|
+
? err.message
|
|
48
|
+
: 'Authentication failed. Check your details and try again.'
|
|
49
|
+
)
|
|
40
50
|
} finally {
|
|
41
51
|
setLoading(false)
|
|
42
52
|
}
|
|
43
53
|
}
|
|
44
54
|
|
|
45
55
|
return (
|
|
46
|
-
<
|
|
47
|
-
|
|
56
|
+
<main
|
|
57
|
+
id="main-content"
|
|
58
|
+
className="mx-auto flex min-h-dvh w-full max-w-md flex-col justify-center px-6 py-12"
|
|
59
|
+
>
|
|
60
|
+
<h1 className="text-3xl font-semibold tracking-tight text-pretty">
|
|
61
|
+
{isSignUp ? 'Create Account' : 'Sign In'}
|
|
62
|
+
</h1>
|
|
63
|
+
<p className="mt-2 text-sm text-muted-foreground">Use your email and password to continue.</p>
|
|
48
64
|
|
|
49
65
|
<form
|
|
50
66
|
onSubmit={handleSubmit}
|
|
51
|
-
|
|
67
|
+
className="mt-8 space-y-6"
|
|
68
|
+
aria-describedby={error ? errorId : undefined}
|
|
52
69
|
>
|
|
53
70
|
{isSignUp && (
|
|
54
|
-
<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
71
|
+
<div className="space-y-2">
|
|
72
|
+
<label className="text-sm font-medium" htmlFor={nameId}>
|
|
73
|
+
Full Name
|
|
74
|
+
</label>
|
|
75
|
+
<Input
|
|
76
|
+
id={nameId}
|
|
77
|
+
name="name"
|
|
78
|
+
type="text"
|
|
79
|
+
autoComplete="name"
|
|
80
|
+
placeholder="Sam Rivera…"
|
|
81
|
+
value={name}
|
|
82
|
+
onChange={(e) => setName(e.target.value)}
|
|
83
|
+
required
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
<div className="space-y-2">
|
|
88
|
+
<label className="text-sm font-medium" htmlFor={emailId}>
|
|
89
|
+
Email
|
|
90
|
+
</label>
|
|
91
|
+
<Input
|
|
92
|
+
id={emailId}
|
|
93
|
+
name="email"
|
|
94
|
+
type="email"
|
|
95
|
+
autoComplete={isSignUp ? 'email' : 'username'}
|
|
96
|
+
inputMode="email"
|
|
97
|
+
spellCheck={false}
|
|
98
|
+
placeholder="you@company.com…"
|
|
99
|
+
value={email}
|
|
100
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
59
101
|
required
|
|
60
|
-
style={{ padding: '0.5rem', fontSize: '1rem' }}
|
|
61
102
|
/>
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
/>
|
|
103
|
+
</div>
|
|
104
|
+
<div className="space-y-2">
|
|
105
|
+
<label className="text-sm font-medium" htmlFor={passwordId}>
|
|
106
|
+
Password
|
|
107
|
+
</label>
|
|
108
|
+
<Input
|
|
109
|
+
id={passwordId}
|
|
110
|
+
name="password"
|
|
111
|
+
type="password"
|
|
112
|
+
autoComplete={isSignUp ? 'new-password' : 'current-password'}
|
|
113
|
+
placeholder="At least 8 characters…"
|
|
114
|
+
value={password}
|
|
115
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
116
|
+
required
|
|
117
|
+
/>
|
|
118
|
+
</div>
|
|
79
119
|
|
|
80
|
-
{error &&
|
|
120
|
+
{error && (
|
|
121
|
+
<p id={errorId} className="text-sm text-destructive" aria-live="polite">
|
|
122
|
+
{error}
|
|
123
|
+
</p>
|
|
124
|
+
)}
|
|
81
125
|
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
style={{ padding: '0.5rem 1rem', fontSize: '1rem', cursor: 'pointer' }}
|
|
86
|
-
>
|
|
87
|
-
{loading ? 'Loading...' : isSignUp ? 'Create Account' : 'Sign In'}
|
|
88
|
-
</button>
|
|
126
|
+
<Button type="submit" className="w-full" disabled={loading}>
|
|
127
|
+
{loading ? 'Loading…' : isSignUp ? 'Create Account' : 'Sign In'}
|
|
128
|
+
</Button>
|
|
89
129
|
</form>
|
|
90
130
|
|
|
91
|
-
<p
|
|
92
|
-
{isSignUp ? 'Already have an account?
|
|
93
|
-
<button
|
|
94
|
-
onClick={() => setIsSignUp(!isSignUp)}
|
|
95
|
-
style={{
|
|
96
|
-
background: 'none',
|
|
97
|
-
border: 'none',
|
|
98
|
-
color: 'blue',
|
|
99
|
-
cursor: 'pointer',
|
|
100
|
-
textDecoration: 'underline',
|
|
101
|
-
}}
|
|
102
|
-
>
|
|
131
|
+
<p className="mt-6 text-sm text-muted-foreground">
|
|
132
|
+
{isSignUp ? 'Already have an account?' : "Don't have an account?"}{' '}
|
|
133
|
+
<Button variant="link" type="button" onClick={() => setIsSignUp(!isSignUp)}>
|
|
103
134
|
{isSignUp ? 'Sign In' : 'Create Account'}
|
|
104
|
-
</
|
|
135
|
+
</Button>
|
|
105
136
|
</p>
|
|
106
|
-
</
|
|
137
|
+
</main>
|
|
107
138
|
)
|
|
108
139
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { defineConfig } from 'drizzle-kit'
|
|
2
2
|
import dotenv from 'dotenv'
|
|
3
|
+
import * as fs from 'fs'
|
|
4
|
+
import * as path from 'path'
|
|
3
5
|
import { dirname, resolve } from 'path'
|
|
4
6
|
import { fileURLToPath } from 'url'
|
|
5
7
|
import { getModuleSchemaPaths } from '@kuckit/db/schema-discovery'
|
|
@@ -26,8 +28,22 @@ function buildDatabaseUrl(): string {
|
|
|
26
28
|
)
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
function normalizeSchemaPath(schemaPath: string): string {
|
|
32
|
+
if (schemaPath.includes(`${path.sep}dist${path.sep}`)) {
|
|
33
|
+
return `${schemaPath}/*.js`
|
|
34
|
+
}
|
|
35
|
+
if (fs.existsSync(path.join(schemaPath, 'auth.js'))) {
|
|
36
|
+
return `${schemaPath}/*.js`
|
|
37
|
+
}
|
|
38
|
+
return schemaPath
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const shouldNormalizeSchemas = process.env.DRIZZLE_SCHEMA_NORMALIZE !== 'false'
|
|
42
|
+
|
|
29
43
|
export default defineConfig({
|
|
30
|
-
schema:
|
|
44
|
+
schema: shouldNormalizeSchemas
|
|
45
|
+
? getModuleSchemaPaths().map(normalizeSchemaPath)
|
|
46
|
+
: getModuleSchemaPaths(),
|
|
31
47
|
out: './drizzle',
|
|
32
48
|
dialect: 'postgresql',
|
|
33
49
|
dbCredentials: { url: buildDatabaseUrl() },
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@kuckit/sdk": "^
|
|
36
|
-
"@kuckit/api": "^
|
|
37
|
-
"@kuckit/auth": "^
|
|
38
|
-
"@kuckit/db": "^
|
|
39
|
-
"@kuckit/domain": "^
|
|
40
|
-
"@kuckit/infrastructure": "^
|
|
35
|
+
"@kuckit/sdk": "^5.0.0",
|
|
36
|
+
"@kuckit/api": "^5.0.0",
|
|
37
|
+
"@kuckit/auth": "^5.0.0",
|
|
38
|
+
"@kuckit/db": "^5.0.0",
|
|
39
|
+
"@kuckit/domain": "^5.0.0",
|
|
40
|
+
"@kuckit/infrastructure": "^5.0.0",
|
|
41
41
|
"awilix": "^12.0.5",
|
|
42
42
|
"dotenv": "^17.0.0",
|
|
43
43
|
"drizzle-orm": "^0.44.0",
|
|
@@ -19,15 +19,15 @@
|
|
|
19
19
|
"id": "items",
|
|
20
20
|
"server": ".",
|
|
21
21
|
"client": "./client",
|
|
22
|
-
"schemaDir": "src/server/
|
|
22
|
+
"schemaDir": "src/server/schema"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"typescript": "^5"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@kuckit/sdk": "^
|
|
29
|
-
"@kuckit/sdk-react": "^
|
|
30
|
-
"@kuckit/api": "^
|
|
28
|
+
"@kuckit/sdk": "^5.0.0",
|
|
29
|
+
"@kuckit/sdk-react": "^5.0.0",
|
|
30
|
+
"@kuckit/api": "^5.0.0",
|
|
31
31
|
"@orpc/server": "^1.10.0",
|
|
32
32
|
"@orpc/zod": "^1.10.0",
|
|
33
33
|
"drizzle-orm": "^0.44.0",
|