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.
Files changed (96) hide show
  1. package/dist/bin.js +1 -1
  2. package/dist/{create-project-DTm05G7D.js → create-project-CP-h4Ygi.js} +7 -5
  3. package/dist/index.js +1 -1
  4. package/package.json +3 -2
  5. package/templates/base/.claude/CLAUDE.md +44 -0
  6. package/templates/base/.claude/agents/daidalos.md +76 -0
  7. package/templates/base/.claude/agents/episteme.md +79 -0
  8. package/templates/base/.claude/agents/librarian.md +132 -0
  9. package/templates/base/.claude/agents/oracle.md +210 -0
  10. package/templates/base/.claude/commands/create-plan.md +159 -0
  11. package/templates/base/.claude/commands/file-beads.md +98 -0
  12. package/templates/base/.claude/commands/review-beads.md +161 -0
  13. package/templates/base/.claude/settings.json +11 -0
  14. package/templates/base/.claude/skills/kuckit/SKILL.md +436 -0
  15. package/templates/base/.claude/skills/kuckit/references/ARCHITECTURE.md +388 -0
  16. package/templates/base/.claude/skills/kuckit/references/CLI-COMMANDS.md +365 -0
  17. package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +581 -0
  18. package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +112 -0
  19. package/templates/base/.claude/skills/kuckit/references/PUBLISHING.md +231 -0
  20. package/templates/base/.env.example +13 -0
  21. package/templates/base/.github/workflows/ci.yml +28 -0
  22. package/templates/base/.husky/pre-commit +1 -0
  23. package/templates/base/.prettierignore +5 -0
  24. package/templates/base/.prettierrc +8 -0
  25. package/templates/base/AGENTS.md +351 -0
  26. package/templates/base/apps/server/.env.example +18 -0
  27. package/templates/base/apps/server/AGENTS.md +93 -0
  28. package/templates/base/apps/server/package.json +13 -2
  29. package/templates/base/apps/server/src/app.ts +20 -0
  30. package/templates/base/apps/server/src/auth.ts +10 -0
  31. package/templates/base/apps/server/src/config/modules.ts +22 -0
  32. package/templates/base/apps/server/src/container.ts +81 -0
  33. package/templates/base/apps/server/src/health.ts +27 -0
  34. package/templates/base/apps/server/src/middleware/container.ts +41 -0
  35. package/templates/base/apps/server/src/rpc-router-registry.ts +26 -0
  36. package/templates/base/apps/server/src/rpc.ts +31 -0
  37. package/templates/base/apps/server/src/server.ts +42 -14
  38. package/templates/base/apps/web/.env.example +4 -0
  39. package/templates/base/apps/web/AGENTS.md +127 -0
  40. package/templates/base/apps/web/index.html +1 -1
  41. package/templates/base/apps/web/package.json +15 -2
  42. package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +82 -0
  43. package/templates/base/apps/web/src/lib/kuckit-router.ts +42 -0
  44. package/templates/base/apps/web/src/main.tsx +26 -14
  45. package/templates/base/apps/web/src/modules.client.ts +4 -3
  46. package/templates/base/apps/web/src/providers/KuckitProvider.tsx +147 -0
  47. package/templates/base/apps/web/src/providers/ServicesProvider.tsx +47 -0
  48. package/templates/base/apps/web/src/routeTree.gen.ts +91 -0
  49. package/templates/base/apps/web/src/routes/$.tsx +14 -0
  50. package/templates/base/apps/web/src/routes/__root.tsx +31 -0
  51. package/templates/base/apps/web/src/routes/index.tsx +46 -0
  52. package/templates/base/apps/web/src/routes/login.tsx +108 -0
  53. package/templates/base/apps/web/src/services/auth-client.ts +12 -0
  54. package/templates/base/apps/web/src/services/index.ts +3 -0
  55. package/templates/base/apps/web/src/services/rpc.ts +29 -0
  56. package/templates/base/apps/web/src/services/types.ts +14 -0
  57. package/templates/base/apps/web/tsconfig.json +5 -1
  58. package/templates/base/apps/web/vite.config.ts +8 -1
  59. package/templates/base/docker-compose.yml +23 -0
  60. package/templates/base/eslint.config.js +18 -0
  61. package/templates/base/package.json +32 -2
  62. package/templates/base/packages/api/AGENTS.md +66 -0
  63. package/templates/base/packages/api/package.json +35 -0
  64. package/templates/base/packages/api/src/context.ts +48 -0
  65. package/templates/base/packages/api/src/index.ts +22 -0
  66. package/templates/base/packages/api/tsconfig.json +8 -0
  67. package/templates/base/packages/auth/AGENTS.md +61 -0
  68. package/templates/base/packages/auth/package.json +27 -0
  69. package/templates/base/packages/auth/src/index.ts +22 -0
  70. package/templates/base/packages/auth/tsconfig.json +8 -0
  71. package/templates/base/packages/db/AGENTS.md +74 -0
  72. package/templates/base/packages/db/drizzle.config.ts +19 -0
  73. package/templates/base/packages/db/package.json +36 -0
  74. package/templates/base/packages/db/src/connection.ts +40 -0
  75. package/templates/base/packages/db/src/index.ts +4 -0
  76. package/templates/base/packages/db/src/migrations/0000_init.sql +54 -0
  77. package/templates/base/packages/db/src/migrations/meta/_journal.json +13 -0
  78. package/templates/base/packages/db/src/schema/auth.ts +51 -0
  79. package/templates/base/packages/db/tsconfig.json +8 -0
  80. package/templates/base/packages/items-module/AGENTS.md +210 -0
  81. package/templates/base/packages/items-module/package.json +32 -0
  82. package/templates/base/packages/items-module/src/adapters/item.drizzle.ts +66 -0
  83. package/templates/base/packages/items-module/src/api/items.router.ts +47 -0
  84. package/templates/base/packages/items-module/src/client-module.ts +39 -0
  85. package/templates/base/packages/items-module/src/domain/item.entity.ts +36 -0
  86. package/templates/base/packages/items-module/src/index.ts +15 -0
  87. package/templates/base/packages/items-module/src/module.ts +53 -0
  88. package/templates/base/packages/items-module/src/ports/item.repository.ts +13 -0
  89. package/templates/base/packages/items-module/src/ui/ItemsPage.tsx +144 -0
  90. package/templates/base/packages/items-module/src/usecases/create-item.ts +25 -0
  91. package/templates/base/packages/items-module/src/usecases/delete-item.ts +18 -0
  92. package/templates/base/packages/items-module/src/usecases/get-item.ts +19 -0
  93. package/templates/base/packages/items-module/src/usecases/list-items.ts +21 -0
  94. package/templates/base/packages/items-module/tsconfig.json +9 -0
  95. package/templates/base/turbo.json +13 -1
  96. 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 { StrictMode } from 'react'
2
- import { createRoot } from 'react-dom/client'
3
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
4
- import { App } from './App.js'
5
-
6
- const queryClient = new QueryClient()
7
-
8
- createRoot(document.getElementById('root')!).render(
9
- <StrictMode>
10
- <QueryClientProvider client={queryClient}>
11
- <App />
12
- </QueryClientProvider>
13
- </StrictMode>
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
- // KUCKIT_MODULES_START
11
- // Client modules will be added here
12
- // KUCKIT_MODULES_END
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>
@@ -0,0 +1,3 @@
1
+ export * from './types'
2
+ export * from './rpc'
3
+ export * from './auth-client'