kofi-stack-template-generator 2.1.17 → 2.1.18
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/index.js
CHANGED
|
@@ -249,7 +249,7 @@ var EMBEDDED_TEMPLATES = {
|
|
|
249
249
|
"convex/convex/convex.config.ts.hbs": "import { defineApp } from 'convex/server'\nimport betterAuth from '@convex-dev/better-auth/convex.config'\n\nconst app = defineApp()\napp.use(betterAuth)\n\nexport default app\n",
|
|
250
250
|
"convex/convex/http.ts.hbs": "import { httpRouter } from 'convex/server'\nimport { authComponent, createAuth } from './auth'\n\nconst http = httpRouter()\n\n// Register Better Auth routes\nauthComponent.registerRoutes(http, createAuth)\n\nexport default http\n",
|
|
251
251
|
"convex/convex/schema.ts.hbs": "import { defineSchema, defineTable } from 'convex/server'\nimport { v } from 'convex/values'\n\n// Better Auth manages its own tables via the betterAuth component\n// Add your custom application tables here\nexport default defineSchema({\n // Example:\n // posts: defineTable({\n // title: v.string(),\n // content: v.string(),\n // userId: v.string(), // Better Auth user ID\n // createdAt: v.number(),\n // }).index('by_user', ['userId']),\n})\n",
|
|
252
|
-
"convex/convex/users.ts.hbs": "import { query } from './_generated/server'\nimport { authComponent } from './auth'\n\n// Get current user from Better Auth session\nexport const current = query({\n args: {},\n handler: async (ctx) => {\n return authComponent.getAuthUser(ctx)\n },\n})\n\n// Alias for current user - used by dashboard components\nexport const viewer = query({\n args: {},\n handler: async (ctx) => {\n return authComponent.getAuthUser(ctx)\n },\n})\n",
|
|
252
|
+
"convex/convex/users.ts.hbs": "import { query } from './_generated/server'\nimport { authComponent } from './auth'\n\n// Get current user from Better Auth session\n// Returns null if not authenticated (instead of throwing)\nexport const current = query({\n args: {},\n handler: async (ctx) => {\n try {\n return await authComponent.getAuthUser(ctx)\n } catch {\n return null\n }\n },\n})\n\n// Alias for current user - used by dashboard components\n// Returns null if not authenticated (instead of throwing)\nexport const viewer = query({\n args: {},\n handler: async (ctx) => {\n try {\n return await authComponent.getAuthUser(ctx)\n } catch {\n return null\n }\n },\n})\n",
|
|
253
253
|
"convex/package.json.hbs": `{{#if (eq structure 'monorepo')}}{
|
|
254
254
|
"name": "@repo/backend",
|
|
255
255
|
"version": "0.1.0",
|
|
@@ -309,6 +309,47 @@ var EMBEDDED_TEMPLATES = {
|
|
|
309
309
|
"marketing/nextjs/src/app/page.tsx.hbs": `export default function HomePage() {
|
|
310
310
|
return (
|
|
311
311
|
<main className="min-h-screen">
|
|
312
|
+
{/* Navigation */}
|
|
313
|
+
<nav className="sticky top-0 z-50 w-full border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm">
|
|
314
|
+
<div className="container mx-auto px-4">
|
|
315
|
+
<div className="flex h-16 items-center justify-between">
|
|
316
|
+
<div className="flex items-center gap-2">
|
|
317
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gray-900 dark:bg-white">
|
|
318
|
+
<svg className="h-4 w-4 text-white dark:text-gray-900" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
319
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
320
|
+
</svg>
|
|
321
|
+
</div>
|
|
322
|
+
<span className="font-semibold text-gray-900 dark:text-white">{{projectName}}</span>
|
|
323
|
+
</div>
|
|
324
|
+
<div className="hidden md:flex items-center gap-6">
|
|
325
|
+
<a href="#features" className="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors">
|
|
326
|
+
Features
|
|
327
|
+
</a>
|
|
328
|
+
<a href="#" className="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors">
|
|
329
|
+
Pricing
|
|
330
|
+
</a>
|
|
331
|
+
<a href="#" className="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors">
|
|
332
|
+
Docs
|
|
333
|
+
</a>
|
|
334
|
+
</div>
|
|
335
|
+
<div className="flex items-center gap-4">
|
|
336
|
+
<a
|
|
337
|
+
href="/sign-in"
|
|
338
|
+
className="text-sm font-medium text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors"
|
|
339
|
+
>
|
|
340
|
+
Sign in
|
|
341
|
+
</a>
|
|
342
|
+
<a
|
|
343
|
+
href="/sign-up"
|
|
344
|
+
className="rounded-lg bg-gray-900 dark:bg-white px-4 py-2 text-sm font-medium text-white dark:text-gray-900 hover:opacity-90 transition-opacity"
|
|
345
|
+
>
|
|
346
|
+
Get Started
|
|
347
|
+
</a>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
</nav>
|
|
352
|
+
|
|
312
353
|
{/* Hero Section */}
|
|
313
354
|
<section className="relative overflow-hidden bg-gradient-to-b from-gray-50 to-white dark:from-gray-900 dark:to-gray-800">
|
|
314
355
|
<div className="container mx-auto px-4 py-24 sm:py-32">
|
|
@@ -1143,10 +1184,10 @@ export function AppSidebar() {
|
|
|
1143
1184
|
<SidebarMenuItem>
|
|
1144
1185
|
<SidebarMenuButton size="lg" asChild tooltip="{{projectName}}">
|
|
1145
1186
|
<a href="/">
|
|
1146
|
-
<div className="flex aspect-square size-8 items-center justify-center rounded-lg
|
|
1187
|
+
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
|
1147
1188
|
<GalleryVerticalEnd className="size-4" />
|
|
1148
1189
|
</div>
|
|
1149
|
-
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
1190
|
+
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
|
|
1150
1191
|
<span className="truncate font-semibold">{{projectName}}</span>
|
|
1151
1192
|
<span className="truncate text-xs">Dashboard</span>
|
|
1152
1193
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Auto-generated file. Do not edit manually.
|
|
2
2
|
// Run 'pnpm prebuild' to regenerate.
|
|
3
|
-
// Generated: 2026-01-15T02:
|
|
3
|
+
// Generated: 2026-01-15T02:23:02.427Z
|
|
4
4
|
// Template count: 90
|
|
5
5
|
|
|
6
6
|
export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
@@ -12,7 +12,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
|
12
12
|
"convex/convex/convex.config.ts.hbs": "import { defineApp } from 'convex/server'\nimport betterAuth from '@convex-dev/better-auth/convex.config'\n\nconst app = defineApp()\napp.use(betterAuth)\n\nexport default app\n",
|
|
13
13
|
"convex/convex/http.ts.hbs": "import { httpRouter } from 'convex/server'\nimport { authComponent, createAuth } from './auth'\n\nconst http = httpRouter()\n\n// Register Better Auth routes\nauthComponent.registerRoutes(http, createAuth)\n\nexport default http\n",
|
|
14
14
|
"convex/convex/schema.ts.hbs": "import { defineSchema, defineTable } from 'convex/server'\nimport { v } from 'convex/values'\n\n// Better Auth manages its own tables via the betterAuth component\n// Add your custom application tables here\nexport default defineSchema({\n // Example:\n // posts: defineTable({\n // title: v.string(),\n // content: v.string(),\n // userId: v.string(), // Better Auth user ID\n // createdAt: v.number(),\n // }).index('by_user', ['userId']),\n})\n",
|
|
15
|
-
"convex/convex/users.ts.hbs": "import { query } from './_generated/server'\nimport { authComponent } from './auth'\n\n// Get current user from Better Auth session\nexport const current = query({\n args: {},\n handler: async (ctx) => {\n return authComponent.getAuthUser(ctx)\n },\n})\n\n// Alias for current user - used by dashboard components\nexport const viewer = query({\n args: {},\n handler: async (ctx) => {\n return authComponent.getAuthUser(ctx)\n },\n})\n",
|
|
15
|
+
"convex/convex/users.ts.hbs": "import { query } from './_generated/server'\nimport { authComponent } from './auth'\n\n// Get current user from Better Auth session\n// Returns null if not authenticated (instead of throwing)\nexport const current = query({\n args: {},\n handler: async (ctx) => {\n try {\n return await authComponent.getAuthUser(ctx)\n } catch {\n return null\n }\n },\n})\n\n// Alias for current user - used by dashboard components\n// Returns null if not authenticated (instead of throwing)\nexport const viewer = query({\n args: {},\n handler: async (ctx) => {\n try {\n return await authComponent.getAuthUser(ctx)\n } catch {\n return null\n }\n },\n})\n",
|
|
16
16
|
"convex/package.json.hbs": "{{#if (eq structure 'monorepo')}}{\n \"name\": \"@repo/backend\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"main\": \"./convex/_generated/api.js\",\n \"types\": \"./convex/_generated/api.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": \"./convex/_generated/api.js\",\n \"types\": \"./convex/_generated/api.d.ts\"\n }\n },\n \"scripts\": {\n \"dev\": \"convex dev\",\n \"dev:setup\": \"convex dev --configure --until-success\",\n \"deploy\": \"convex deploy\"\n },\n \"dependencies\": {\n \"convex\": \"^1.25.0\",\n \"@convex-dev/better-auth\": \"^0.10.0\",\n \"better-auth\": \"1.4.9\",\n \"@convex-dev/resend\": \"^0.2.0\"{{#if (eq integrations.uploads 'convex-fs')}},\n \"convex-fs\": \"^0.2.0\"{{/if}}{{#if (eq integrations.uploads 'r2')}},\n \"@convex-dev/r2\": \"^0.8.0\"{{/if}}{{#if (eq integrations.payments 'stripe')}},\n \"@convex-dev/stripe\": \"^0.1.0\"{{/if}}{{#if (eq integrations.payments 'polar')}},\n \"@convex-dev/polar\": \"^0.7.0\"{{/if}}{{#if (includes addons 'rate-limiting')}},\n \"@convex-dev/rate-limiter\": \"^0.3.0\"{{/if}}\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.0.0\"\n }\n}{{/if}}\n",
|
|
17
17
|
"convex/tsconfig.json.hbs": "{{#if (eq structure 'monorepo')}}{\n \"compilerOptions\": {\n \"target\": \"ESNext\",\n \"module\": \"ESNext\",\n \"moduleResolution\": \"bundler\",\n \"strict\": true,\n \"esModuleInterop\": true,\n \"skipLibCheck\": true,\n \"noEmit\": true,\n \"outDir\": \"dist\"\n },\n \"include\": [\"convex/**/*.ts\"],\n \"exclude\": [\"node_modules\"]\n}{{/if}}\n",
|
|
18
18
|
"integrations/posthog/src/components/providers/posthog-provider.tsx.hbs": "'use client'\n\nimport posthog from 'posthog-js'\nimport { PostHogProvider as PHProvider } from 'posthog-js/react'\nimport { useEffect } from 'react'\n\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n useEffect(() => {\n posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {\n api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',\n person_profiles: 'identified_only',\n capture_pageview: false, // We capture pageviews manually\n })\n }, [])\n\n return <PHProvider client={posthog}>{children}</PHProvider>\n}\n",
|
|
@@ -21,7 +21,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
|
21
21
|
"marketing/nextjs/postcss.config.mjs.hbs": "export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n}\n",
|
|
22
22
|
"marketing/nextjs/src/app/globals.css.hbs": "@import \"tailwindcss\";\n\n:root {\n --background: #ffffff;\n --foreground: #171717;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --background: #0a0a0a;\n --foreground: #ededed;\n }\n}\n\nbody {\n color: var(--foreground);\n background: var(--background);\n}\n",
|
|
23
23
|
"marketing/nextjs/src/app/layout.tsx.hbs": "import type { Metadata } from 'next'\nimport { Geist, Geist_Mono } from 'next/font/google'\nimport './globals.css'\n\nconst geistSans = Geist({\n variable: '--font-geist-sans',\n subsets: ['latin'],\n})\n\nconst geistMono = Geist_Mono({\n variable: '--font-geist-mono',\n subsets: ['latin'],\n})\n\nexport const metadata: Metadata = {\n title: '{{projectName}} - Marketing',\n description: 'Marketing site for {{projectName}}',\n}\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <html lang=\"en\">\n <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>\n {children}\n </body>\n </html>\n )\n}\n",
|
|
24
|
-
"marketing/nextjs/src/app/page.tsx.hbs": "export default function HomePage() {\n return (\n <main className=\"min-h-screen\">\n {/* Hero Section */}\n <section className=\"relative overflow-hidden bg-gradient-to-b from-gray-50 to-white dark:from-gray-900 dark:to-gray-800\">\n <div className=\"container mx-auto px-4 py-24 sm:py-32\">\n <div className=\"text-center\">\n <h1 className=\"text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-6xl\">\n Welcome to {{projectName}}\n </h1>\n <p className=\"mt-6 text-lg leading-8 text-gray-600 dark:text-gray-300 max-w-2xl mx-auto\">\n A modern full-stack application built with Next.js, Convex, and Better-Auth.\n </p>\n <div className=\"mt-10 flex items-center justify-center gap-x-6\">\n <a\n href=\"/app\"\n className=\"rounded-lg bg-gray-900 dark:bg-white px-6 py-3 text-sm font-semibold text-white dark:text-gray-900 shadow-sm hover:opacity-90 transition-opacity\"\n >\n Get Started\n </a>\n <a\n href=\"#features\"\n className=\"text-sm font-semibold leading-6 text-gray-900 dark:text-white\"\n >\n Learn more <span aria-hidden=\"true\">→</span>\n </a>\n </div>\n </div>\n </div>\n </section>\n\n {/* Features Section */}\n <section id=\"features\" className=\"py-24 sm:py-32\">\n <div className=\"container mx-auto px-4\">\n <div className=\"text-center mb-16\">\n <h2 className=\"text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl\">\n Everything you need\n </h2>\n <p className=\"mt-4 text-lg text-gray-600 dark:text-gray-300\">\n Built with the best tools for modern web development\n </p>\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8\">\n <div className=\"p-6 rounded-xl border border-gray-200 dark:border-gray-700\">\n <div className=\"w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center mb-4\">\n <svg className=\"w-6 h-6 text-gray-900 dark:text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-semibold text-gray-900 dark:text-white mb-2\">\n Lightning Fast\n </h3>\n <p className=\"text-gray-600 dark:text-gray-300\">\n Built on Next.js with Turbopack for instant hot reload and optimized production builds.\n </p>\n </div>\n <div className=\"p-6 rounded-xl border border-gray-200 dark:border-gray-700\">\n <div className=\"w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center mb-4\">\n <svg className=\"w-6 h-6 text-gray-900 dark:text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-semibold text-gray-900 dark:text-white mb-2\">\n Real-time Database\n </h3>\n <p className=\"text-gray-600 dark:text-gray-300\">\n Powered by Convex for automatic real-time sync and type-safe queries.\n </p>\n </div>\n <div className=\"p-6 rounded-xl border border-gray-200 dark:border-gray-700\">\n <div className=\"w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center mb-4\">\n <svg className=\"w-6 h-6 text-gray-900 dark:text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-semibold text-gray-900 dark:text-white mb-2\">\n Secure Auth\n </h3>\n <p className=\"text-gray-600 dark:text-gray-300\">\n Better-Auth provides secure, flexible authentication with social login support.\n </p>\n </div>\n </div>\n </div>\n </section>\n\n {/* CTA Section */}\n <section className=\"py-24 sm:py-32 bg-gray-900 dark:bg-gray-800\">\n <div className=\"container mx-auto px-4 text-center\">\n <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n Ready to get started?\n </h2>\n <p className=\"mt-4 text-lg text-gray-300 max-w-2xl mx-auto\">\n Start building your next project with our full-stack template.\n </p>\n <div className=\"mt-10\">\n <a\n href=\"/app\"\n className=\"rounded-lg bg-white px-6 py-3 text-sm font-semibold text-gray-900 shadow-sm hover:opacity-90 transition-opacity\"\n >\n Launch App\n </a>\n </div>\n </div>\n </section>\n\n {/* Footer */}\n <footer className=\"border-t border-gray-200 dark:border-gray-700\">\n <div className=\"container mx-auto px-4 py-12\">\n <div className=\"text-center text-gray-600 dark:text-gray-300\">\n <p>\n Built with{' '}\n <a\n href=\"https://github.com/theodenanyoh11/create-kofi-stack\"\n className=\"text-gray-900 dark:text-white hover:underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n create-kofi-stack\n </a>\n </p>\n </div>\n </div>\n </footer>\n </main>\n )\n}\n",
|
|
24
|
+
"marketing/nextjs/src/app/page.tsx.hbs": "export default function HomePage() {\n return (\n <main className=\"min-h-screen\">\n {/* Navigation */}\n <nav className=\"sticky top-0 z-50 w-full border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm\">\n <div className=\"container mx-auto px-4\">\n <div className=\"flex h-16 items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-lg bg-gray-900 dark:bg-white\">\n <svg className=\"h-4 w-4 text-white dark:text-gray-900\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n </div>\n <span className=\"font-semibold text-gray-900 dark:text-white\">{{projectName}}</span>\n </div>\n <div className=\"hidden md:flex items-center gap-6\">\n <a href=\"#features\" className=\"text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors\">\n Features\n </a>\n <a href=\"#\" className=\"text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors\">\n Pricing\n </a>\n <a href=\"#\" className=\"text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors\">\n Docs\n </a>\n </div>\n <div className=\"flex items-center gap-4\">\n <a\n href=\"/sign-in\"\n className=\"text-sm font-medium text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors\"\n >\n Sign in\n </a>\n <a\n href=\"/sign-up\"\n className=\"rounded-lg bg-gray-900 dark:bg-white px-4 py-2 text-sm font-medium text-white dark:text-gray-900 hover:opacity-90 transition-opacity\"\n >\n Get Started\n </a>\n </div>\n </div>\n </div>\n </nav>\n\n {/* Hero Section */}\n <section className=\"relative overflow-hidden bg-gradient-to-b from-gray-50 to-white dark:from-gray-900 dark:to-gray-800\">\n <div className=\"container mx-auto px-4 py-24 sm:py-32\">\n <div className=\"text-center\">\n <h1 className=\"text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-6xl\">\n Welcome to {{projectName}}\n </h1>\n <p className=\"mt-6 text-lg leading-8 text-gray-600 dark:text-gray-300 max-w-2xl mx-auto\">\n A modern full-stack application built with Next.js, Convex, and Better-Auth.\n </p>\n <div className=\"mt-10 flex items-center justify-center gap-x-6\">\n <a\n href=\"/app\"\n className=\"rounded-lg bg-gray-900 dark:bg-white px-6 py-3 text-sm font-semibold text-white dark:text-gray-900 shadow-sm hover:opacity-90 transition-opacity\"\n >\n Get Started\n </a>\n <a\n href=\"#features\"\n className=\"text-sm font-semibold leading-6 text-gray-900 dark:text-white\"\n >\n Learn more <span aria-hidden=\"true\">→</span>\n </a>\n </div>\n </div>\n </div>\n </section>\n\n {/* Features Section */}\n <section id=\"features\" className=\"py-24 sm:py-32\">\n <div className=\"container mx-auto px-4\">\n <div className=\"text-center mb-16\">\n <h2 className=\"text-3xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-4xl\">\n Everything you need\n </h2>\n <p className=\"mt-4 text-lg text-gray-600 dark:text-gray-300\">\n Built with the best tools for modern web development\n </p>\n </div>\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-8\">\n <div className=\"p-6 rounded-xl border border-gray-200 dark:border-gray-700\">\n <div className=\"w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center mb-4\">\n <svg className=\"w-6 h-6 text-gray-900 dark:text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 10V3L4 14h7v7l9-11h-7z\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-semibold text-gray-900 dark:text-white mb-2\">\n Lightning Fast\n </h3>\n <p className=\"text-gray-600 dark:text-gray-300\">\n Built on Next.js with Turbopack for instant hot reload and optimized production builds.\n </p>\n </div>\n <div className=\"p-6 rounded-xl border border-gray-200 dark:border-gray-700\">\n <div className=\"w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center mb-4\">\n <svg className=\"w-6 h-6 text-gray-900 dark:text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-semibold text-gray-900 dark:text-white mb-2\">\n Real-time Database\n </h3>\n <p className=\"text-gray-600 dark:text-gray-300\">\n Powered by Convex for automatic real-time sync and type-safe queries.\n </p>\n </div>\n <div className=\"p-6 rounded-xl border border-gray-200 dark:border-gray-700\">\n <div className=\"w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-lg flex items-center justify-center mb-4\">\n <svg className=\"w-6 h-6 text-gray-900 dark:text-white\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\" />\n </svg>\n </div>\n <h3 className=\"text-lg font-semibold text-gray-900 dark:text-white mb-2\">\n Secure Auth\n </h3>\n <p className=\"text-gray-600 dark:text-gray-300\">\n Better-Auth provides secure, flexible authentication with social login support.\n </p>\n </div>\n </div>\n </div>\n </section>\n\n {/* CTA Section */}\n <section className=\"py-24 sm:py-32 bg-gray-900 dark:bg-gray-800\">\n <div className=\"container mx-auto px-4 text-center\">\n <h2 className=\"text-3xl font-bold tracking-tight text-white sm:text-4xl\">\n Ready to get started?\n </h2>\n <p className=\"mt-4 text-lg text-gray-300 max-w-2xl mx-auto\">\n Start building your next project with our full-stack template.\n </p>\n <div className=\"mt-10\">\n <a\n href=\"/app\"\n className=\"rounded-lg bg-white px-6 py-3 text-sm font-semibold text-gray-900 shadow-sm hover:opacity-90 transition-opacity\"\n >\n Launch App\n </a>\n </div>\n </div>\n </section>\n\n {/* Footer */}\n <footer className=\"border-t border-gray-200 dark:border-gray-700\">\n <div className=\"container mx-auto px-4 py-12\">\n <div className=\"text-center text-gray-600 dark:text-gray-300\">\n <p>\n Built with{' '}\n <a\n href=\"https://github.com/theodenanyoh11/create-kofi-stack\"\n className=\"text-gray-900 dark:text-white hover:underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n create-kofi-stack\n </a>\n </p>\n </div>\n </div>\n </footer>\n </main>\n )\n}\n",
|
|
25
25
|
"marketing/nextjs/tsconfig.json.hbs": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"react-jsx\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"paths\": {\n \"@/*\": [\"./src/*\"]\n }\n },\n \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\", \".next/dev/types/**/*.ts\"],\n \"exclude\": [\"node_modules\"]\n}\n",
|
|
26
26
|
"marketing/payload/_env.example.hbs": "# Database (Supabase PostgreSQL)\nDATABASE_URL=\"postgresql://postgres:[PASSWORD]@db.[PROJECT].supabase.co:5432/postgres\"\n\n# Payload CMS\nPAYLOAD_SECRET=\"\" # Generate with: openssl rand -base64 32\n\n# Scheduled Jobs\nCRON_SECRET=\"\" # Generate with: openssl rand -base64 32\n\n# Draft Previews\nPREVIEW_SECRET=\"\" # Generate with: openssl rand -base64 32\n\n# S3 Storage (Supabase Storage)\nS3_BUCKET=\"media\"\nS3_ACCESS_KEY_ID=\"\"\nS3_SECRET_ACCESS_KEY=\"\"\nS3_REGION=\"auto\"\nS3_ENDPOINT=\"https://[PROJECT].supabase.co/storage/v1/s3\"\n",
|
|
27
27
|
"marketing/payload/_env.local.hbs": "# Database (Supabase PostgreSQL)\nDATABASE_URL=\n\n# Payload CMS\nPAYLOAD_SECRET=\n\n# Scheduled Jobs\nCRON_SECRET=\n\n# Draft Previews\nPREVIEW_SECRET=\n\n# S3 Storage (Supabase Storage)\nS3_BUCKET=\"media\"\nS3_ACCESS_KEY_ID=\nS3_SECRET_ACCESS_KEY=\nS3_REGION=\"auto\"\nS3_ENDPOINT=\n",
|
|
@@ -86,7 +86,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
|
86
86
|
"web/src/app/page.tsx.hbs": "'use client'\n\nimport { useQuery } from 'convex/react'\nimport { api } from '{{#if (eq structure 'monorepo')}}@repo/backend{{else}}../convex/_generated/api{{/if}}'\nimport { DashboardLayout } from '@/components/dashboard/dashboard-layout'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Skeleton } from '@/components/ui/skeleton'\n\nexport default function HomePage() {\n const user = useQuery(api.users.viewer)\n\n if (user === undefined) {\n return (\n <DashboardLayout title=\"Dashboard\">\n <div className=\"space-y-6\">\n <Skeleton className=\"h-8 w-64\" />\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n </DashboardLayout>\n )\n }\n\n return (\n <DashboardLayout title=\"Dashboard\">\n <div className=\"space-y-6\">\n <div>\n <h1 className=\"text-3xl font-bold tracking-tight\">\n Welcome back{user?.name ? `, ${user.name}` : ''}!\n </h1>\n <p className=\"text-muted-foreground\">\n Here's what's happening with your project today.\n </p>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n <Card>\n <CardHeader>\n <CardTitle>Getting Started</CardTitle>\n <CardDescription>Quick start guide for your app</CardDescription>\n </CardHeader>\n <CardContent>\n <ul className=\"list-disc list-inside space-y-1 text-sm text-muted-foreground\">\n <li>Customize your dashboard layout</li>\n <li>Add new pages to the sidebar</li>\n <li>Connect your data sources</li>\n </ul>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Documentation</CardTitle>\n <CardDescription>Learn how to build with Kofi Stack</CardDescription>\n </CardHeader>\n <CardContent>\n <ul className=\"list-disc list-inside space-y-1 text-sm text-muted-foreground\">\n <li>\n <a\n href=\"https://docs.convex.dev\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:underline\"\n >\n Convex Documentation\n </a>\n </li>\n <li>\n <a\n href=\"https://ui.shadcn.com\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:underline\"\n >\n shadcn/ui Components\n </a>\n </li>\n <li>\n <a\n href=\"https://nextjs.org/docs\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:underline\"\n >\n Next.js Documentation\n </a>\n </li>\n </ul>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Your Stack</CardTitle>\n <CardDescription>Technologies powering your app</CardDescription>\n </CardHeader>\n <CardContent>\n <ul className=\"list-disc list-inside space-y-1 text-sm text-muted-foreground\">\n <li>Next.js 15 with App Router</li>\n <li>Convex for backend & database</li>\n <li>Convex Auth for authentication</li>\n <li>shadcn/ui components</li>\n <li>Tailwind CSS for styling</li>\n </ul>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"pt-4 border-t\">\n <p className=\"text-sm text-muted-foreground\">\n Created with{' '}\n <a\n href=\"https://github.com/theodenanyoh11/create-kofi-stack\"\n className=\"text-primary hover:underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n create-kofi-stack\n </a>\n </p>\n </div>\n </div>\n </DashboardLayout>\n )\n}\n",
|
|
87
87
|
"web/src/components/auth/sign-in-form.tsx.hbs": "'use client'\n\nimport { useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport Link from 'next/link'\nimport { signIn } from '@/lib/auth'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Separator } from '@/components/ui/separator'\n\nexport function SignInForm() {\n const router = useRouter()\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n try {\n const result = await signIn.email({\n email,\n password,\n })\n\n if (result.error) {\n setError(result.error.message || 'Invalid email or password')\n } else {\n router.push('/')\n }\n } catch (err) {\n setError('Invalid email or password')\n } finally {\n setIsLoading(false)\n }\n }\n\n const handleSocialSignIn = async (provider: 'github' | 'google') => {\n await signIn.social({\n provider,\n callbackURL: '/',\n })\n }\n\n return (\n <Card>\n <CardHeader className=\"text-center\">\n <CardTitle className=\"text-2xl\">Welcome back</CardTitle>\n <CardDescription>Sign in to your account to continue</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"you@example.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"password\">Password</Label>\n <Input\n id=\"password\"\n type=\"password\"\n placeholder=\"Enter your password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n required\n />\n </div>\n\n {error && (\n <p className=\"text-sm text-destructive\">{error}</p>\n )}\n\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Signing in...' : 'Sign In'}\n </Button>\n </form>\n\n <div className=\"relative my-6\">\n <div className=\"absolute inset-0 flex items-center\">\n <Separator className=\"w-full\" />\n </div>\n <div className=\"relative flex justify-center text-xs uppercase\">\n <span className=\"bg-card px-2 text-muted-foreground\">Or continue with</span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <Button variant=\"outline\" onClick={() => handleSocialSignIn('github')}>\n <svg className=\"mr-2 h-4 w-4\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z\"/>\n </svg>\n GitHub\n </Button>\n <Button variant=\"outline\" onClick={() => handleSocialSignIn('google')}>\n <svg className=\"mr-2 h-4 w-4\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"/>\n <path fill=\"currentColor\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"/>\n <path fill=\"currentColor\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"/>\n <path fill=\"currentColor\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"/>\n </svg>\n Google\n </Button>\n </div>\n\n <p className=\"mt-6 text-center text-sm text-muted-foreground\">\n Don't have an account?{' '}\n <Link href=\"/sign-up\" className=\"text-primary hover:underline font-medium\">\n Sign up\n </Link>\n </p>\n </CardContent>\n </Card>\n )\n}\n",
|
|
88
88
|
"web/src/components/auth/sign-up-form.tsx.hbs": "'use client'\n\nimport { useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport Link from 'next/link'\nimport { signUp, signIn } from '@/lib/auth'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Label } from '@/components/ui/label'\nimport { Separator } from '@/components/ui/separator'\n\nexport function SignUpForm() {\n const router = useRouter()\n const [name, setName] = useState('')\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault()\n setIsLoading(true)\n setError(null)\n\n if (password.length < 8) {\n setError('Password must be at least 8 characters')\n setIsLoading(false)\n return\n }\n\n try {\n const result = await signUp.email({\n email,\n password,\n name,\n })\n\n if (result.error) {\n setError(result.error.message || 'Failed to create account')\n } else {\n router.push('/')\n }\n } catch (err) {\n setError('Failed to create account. Email may already be in use.')\n } finally {\n setIsLoading(false)\n }\n }\n\n const handleSocialSignIn = async (provider: 'github' | 'google') => {\n await signIn.social({\n provider,\n callbackURL: '/',\n })\n }\n\n return (\n <Card>\n <CardHeader className=\"text-center\">\n <CardTitle className=\"text-2xl\">Create an account</CardTitle>\n <CardDescription>Enter your details to get started</CardDescription>\n </CardHeader>\n <CardContent>\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"name\">Name</Label>\n <Input\n id=\"name\"\n type=\"text\"\n placeholder=\"Your name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"email\">Email</Label>\n <Input\n id=\"email\"\n type=\"email\"\n placeholder=\"you@example.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n required\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"password\">Password</Label>\n <Input\n id=\"password\"\n type=\"password\"\n placeholder=\"At least 8 characters\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n required\n minLength={8}\n />\n </div>\n\n {error && (\n <p className=\"text-sm text-destructive\">{error}</p>\n )}\n\n <Button type=\"submit\" className=\"w-full\" disabled={isLoading}>\n {isLoading ? 'Creating account...' : 'Create Account'}\n </Button>\n </form>\n\n <div className=\"relative my-6\">\n <div className=\"absolute inset-0 flex items-center\">\n <Separator className=\"w-full\" />\n </div>\n <div className=\"relative flex justify-center text-xs uppercase\">\n <span className=\"bg-card px-2 text-muted-foreground\">Or continue with</span>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <Button variant=\"outline\" onClick={() => handleSocialSignIn('github')}>\n <svg className=\"mr-2 h-4 w-4\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z\"/>\n </svg>\n GitHub\n </Button>\n <Button variant=\"outline\" onClick={() => handleSocialSignIn('google')}>\n <svg className=\"mr-2 h-4 w-4\" viewBox=\"0 0 24 24\">\n <path fill=\"currentColor\" d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"/>\n <path fill=\"currentColor\" d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"/>\n <path fill=\"currentColor\" d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"/>\n <path fill=\"currentColor\" d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"/>\n </svg>\n Google\n </Button>\n </div>\n\n <p className=\"mt-6 text-center text-sm text-muted-foreground\">\n Already have an account?{' '}\n <Link href=\"/sign-in\" className=\"text-primary hover:underline font-medium\">\n Sign in\n </Link>\n </p>\n </CardContent>\n </Card>\n )\n}\n",
|
|
89
|
-
"web/src/components/dashboard/app-sidebar.tsx.hbs": "'use client'\n\nimport {\n GalleryVerticalEnd,\n Home,\n Settings,\n ChevronsUpDown,\n LogOut,\n User,\n} from 'lucide-react'\nimport { useRouter } from 'next/navigation'\nimport { signOut, useSession } from '@/lib/auth'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n useSidebar,\n} from '@/components/ui/sidebar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\n\nconst navigation = [\n {\n title: 'Home',\n url: '/',\n icon: Home,\n },\n {\n title: 'Settings',\n url: '/settings',\n icon: Settings,\n },\n]\n\nexport function AppSidebar() {\n const router = useRouter()\n const { data: session } = useSession()\n const { isMobile } = useSidebar()\n const user = session?.user\n\n const handleSignOut = async () => {\n await signOut()\n router.push('/sign-in')\n }\n\n const getInitials = (name?: string | null) => {\n if (!name) return 'U'\n return name\n .split(' ')\n .map((n) => n[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)\n }\n\n return (\n <Sidebar collapsible=\"icon\">\n <SidebarHeader>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton size=\"lg\" asChild tooltip=\"{{projectName}}\">\n <a href=\"/\">\n <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg
|
|
89
|
+
"web/src/components/dashboard/app-sidebar.tsx.hbs": "'use client'\n\nimport {\n GalleryVerticalEnd,\n Home,\n Settings,\n ChevronsUpDown,\n LogOut,\n User,\n} from 'lucide-react'\nimport { useRouter } from 'next/navigation'\nimport { signOut, useSession } from '@/lib/auth'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarRail,\n useSidebar,\n} from '@/components/ui/sidebar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\n\nconst navigation = [\n {\n title: 'Home',\n url: '/',\n icon: Home,\n },\n {\n title: 'Settings',\n url: '/settings',\n icon: Settings,\n },\n]\n\nexport function AppSidebar() {\n const router = useRouter()\n const { data: session } = useSession()\n const { isMobile } = useSidebar()\n const user = session?.user\n\n const handleSignOut = async () => {\n await signOut()\n router.push('/sign-in')\n }\n\n const getInitials = (name?: string | null) => {\n if (!name) return 'U'\n return name\n .split(' ')\n .map((n) => n[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)\n }\n\n return (\n <Sidebar collapsible=\"icon\">\n <SidebarHeader>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton size=\"lg\" asChild tooltip=\"{{projectName}}\">\n <a href=\"/\">\n <div className=\"bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg\">\n <GalleryVerticalEnd className=\"size-4\" />\n </div>\n <div className=\"grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden\">\n <span className=\"truncate font-semibold\">{{projectName}}</span>\n <span className=\"truncate text-xs\">Dashboard</span>\n </div>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarHeader>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Navigation</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {navigation.map((item) => (\n <SidebarMenuItem key={item.title}>\n <SidebarMenuButton asChild tooltip={item.title}>\n <a href={item.url}>\n <item.icon />\n <span>{item.title}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n <SidebarFooter>\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user?.image ?? undefined} alt={user?.name ?? 'User'} />\n <AvatarFallback className=\"rounded-lg\">\n {getInitials(user?.name)}\n </AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user?.name ?? 'User'}</span>\n <span className=\"truncate text-xs\">\n {user?.email ?? ''}\n </span>\n </div>\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg\"\n side={isMobile ? 'bottom' : 'right'}\n align=\"end\"\n sideOffset={4}\n >\n <DropdownMenuItem asChild>\n <a href=\"/settings\">\n <User />\n Profile\n </a>\n </DropdownMenuItem>\n <DropdownMenuItem asChild>\n <a href=\"/settings\">\n <Settings />\n Settings\n </a>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={handleSignOut}>\n <LogOut />\n Sign out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n",
|
|
90
90
|
"web/src/components/dashboard/dashboard-layout.tsx.hbs": "'use client'\n\nimport { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'\nimport { Separator } from '@/components/ui/separator'\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbList,\n BreadcrumbPage,\n} from '@/components/ui/breadcrumb'\nimport { AppSidebar } from './app-sidebar'\n\ninterface DashboardLayoutProps {\n children: React.ReactNode\n title?: string\n}\n\nexport function DashboardLayout({ children, title = 'Dashboard' }: DashboardLayoutProps) {\n return (\n <SidebarProvider>\n <AppSidebar />\n <SidebarInset>\n <header className=\"flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12\">\n <div className=\"flex items-center gap-2 px-4\">\n <SidebarTrigger className=\"-ml-1\" />\n <Separator orientation=\"vertical\" className=\"mr-2 data-[orientation=vertical]:h-4\" />\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbPage>{title}</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n </div>\n </header>\n <main className=\"flex flex-1 flex-col gap-4 p-4 pt-0\">{children}</main>\n </SidebarInset>\n </SidebarProvider>\n )\n}\n",
|
|
91
91
|
"web/src/components/providers/convex-provider.tsx.hbs": "'use client'\n\nimport { ConvexReactClient } from 'convex/react'\nimport { ConvexBetterAuthProvider } from '@convex-dev/better-auth/react'\nimport { authClient } from '@/lib/auth'\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!)\n\nexport function ConvexClientProvider({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n {children}\n </ConvexBetterAuthProvider>\n )\n}\n",
|
|
92
92
|
"web/src/lib/auth-server.ts.hbs": "import { convexBetterAuthNextJs } from '@convex-dev/better-auth/nextjs'\n\nexport const {\n handler,\n preloadAuthQuery,\n isAuthenticated,\n getToken,\n fetchAuthQuery,\n fetchAuthMutation,\n fetchAuthAction,\n} = convexBetterAuthNextJs({\n convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,\n convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL || process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',\n})\n",
|
|
@@ -2,17 +2,27 @@ import { query } from './_generated/server'
|
|
|
2
2
|
import { authComponent } from './auth'
|
|
3
3
|
|
|
4
4
|
// Get current user from Better Auth session
|
|
5
|
+
// Returns null if not authenticated (instead of throwing)
|
|
5
6
|
export const current = query({
|
|
6
7
|
args: {},
|
|
7
8
|
handler: async (ctx) => {
|
|
8
|
-
|
|
9
|
+
try {
|
|
10
|
+
return await authComponent.getAuthUser(ctx)
|
|
11
|
+
} catch {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
9
14
|
},
|
|
10
15
|
})
|
|
11
16
|
|
|
12
17
|
// Alias for current user - used by dashboard components
|
|
18
|
+
// Returns null if not authenticated (instead of throwing)
|
|
13
19
|
export const viewer = query({
|
|
14
20
|
args: {},
|
|
15
21
|
handler: async (ctx) => {
|
|
16
|
-
|
|
22
|
+
try {
|
|
23
|
+
return await authComponent.getAuthUser(ctx)
|
|
24
|
+
} catch {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
17
27
|
},
|
|
18
28
|
})
|
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
export default function HomePage() {
|
|
2
2
|
return (
|
|
3
3
|
<main className="min-h-screen">
|
|
4
|
+
{/* Navigation */}
|
|
5
|
+
<nav className="sticky top-0 z-50 w-full border-b border-gray-200 dark:border-gray-800 bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm">
|
|
6
|
+
<div className="container mx-auto px-4">
|
|
7
|
+
<div className="flex h-16 items-center justify-between">
|
|
8
|
+
<div className="flex items-center gap-2">
|
|
9
|
+
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-gray-900 dark:bg-white">
|
|
10
|
+
<svg className="h-4 w-4 text-white dark:text-gray-900" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
11
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
12
|
+
</svg>
|
|
13
|
+
</div>
|
|
14
|
+
<span className="font-semibold text-gray-900 dark:text-white">{{projectName}}</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div className="hidden md:flex items-center gap-6">
|
|
17
|
+
<a href="#features" className="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors">
|
|
18
|
+
Features
|
|
19
|
+
</a>
|
|
20
|
+
<a href="#" className="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors">
|
|
21
|
+
Pricing
|
|
22
|
+
</a>
|
|
23
|
+
<a href="#" className="text-sm text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors">
|
|
24
|
+
Docs
|
|
25
|
+
</a>
|
|
26
|
+
</div>
|
|
27
|
+
<div className="flex items-center gap-4">
|
|
28
|
+
<a
|
|
29
|
+
href="/sign-in"
|
|
30
|
+
className="text-sm font-medium text-gray-600 hover:text-gray-900 dark:text-gray-300 dark:hover:text-white transition-colors"
|
|
31
|
+
>
|
|
32
|
+
Sign in
|
|
33
|
+
</a>
|
|
34
|
+
<a
|
|
35
|
+
href="/sign-up"
|
|
36
|
+
className="rounded-lg bg-gray-900 dark:bg-white px-4 py-2 text-sm font-medium text-white dark:text-gray-900 hover:opacity-90 transition-opacity"
|
|
37
|
+
>
|
|
38
|
+
Get Started
|
|
39
|
+
</a>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</nav>
|
|
44
|
+
|
|
4
45
|
{/* Hero Section */}
|
|
5
46
|
<section className="relative overflow-hidden bg-gradient-to-b from-gray-50 to-white dark:from-gray-900 dark:to-gray-800">
|
|
6
47
|
<div className="container mx-auto px-4 py-24 sm:py-32">
|
|
@@ -74,10 +74,10 @@ export function AppSidebar() {
|
|
|
74
74
|
<SidebarMenuItem>
|
|
75
75
|
<SidebarMenuButton size="lg" asChild tooltip="{{projectName}}">
|
|
76
76
|
<a href="/">
|
|
77
|
-
<div className="flex aspect-square size-8 items-center justify-center rounded-lg
|
|
77
|
+
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
|
78
78
|
<GalleryVerticalEnd className="size-4" />
|
|
79
79
|
</div>
|
|
80
|
-
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
80
|
+
<div className="grid flex-1 text-left text-sm leading-tight group-data-[collapsible=icon]:hidden">
|
|
81
81
|
<span className="truncate font-semibold">{{projectName}}</span>
|
|
82
82
|
<span className="truncate text-xs">Dashboard</span>
|
|
83
83
|
</div>
|