create-kuckit-app 0.1.1 → 0.2.1
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/bin.js +1 -1
- package/dist/{create-project-DTm05G7D.js → create-project-CP-h4Ygi.js} +7 -5
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/templates/base/.claude/CLAUDE.md +44 -0
- package/templates/base/.claude/agents/daidalos.md +76 -0
- package/templates/base/.claude/agents/episteme.md +79 -0
- package/templates/base/.claude/agents/librarian.md +132 -0
- package/templates/base/.claude/agents/oracle.md +210 -0
- package/templates/base/.claude/commands/create-plan.md +159 -0
- package/templates/base/.claude/commands/file-beads.md +98 -0
- package/templates/base/.claude/commands/review-beads.md +161 -0
- package/templates/base/.claude/settings.json +11 -0
- package/templates/base/.claude/skills/kuckit/SKILL.md +436 -0
- package/templates/base/.claude/skills/kuckit/references/ARCHITECTURE.md +388 -0
- package/templates/base/.claude/skills/kuckit/references/CLI-COMMANDS.md +365 -0
- package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +581 -0
- package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +112 -0
- package/templates/base/.claude/skills/kuckit/references/PUBLISHING.md +231 -0
- package/templates/base/.env.example +13 -0
- package/templates/base/.github/workflows/ci.yml +28 -0
- package/templates/base/.husky/pre-commit +1 -0
- package/templates/base/.prettierignore +5 -0
- package/templates/base/.prettierrc +8 -0
- package/templates/base/AGENTS.md +351 -0
- package/templates/base/apps/server/.env.example +18 -0
- package/templates/base/apps/server/AGENTS.md +93 -0
- package/templates/base/apps/server/package.json +13 -2
- package/templates/base/apps/server/src/app.ts +20 -0
- package/templates/base/apps/server/src/auth.ts +10 -0
- package/templates/base/apps/server/src/config/modules.ts +22 -0
- package/templates/base/apps/server/src/container.ts +81 -0
- package/templates/base/apps/server/src/health.ts +27 -0
- package/templates/base/apps/server/src/middleware/container.ts +41 -0
- package/templates/base/apps/server/src/rpc-router-registry.ts +26 -0
- package/templates/base/apps/server/src/rpc.ts +31 -0
- package/templates/base/apps/server/src/server.ts +42 -14
- package/templates/base/apps/web/.env.example +4 -0
- package/templates/base/apps/web/AGENTS.md +127 -0
- package/templates/base/apps/web/index.html +1 -1
- package/templates/base/apps/web/package.json +15 -2
- package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +82 -0
- package/templates/base/apps/web/src/lib/kuckit-router.ts +42 -0
- package/templates/base/apps/web/src/main.tsx +26 -14
- package/templates/base/apps/web/src/modules.client.ts +4 -3
- package/templates/base/apps/web/src/providers/KuckitProvider.tsx +147 -0
- package/templates/base/apps/web/src/providers/ServicesProvider.tsx +47 -0
- package/templates/base/apps/web/src/routeTree.gen.ts +91 -0
- package/templates/base/apps/web/src/routes/$.tsx +14 -0
- package/templates/base/apps/web/src/routes/__root.tsx +31 -0
- package/templates/base/apps/web/src/routes/index.tsx +46 -0
- package/templates/base/apps/web/src/routes/login.tsx +108 -0
- package/templates/base/apps/web/src/services/auth-client.ts +12 -0
- package/templates/base/apps/web/src/services/index.ts +3 -0
- package/templates/base/apps/web/src/services/rpc.ts +29 -0
- package/templates/base/apps/web/src/services/types.ts +14 -0
- package/templates/base/apps/web/tsconfig.json +5 -1
- package/templates/base/apps/web/vite.config.ts +8 -1
- package/templates/base/docker-compose.yml +23 -0
- package/templates/base/eslint.config.js +18 -0
- package/templates/base/package.json +32 -2
- package/templates/base/packages/api/AGENTS.md +66 -0
- package/templates/base/packages/api/package.json +35 -0
- package/templates/base/packages/api/src/context.ts +48 -0
- package/templates/base/packages/api/src/index.ts +22 -0
- package/templates/base/packages/api/tsconfig.json +8 -0
- package/templates/base/packages/auth/AGENTS.md +61 -0
- package/templates/base/packages/auth/package.json +27 -0
- package/templates/base/packages/auth/src/index.ts +22 -0
- package/templates/base/packages/auth/tsconfig.json +8 -0
- package/templates/base/packages/db/AGENTS.md +74 -0
- package/templates/base/packages/db/drizzle.config.ts +19 -0
- package/templates/base/packages/db/package.json +36 -0
- package/templates/base/packages/db/src/connection.ts +40 -0
- package/templates/base/packages/db/src/index.ts +4 -0
- package/templates/base/packages/db/src/migrations/0000_init.sql +54 -0
- package/templates/base/packages/db/src/migrations/meta/_journal.json +13 -0
- package/templates/base/packages/db/src/schema/auth.ts +51 -0
- package/templates/base/packages/db/tsconfig.json +8 -0
- package/templates/base/packages/items-module/AGENTS.md +210 -0
- package/templates/base/packages/items-module/package.json +32 -0
- package/templates/base/packages/items-module/src/adapters/item.drizzle.ts +66 -0
- package/templates/base/packages/items-module/src/api/items.router.ts +47 -0
- package/templates/base/packages/items-module/src/client-module.ts +39 -0
- package/templates/base/packages/items-module/src/domain/item.entity.ts +36 -0
- package/templates/base/packages/items-module/src/index.ts +15 -0
- package/templates/base/packages/items-module/src/module.ts +53 -0
- package/templates/base/packages/items-module/src/ports/item.repository.ts +13 -0
- package/templates/base/packages/items-module/src/ui/ItemsPage.tsx +144 -0
- package/templates/base/packages/items-module/src/usecases/create-item.ts +25 -0
- package/templates/base/packages/items-module/src/usecases/delete-item.ts +18 -0
- package/templates/base/packages/items-module/src/usecases/get-item.ts +19 -0
- package/templates/base/packages/items-module/src/usecases/list-items.ts +21 -0
- package/templates/base/packages/items-module/tsconfig.json +9 -0
- package/templates/base/turbo.json +13 -1
- package/templates/base/apps/web/src/App.tsx +0 -16
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useRouterState, Link, useNavigate } from '@tanstack/react-router'
|
|
2
|
+
import { useKuckit } from '@/providers/KuckitProvider'
|
|
3
|
+
import { useServices } from '@/providers/ServicesProvider'
|
|
4
|
+
import { useEffect, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dynamic route renderer for Kuckit module routes.
|
|
8
|
+
*
|
|
9
|
+
* This component looks up the current path in the RouteRegistry
|
|
10
|
+
* and renders the corresponding module component if found.
|
|
11
|
+
*
|
|
12
|
+
* For routes with meta.requiresAuth: true, redirects to /login if not authenticated.
|
|
13
|
+
*/
|
|
14
|
+
export function KuckitModuleRoute() {
|
|
15
|
+
const { routeRegistry } = useKuckit()
|
|
16
|
+
const { authClient } = useServices()
|
|
17
|
+
const { location } = useRouterState()
|
|
18
|
+
const navigate = useNavigate()
|
|
19
|
+
const pathname = location.pathname
|
|
20
|
+
|
|
21
|
+
const [authChecked, setAuthChecked] = useState(false)
|
|
22
|
+
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
|
23
|
+
|
|
24
|
+
// Find matching route in registry
|
|
25
|
+
const routeDef = routeRegistry.getAll().find((r) => r.path === pathname)
|
|
26
|
+
|
|
27
|
+
// Check auth for routes with requiresAuth
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
async function checkAuth() {
|
|
30
|
+
if (!routeDef) {
|
|
31
|
+
setAuthChecked(true)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if auth is required
|
|
36
|
+
if (routeDef.meta?.requiresAuth) {
|
|
37
|
+
const session = await authClient.getSession()
|
|
38
|
+
if (!session.data) {
|
|
39
|
+
// Redirect to login with return URL
|
|
40
|
+
navigate({
|
|
41
|
+
to: '/login',
|
|
42
|
+
search: { redirect: pathname },
|
|
43
|
+
})
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
setIsAuthenticated(true)
|
|
47
|
+
}
|
|
48
|
+
setAuthChecked(true)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
checkAuth()
|
|
52
|
+
}, [routeDef, pathname, navigate, authClient])
|
|
53
|
+
|
|
54
|
+
// Show loading while checking auth for protected routes
|
|
55
|
+
if (routeDef?.meta?.requiresAuth && !authChecked) {
|
|
56
|
+
return (
|
|
57
|
+
<div className="flex items-center justify-center min-h-screen">
|
|
58
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Protected route - waiting for redirect
|
|
64
|
+
if (routeDef?.meta?.requiresAuth && !isAuthenticated) {
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (!routeDef) {
|
|
69
|
+
return (
|
|
70
|
+
<div className="flex flex-col items-center justify-center min-h-[50vh] gap-4">
|
|
71
|
+
<h1 className="text-2xl font-bold">Page Not Found</h1>
|
|
72
|
+
<p className="text-muted-foreground">The page "{pathname}" could not be found.</p>
|
|
73
|
+
<Link to="/" className="text-primary hover:underline">
|
|
74
|
+
Go Home
|
|
75
|
+
</Link>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const Component = routeDef.component as React.ComponentType
|
|
81
|
+
return <Component />
|
|
82
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRoute, type AnyRoute, type RouteComponent } from '@tanstack/react-router'
|
|
2
|
+
import type { QueryClient } from '@tanstack/react-query'
|
|
3
|
+
import type { RouteRegistry, RouteDefinition } from '@kuckit/sdk-react'
|
|
4
|
+
import type { ORPCUtils } from '../services/types'
|
|
5
|
+
|
|
6
|
+
export interface KuckitRouterContext {
|
|
7
|
+
orpc: ORPCUtils
|
|
8
|
+
queryClient: QueryClient
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build TanStack Router routes from a RouteRegistry
|
|
13
|
+
*/
|
|
14
|
+
export function buildModuleRoutes(routeRegistry: RouteRegistry, rootRoute: AnyRoute): AnyRoute[] {
|
|
15
|
+
const routes = routeRegistry.getAll()
|
|
16
|
+
const routeMap = new Map<string, AnyRoute>()
|
|
17
|
+
|
|
18
|
+
for (const routeDef of routes) {
|
|
19
|
+
const route = createModuleRoute(routeDef, rootRoute)
|
|
20
|
+
routeMap.set(routeDef.id, route)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const topLevelRoutes = routes
|
|
24
|
+
.filter((r) => !r.parentRouteId || r.parentRouteId === '__root__')
|
|
25
|
+
.map((r) => routeMap.get(r.id)!)
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
|
|
28
|
+
return topLevelRoutes
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createModuleRoute(routeDef: RouteDefinition, parentRoute: AnyRoute): AnyRoute {
|
|
32
|
+
return createRoute({
|
|
33
|
+
getParentRoute: () => parentRoute,
|
|
34
|
+
path: routeDef.path,
|
|
35
|
+
component: routeDef.component as RouteComponent,
|
|
36
|
+
...(routeDef.meta?.title && {
|
|
37
|
+
head: () => ({
|
|
38
|
+
meta: [{ title: routeDef.meta!.title }],
|
|
39
|
+
}),
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
}
|
|
@@ -1,14 +1,26 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { createRouter } from '@tanstack/react-router'
|
|
2
|
+
import ReactDOM from 'react-dom/client'
|
|
3
|
+
import { routeTree } from './routeTree.gen'
|
|
4
|
+
import { ServicesProvider } from './providers/ServicesProvider'
|
|
5
|
+
import { KuckitProvider } from './providers/KuckitProvider'
|
|
6
|
+
|
|
7
|
+
declare module '@tanstack/react-router' {
|
|
8
|
+
interface Register {
|
|
9
|
+
router: ReturnType<typeof createRouter<typeof routeTree>>
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const rootElement = document.getElementById('app')
|
|
14
|
+
|
|
15
|
+
if (!rootElement) {
|
|
16
|
+
throw new Error('Root element not found')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!rootElement.innerHTML) {
|
|
20
|
+
const root = ReactDOM.createRoot(rootElement)
|
|
21
|
+
root.render(
|
|
22
|
+
<ServicesProvider>
|
|
23
|
+
<KuckitProvider />
|
|
24
|
+
</ServicesProvider>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ClientModuleSpec } from '@kuckit/sdk-react'
|
|
2
|
+
import { kuckitClientModule as itemsClientModule } from '@__APP_NAME_KEBAB__/items-module/client'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Client modules configuration
|
|
@@ -7,9 +8,9 @@ import type { ClientModuleSpec } from '@kuckit/sdk-react'
|
|
|
7
8
|
*/
|
|
8
9
|
export const getClientModuleSpecs = (): ClientModuleSpec[] => {
|
|
9
10
|
const modules: ClientModuleSpec[] = [
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
//
|
|
11
|
+
// KUCKIT_CLIENT_MODULES_START
|
|
12
|
+
{ module: itemsClientModule },
|
|
13
|
+
// KUCKIT_CLIENT_MODULES_END
|
|
13
14
|
]
|
|
14
15
|
|
|
15
16
|
return modules
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createContext, useContext, useEffect, useState, useMemo, type ReactNode } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
RouterProvider,
|
|
4
|
+
createRouter,
|
|
5
|
+
createRoute,
|
|
6
|
+
type RouteComponent,
|
|
7
|
+
} from '@tanstack/react-router'
|
|
8
|
+
import {
|
|
9
|
+
loadKuckitClientModules,
|
|
10
|
+
KuckitNavProvider,
|
|
11
|
+
KuckitSlotProvider,
|
|
12
|
+
KuckitRpcProvider,
|
|
13
|
+
type LoadClientModulesResult,
|
|
14
|
+
type RouteRegistry,
|
|
15
|
+
type NavRegistry,
|
|
16
|
+
type SlotRegistry,
|
|
17
|
+
} from '@kuckit/sdk-react'
|
|
18
|
+
import { routeTree } from '../routeTree.gen'
|
|
19
|
+
import { Route as rootRoute } from '../routes/__root'
|
|
20
|
+
import { useServices } from './ServicesProvider'
|
|
21
|
+
import { getClientModuleSpecs } from '../modules.client'
|
|
22
|
+
|
|
23
|
+
interface KuckitContextValue {
|
|
24
|
+
routeRegistry: RouteRegistry
|
|
25
|
+
navRegistry: NavRegistry
|
|
26
|
+
slotRegistry: SlotRegistry
|
|
27
|
+
isLoaded: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const KuckitContext = createContext<KuckitContextValue | null>(null)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Hook to access Kuckit registries
|
|
34
|
+
*/
|
|
35
|
+
export function useKuckit(): KuckitContextValue {
|
|
36
|
+
const ctx = useContext(KuckitContext)
|
|
37
|
+
if (!ctx) {
|
|
38
|
+
throw new Error('useKuckit must be used within a KuckitProvider')
|
|
39
|
+
}
|
|
40
|
+
return ctx
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface KuckitProviderProps {
|
|
44
|
+
children?: ReactNode
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* KuckitProvider handles module loading and router creation
|
|
49
|
+
*/
|
|
50
|
+
export function KuckitProvider({ children }: KuckitProviderProps) {
|
|
51
|
+
const { orpc, queryClient, rpcClient } = useServices()
|
|
52
|
+
const [loadResult, setLoadResult] = useState<LoadClientModulesResult | null>(null)
|
|
53
|
+
const [error, setError] = useState<Error | null>(null)
|
|
54
|
+
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
let cancelled = false
|
|
57
|
+
|
|
58
|
+
const loadModules = async () => {
|
|
59
|
+
try {
|
|
60
|
+
const result = await loadKuckitClientModules({
|
|
61
|
+
orpc,
|
|
62
|
+
queryClient,
|
|
63
|
+
env: import.meta.env.MODE,
|
|
64
|
+
modules: getClientModuleSpecs(),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
if (!cancelled) {
|
|
68
|
+
setLoadResult(result)
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (!cancelled) {
|
|
72
|
+
setError(err instanceof Error ? err : new Error(String(err)))
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
loadModules()
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true
|
|
81
|
+
}
|
|
82
|
+
}, [orpc, queryClient])
|
|
83
|
+
|
|
84
|
+
// Build router with module routes
|
|
85
|
+
const router = useMemo(() => {
|
|
86
|
+
if (!loadResult) return null
|
|
87
|
+
|
|
88
|
+
const moduleRouteDefs = loadResult.routeRegistry.getAll()
|
|
89
|
+
const moduleRoutes = moduleRouteDefs.map((routeDef) =>
|
|
90
|
+
createRoute({
|
|
91
|
+
getParentRoute: () => rootRoute,
|
|
92
|
+
path: routeDef.path,
|
|
93
|
+
component: routeDef.component as RouteComponent,
|
|
94
|
+
})
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if (moduleRoutes.length > 0) {
|
|
98
|
+
console.log(
|
|
99
|
+
`Loaded ${moduleRoutes.length} module routes:`,
|
|
100
|
+
moduleRouteDefs.map((r) => r.path)
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return createRouter({
|
|
105
|
+
routeTree,
|
|
106
|
+
defaultPreload: 'intent',
|
|
107
|
+
context: { orpc, queryClient },
|
|
108
|
+
})
|
|
109
|
+
}, [loadResult, orpc, queryClient])
|
|
110
|
+
|
|
111
|
+
if (error) {
|
|
112
|
+
return (
|
|
113
|
+
<div style={{ padding: '2rem', color: 'red' }}>
|
|
114
|
+
<h2>Failed to load modules</h2>
|
|
115
|
+
<p>{error.message}</p>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!loadResult || !router) {
|
|
121
|
+
return (
|
|
122
|
+
<div style={{ padding: '2rem' }}>
|
|
123
|
+
<p>Loading...</p>
|
|
124
|
+
</div>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const contextValue: KuckitContextValue = {
|
|
129
|
+
routeRegistry: loadResult.routeRegistry,
|
|
130
|
+
navRegistry: loadResult.navRegistry,
|
|
131
|
+
slotRegistry: loadResult.slotRegistry,
|
|
132
|
+
isLoaded: true,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<KuckitContext.Provider value={contextValue}>
|
|
137
|
+
<KuckitRpcProvider client={rpcClient}>
|
|
138
|
+
<KuckitNavProvider registry={loadResult.navRegistry}>
|
|
139
|
+
<KuckitSlotProvider registry={loadResult.slotRegistry}>
|
|
140
|
+
<RouterProvider router={router} />
|
|
141
|
+
{children}
|
|
142
|
+
</KuckitSlotProvider>
|
|
143
|
+
</KuckitNavProvider>
|
|
144
|
+
</KuckitRpcProvider>
|
|
145
|
+
</KuckitContext.Provider>
|
|
146
|
+
)
|
|
147
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo, type ReactNode } from 'react'
|
|
2
|
+
import { QueryClientProvider } from '@tanstack/react-query'
|
|
3
|
+
import {
|
|
4
|
+
createQueryClient,
|
|
5
|
+
createRPCLink,
|
|
6
|
+
createRPCClient,
|
|
7
|
+
createORPCUtils,
|
|
8
|
+
createAuthClientService,
|
|
9
|
+
type Services,
|
|
10
|
+
} from '../services'
|
|
11
|
+
|
|
12
|
+
const ServicesContext = createContext<Services | null>(null)
|
|
13
|
+
|
|
14
|
+
interface ServicesProviderProps {
|
|
15
|
+
children: ReactNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ServicesProvider({ children }: ServicesProviderProps) {
|
|
19
|
+
const services = useMemo<Services>(() => {
|
|
20
|
+
const queryClient = createQueryClient()
|
|
21
|
+
const link = createRPCLink()
|
|
22
|
+
const rpcClient = createRPCClient(link)
|
|
23
|
+
const orpc = createORPCUtils(rpcClient)
|
|
24
|
+
const authClient = createAuthClientService()
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
queryClient,
|
|
28
|
+
rpcClient,
|
|
29
|
+
orpc,
|
|
30
|
+
authClient,
|
|
31
|
+
}
|
|
32
|
+
}, [])
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ServicesContext.Provider value={services}>
|
|
36
|
+
<QueryClientProvider client={services.queryClient}>{children}</QueryClientProvider>
|
|
37
|
+
</ServicesContext.Provider>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useServices(): Services {
|
|
42
|
+
const context = useContext(ServicesContext)
|
|
43
|
+
if (!context) {
|
|
44
|
+
throw new Error('useServices must be used within a ServicesProvider')
|
|
45
|
+
}
|
|
46
|
+
return context
|
|
47
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
// noinspection JSUnusedGlobalSymbols
|
|
6
|
+
|
|
7
|
+
// This file was automatically generated by TanStack Router.
|
|
8
|
+
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
+
|
|
11
|
+
// Import Routes
|
|
12
|
+
|
|
13
|
+
import { Route as rootRoute } from './routes/__root'
|
|
14
|
+
import { Route as LoginImport } from './routes/login'
|
|
15
|
+
import { Route as IndexImport } from './routes/index'
|
|
16
|
+
|
|
17
|
+
// Create/Update Routes
|
|
18
|
+
|
|
19
|
+
const LoginRoute = LoginImport.update({
|
|
20
|
+
id: '/login',
|
|
21
|
+
path: '/login',
|
|
22
|
+
getParentRoute: () => rootRoute,
|
|
23
|
+
} as any)
|
|
24
|
+
|
|
25
|
+
const IndexRoute = IndexImport.update({
|
|
26
|
+
id: '/',
|
|
27
|
+
path: '/',
|
|
28
|
+
getParentRoute: () => rootRoute,
|
|
29
|
+
} as any)
|
|
30
|
+
|
|
31
|
+
// Populate the FileRoutesByPath interface
|
|
32
|
+
|
|
33
|
+
declare module '@tanstack/react-router' {
|
|
34
|
+
interface FileRoutesByPath {
|
|
35
|
+
'/': {
|
|
36
|
+
id: '/'
|
|
37
|
+
path: '/'
|
|
38
|
+
fullPath: '/'
|
|
39
|
+
preLoaderRoute: typeof IndexImport
|
|
40
|
+
parentRoute: typeof rootRoute
|
|
41
|
+
}
|
|
42
|
+
'/login': {
|
|
43
|
+
id: '/login'
|
|
44
|
+
path: '/login'
|
|
45
|
+
fullPath: '/login'
|
|
46
|
+
preLoaderRoute: typeof LoginImport
|
|
47
|
+
parentRoute: typeof rootRoute
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Create and export the route tree
|
|
53
|
+
|
|
54
|
+
export interface FileRoutesByFullPath {
|
|
55
|
+
'/': typeof IndexRoute
|
|
56
|
+
'/login': typeof LoginRoute
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface FileRoutesByTo {
|
|
60
|
+
'/': typeof IndexRoute
|
|
61
|
+
'/login': typeof LoginRoute
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface FileRoutesById {
|
|
65
|
+
__root__: typeof rootRoute
|
|
66
|
+
'/': typeof IndexRoute
|
|
67
|
+
'/login': typeof LoginRoute
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface FileRouteTypes {
|
|
71
|
+
fileRoutesByFullPath: FileRoutesByFullPath
|
|
72
|
+
fullPaths: '/' | '/login'
|
|
73
|
+
fileRoutesByTo: FileRoutesByTo
|
|
74
|
+
to: '/' | '/login'
|
|
75
|
+
id: '__root__' | '/' | '/login'
|
|
76
|
+
fileRoutesById: FileRoutesById
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface RootRouteChildren {
|
|
80
|
+
IndexRoute: typeof IndexRoute
|
|
81
|
+
LoginRoute: typeof LoginRoute
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const rootRouteChildren: RootRouteChildren = {
|
|
85
|
+
IndexRoute: IndexRoute,
|
|
86
|
+
LoginRoute: LoginRoute,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const routeTree = rootRoute
|
|
90
|
+
._addFileChildren(rootRouteChildren)
|
|
91
|
+
._addFileTypes<FileRouteTypes>()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { KuckitModuleRoute } from '@/components/KuckitModuleRoute'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Catch-all route for KuckitModule routes at root level.
|
|
6
|
+
*
|
|
7
|
+
* This route matches any path not handled by other file-based routes
|
|
8
|
+
* and uses the KuckitModuleRoute component to look up and render
|
|
9
|
+
* the appropriate module component from the RouteRegistry.
|
|
10
|
+
*/
|
|
11
|
+
// @ts-expect-error - Route types are generated when dev server runs
|
|
12
|
+
export const Route = createFileRoute('/$')({
|
|
13
|
+
component: KuckitModuleRoute,
|
|
14
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { QueryClient } from '@tanstack/react-query'
|
|
2
|
+
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
|
3
|
+
import type { ORPCUtils } from '../services/types'
|
|
4
|
+
|
|
5
|
+
export interface RouterAppContext {
|
|
6
|
+
orpc: ORPCUtils
|
|
7
|
+
queryClient: QueryClient
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const Route = createRootRouteWithContext<RouterAppContext>()({
|
|
11
|
+
component: RootComponent,
|
|
12
|
+
head: () => ({
|
|
13
|
+
meta: [
|
|
14
|
+
{
|
|
15
|
+
title: '__APP_NAME__',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'description',
|
|
19
|
+
content: '__APP_NAME__ - A Kuckit application',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
}),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
function RootComponent() {
|
|
26
|
+
return (
|
|
27
|
+
<div style={{ minHeight: '100vh' }}>
|
|
28
|
+
<Outlet />
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
+
import { useServices } from '../providers/ServicesProvider'
|
|
3
|
+
|
|
4
|
+
export const Route = createFileRoute('/')({
|
|
5
|
+
component: HomePage,
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
function HomePage() {
|
|
9
|
+
const { authClient } = useServices()
|
|
10
|
+
const { data: session, isPending } = authClient.useSession()
|
|
11
|
+
|
|
12
|
+
if (isPending) {
|
|
13
|
+
return (
|
|
14
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
|
|
15
|
+
<p>Loading...</p>
|
|
16
|
+
</div>
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
|
|
22
|
+
<h1>Welcome to __APP_NAME__</h1>
|
|
23
|
+
{session?.user ? (
|
|
24
|
+
<div>
|
|
25
|
+
<p>Hello, {session.user.name || session.user.email}!</p>
|
|
26
|
+
<button
|
|
27
|
+
onClick={() => authClient.signOut()}
|
|
28
|
+
style={{
|
|
29
|
+
padding: '0.5rem 1rem',
|
|
30
|
+
cursor: 'pointer',
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
Sign Out
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
) : (
|
|
37
|
+
<div>
|
|
38
|
+
<p>Your Kuckit application is ready!</p>
|
|
39
|
+
<Link to="/login" style={{ color: 'blue', textDecoration: 'underline' }}>
|
|
40
|
+
Sign In
|
|
41
|
+
</Link>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|
2
|
+
import { useState } from 'react'
|
|
3
|
+
import { useServices } from '../providers/ServicesProvider'
|
|
4
|
+
|
|
5
|
+
export const Route = createFileRoute('/login')({
|
|
6
|
+
component: LoginPage,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
function LoginPage() {
|
|
10
|
+
const { authClient } = useServices()
|
|
11
|
+
const navigate = useNavigate()
|
|
12
|
+
const [isSignUp, setIsSignUp] = useState(false)
|
|
13
|
+
const [email, setEmail] = useState('')
|
|
14
|
+
const [password, setPassword] = useState('')
|
|
15
|
+
const [name, setName] = useState('')
|
|
16
|
+
const [error, setError] = useState('')
|
|
17
|
+
const [loading, setLoading] = useState(false)
|
|
18
|
+
|
|
19
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
20
|
+
e.preventDefault()
|
|
21
|
+
setError('')
|
|
22
|
+
setLoading(true)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
if (isSignUp) {
|
|
26
|
+
await authClient.signUp.email({
|
|
27
|
+
email,
|
|
28
|
+
password,
|
|
29
|
+
name,
|
|
30
|
+
})
|
|
31
|
+
} else {
|
|
32
|
+
await authClient.signIn.email({
|
|
33
|
+
email,
|
|
34
|
+
password,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
navigate({ to: '/' })
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setError(err instanceof Error ? err.message : 'Authentication failed')
|
|
40
|
+
} finally {
|
|
41
|
+
setLoading(false)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div style={{ padding: '2rem', fontFamily: 'system-ui', maxWidth: '400px', margin: '0 auto' }}>
|
|
47
|
+
<h1>{isSignUp ? 'Create Account' : 'Sign In'}</h1>
|
|
48
|
+
|
|
49
|
+
<form
|
|
50
|
+
onSubmit={handleSubmit}
|
|
51
|
+
style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
|
|
52
|
+
>
|
|
53
|
+
{isSignUp && (
|
|
54
|
+
<input
|
|
55
|
+
type="text"
|
|
56
|
+
placeholder="Name"
|
|
57
|
+
value={name}
|
|
58
|
+
onChange={(e) => setName(e.target.value)}
|
|
59
|
+
required
|
|
60
|
+
style={{ padding: '0.5rem', fontSize: '1rem' }}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
<input
|
|
64
|
+
type="email"
|
|
65
|
+
placeholder="Email"
|
|
66
|
+
value={email}
|
|
67
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
68
|
+
required
|
|
69
|
+
style={{ padding: '0.5rem', fontSize: '1rem' }}
|
|
70
|
+
/>
|
|
71
|
+
<input
|
|
72
|
+
type="password"
|
|
73
|
+
placeholder="Password"
|
|
74
|
+
value={password}
|
|
75
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
76
|
+
required
|
|
77
|
+
style={{ padding: '0.5rem', fontSize: '1rem' }}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
{error && <p style={{ color: 'red' }}>{error}</p>}
|
|
81
|
+
|
|
82
|
+
<button
|
|
83
|
+
type="submit"
|
|
84
|
+
disabled={loading}
|
|
85
|
+
style={{ padding: '0.5rem 1rem', fontSize: '1rem', cursor: 'pointer' }}
|
|
86
|
+
>
|
|
87
|
+
{loading ? 'Loading...' : isSignUp ? 'Create Account' : 'Sign In'}
|
|
88
|
+
</button>
|
|
89
|
+
</form>
|
|
90
|
+
|
|
91
|
+
<p style={{ marginTop: '1rem' }}>
|
|
92
|
+
{isSignUp ? 'Already have an account? ' : "Don't 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
|
+
>
|
|
103
|
+
{isSignUp ? 'Sign In' : 'Create Account'}
|
|
104
|
+
</button>
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { auth } from '@__APP_NAME_KEBAB__/auth'
|
|
2
|
+
import { createAuthClient } from 'better-auth/react'
|
|
3
|
+
import { inferAdditionalFields } from 'better-auth/client/plugins'
|
|
4
|
+
|
|
5
|
+
export function createAuthClientService() {
|
|
6
|
+
return createAuthClient({
|
|
7
|
+
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000',
|
|
8
|
+
plugins: [inferAdditionalFields<typeof auth>()],
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type AuthClient = ReturnType<typeof createAuthClientService>
|