create-githat-app 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -18
- package/dist/cli.js +1161 -114
- package/package.json +34 -9
- package/templates/agent/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/agent/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/agent/app/admin/agent/page.tsx.hbs +127 -0
- package/templates/agent/app/globals.css.hbs +87 -0
- package/templates/agent/app/layout.tsx.hbs +41 -0
- package/templates/agent/app/page.tsx.hbs +100 -0
- package/templates/agent/next.config.ts.hbs +8 -0
- package/templates/agent/postcss.config.mjs.hbs +14 -0
- package/templates/agent/proxy.ts.hbs +10 -0
- package/templates/agent/tsconfig.json.hbs +21 -0
- package/templates/base/.env.example.hbs +2 -2
- package/templates/base/.env.local.example.hbs +20 -0
- package/templates/base/.env.local.hbs +13 -2
- package/templates/base/.github/CODEOWNERS.hbs +1 -0
- package/templates/base/.github/SECURITY.md +10 -0
- package/templates/base/.github/dependabot.yml +19 -0
- package/templates/base/.github/workflows/ci.yml.hbs +77 -0
- package/templates/base/.github/workflows/githat-policy.yml +51 -0
- package/templates/base/.gitignore.hbs +17 -2
- package/templates/base/README.md.hbs +31 -52
- package/templates/classroom/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/classroom/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/classroom/app/globals.css.hbs +87 -0
- package/templates/classroom/app/layout.tsx.hbs +41 -0
- package/templates/classroom/app/page.tsx.hbs +103 -0
- package/templates/classroom/app/projects/[id]/feedback/page.tsx.hbs +159 -0
- package/templates/classroom/app/projects/[id]/present/page.tsx.hbs +113 -0
- package/templates/classroom/next.config.ts.hbs +8 -0
- package/templates/classroom/postcss.config.mjs.hbs +14 -0
- package/templates/classroom/proxy.ts.hbs +10 -0
- package/templates/classroom/tsconfig.json.hbs +21 -0
- package/templates/content/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/content/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/content/app/globals.css.hbs +87 -0
- package/templates/content/app/layout.tsx.hbs +41 -0
- package/templates/content/app/newsletter/page.tsx.hbs +90 -0
- package/templates/content/app/page.tsx.hbs +105 -0
- package/templates/content/app/posts/[slug]/page.tsx.hbs +119 -0
- package/templates/content/next.config.ts.hbs +8 -0
- package/templates/content/postcss.config.mjs.hbs +14 -0
- package/templates/content/proxy.ts.hbs +10 -0
- package/templates/content/tsconfig.json.hbs +21 -0
- package/templates/dashboard/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/dashboard/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/dashboard/app/admin/data/[entity]/page.tsx.hbs +68 -0
- package/templates/dashboard/app/admin/page.tsx.hbs +59 -0
- package/templates/dashboard/app/globals.css.hbs +87 -0
- package/templates/dashboard/app/layout.tsx.hbs +41 -0
- package/templates/dashboard/app/page.tsx.hbs +57 -0
- package/templates/dashboard/next.config.ts.hbs +8 -0
- package/templates/dashboard/postcss.config.mjs.hbs +14 -0
- package/templates/dashboard/proxy.ts.hbs +10 -0
- package/templates/dashboard/src/lib/db.ts.hbs +39 -0
- package/templates/dashboard/tsconfig.json.hbs +21 -0
- package/templates/fullstack/apps-api-express/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-express/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-express/package.json.hbs +24 -0
- package/templates/fullstack/apps-api-express/src/index.ts.hbs +41 -0
- package/templates/fullstack/apps-api-express/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-express/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-express/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-api-fastify/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-fastify/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-fastify/package.json.hbs +22 -0
- package/templates/fullstack/apps-api-fastify/src/index.ts.hbs +28 -0
- package/templates/fullstack/apps-api-fastify/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-fastify/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-fastify/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-api-hono/.env.example.hbs +6 -0
- package/templates/fullstack/apps-api-hono/.env.local.hbs +6 -0
- package/templates/fullstack/apps-api-hono/package.json.hbs +22 -0
- package/templates/fullstack/apps-api-hono/src/index.ts.hbs +35 -0
- package/templates/fullstack/apps-api-hono/src/routes/health.ts.hbs +11 -0
- package/templates/fullstack/apps-api-hono/src/routes/users.ts.hbs +43 -0
- package/templates/fullstack/apps-api-hono/tsconfig.json.hbs +16 -0
- package/templates/fullstack/apps-web-nextjs/.env.example.hbs +5 -0
- package/templates/fullstack/apps-web-nextjs/.env.local.hbs +5 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/forgot-password/page.tsx.hbs +11 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/reset-password/page.tsx.hbs +39 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/app/(auth)/verify-email/page.tsx.hbs +11 -0
- package/templates/fullstack/apps-web-nextjs/app/dashboard/layout.tsx.hbs +15 -0
- package/templates/fullstack/apps-web-nextjs/app/dashboard/page.tsx.hbs +27 -0
- package/templates/fullstack/apps-web-nextjs/app/globals.css.hbs +21 -0
- package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +30 -0
- package/templates/fullstack/apps-web-nextjs/app/page.tsx.hbs +17 -0
- package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +16 -0
- package/templates/fullstack/apps-web-nextjs/package.json.hbs +34 -0
- package/templates/fullstack/apps-web-nextjs/postcss.config.mjs.hbs +9 -0
- package/templates/fullstack/apps-web-nextjs/tsconfig.json.hbs +21 -0
- package/templates/fullstack/root/.gitignore.hbs +42 -0
- package/templates/fullstack/root/githat.yaml.hbs +17 -0
- package/templates/fullstack/root/package.json.hbs +15 -0
- package/templates/fullstack/root/turbo.json.hbs +20 -0
- package/templates/marketplace/CULTURE.md +74 -0
- package/templates/marketplace/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/marketplace/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/marketplace/app/(shop)/[slug]/p/[productId]/page.tsx.hbs +99 -0
- package/templates/marketplace/app/(shop)/[slug]/page.tsx.hbs +90 -0
- package/templates/marketplace/app/admin/page.tsx.hbs +95 -0
- package/templates/marketplace/app/cart/page.tsx.hbs +157 -0
- package/templates/marketplace/app/globals.css.hbs +87 -0
- package/templates/marketplace/app/layout.tsx.hbs +77 -0
- package/templates/marketplace/app/page.tsx.hbs +178 -0
- package/templates/marketplace/app/sell/page.tsx.hbs +78 -0
- package/templates/marketplace/next.config.ts.hbs +8 -0
- package/templates/marketplace/postcss.config.mjs.hbs +14 -0
- package/templates/marketplace/proxy.ts.hbs +10 -0
- package/templates/marketplace/src/lib/anon-session.ts.hbs +117 -0
- package/templates/marketplace/src/lib/categories.ts.hbs +35 -0
- package/templates/marketplace/tsconfig.json.hbs +21 -0
- package/templates/nextjs/.github/workflows/deploy.yml.hbs +107 -0
- package/templates/nextjs/app/(auth)/reset-password/page.tsx.hbs +106 -0
- package/templates/nextjs/app/globals.css.hbs +4 -3
- package/templates/nextjs/app/layout.tsx.hbs +5 -1
- package/templates/nextjs/app/page.tsx.hbs +3 -6
- package/templates/nextjs/next.config.ts.hbs +5 -2
- package/templates/nextjs/proxy.ts.hbs +1 -1
- package/templates/plain/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/plain/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/plain/app/globals.css.hbs +87 -0
- package/templates/plain/app/layout.tsx.hbs +41 -0
- package/templates/plain/app/page.tsx.hbs +123 -0
- package/templates/plain/next.config.ts.hbs +8 -0
- package/templates/plain/postcss.config.mjs.hbs +14 -0
- package/templates/plain/proxy.ts.hbs +10 -0
- package/templates/plain/tsconfig.json.hbs +21 -0
- package/templates/portfolio/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/portfolio/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/portfolio/app/globals.css.hbs +87 -0
- package/templates/portfolio/app/layout.tsx.hbs +41 -0
- package/templates/portfolio/app/page.tsx.hbs +86 -0
- package/templates/portfolio/next.config.ts.hbs +8 -0
- package/templates/portfolio/postcss.config.mjs.hbs +14 -0
- package/templates/portfolio/proxy.ts.hbs +10 -0
- package/templates/portfolio/tsconfig.json.hbs +21 -0
- package/templates/react-vite/src/App.tsx.hbs +11 -9
- package/templates/react-vite/src/index.css.hbs +4 -3
- package/templates/react-vite/src/pages/Home.tsx.hbs +3 -6
- package/templates/saas/app/(auth)/sign-in/page.tsx.hbs +9 -0
- package/templates/saas/app/(auth)/sign-up/page.tsx.hbs +9 -0
- package/templates/saas/app/admin/billing/page.tsx.hbs +145 -0
- package/templates/saas/app/admin/page.tsx.hbs +106 -0
- package/templates/saas/app/admin/team/page.tsx.hbs +134 -0
- package/templates/saas/app/globals.css.hbs +87 -0
- package/templates/saas/app/layout.tsx.hbs +41 -0
- package/templates/saas/app/page.tsx.hbs +108 -0
- package/templates/saas/app/pricing/page.tsx.hbs +131 -0
- package/templates/saas/next.config.ts.hbs +8 -0
- package/templates/saas/postcss.config.mjs.hbs +14 -0
- package/templates/saas/proxy.ts.hbs +10 -0
- package/templates/saas/tsconfig.json.hbs +21 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { use } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { useAuth } from '@githat/nextjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Post viewer with paywall.
|
|
9
|
+
*
|
|
10
|
+
* Three states a reader can be in:
|
|
11
|
+
* 1. Post is free → show full content
|
|
12
|
+
* 2. Post is gated + reader is signed in + has active subscription → show full
|
|
13
|
+
* 3. Post is gated + reader is anonymous OR not subscribed → show preview + CTA
|
|
14
|
+
*
|
|
15
|
+
* Subscription check is stubbed (`hasActiveSubscription` always
|
|
16
|
+
* false). Wire to your backend that watches Sebastn webhooks.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const SAMPLE_POSTS: Record<string, { title: string; gated: boolean; body: string }> = {
|
|
20
|
+
welcome: {
|
|
21
|
+
title: 'Welcome to {{businessName}}',
|
|
22
|
+
gated: false,
|
|
23
|
+
body: 'This is the first post. Free to read. The rest of this template ' +
|
|
24
|
+
'is a working content site — paywall, newsletter, author dashboard. ' +
|
|
25
|
+
'Replace the sample posts with content from your CMS, Markdown files, ' +
|
|
26
|
+
'or a Postgres table.',
|
|
27
|
+
},
|
|
28
|
+
'first-deep-dive': {
|
|
29
|
+
title: 'Why I started this',
|
|
30
|
+
gated: false,
|
|
31
|
+
body: 'The one thing nobody told me when I started writing this newsletter: ' +
|
|
32
|
+
'it takes the first ten posts before you find the voice. Free to read.',
|
|
33
|
+
},
|
|
34
|
+
'paid-essay-1': {
|
|
35
|
+
title: 'Paid essay: the long version',
|
|
36
|
+
gated: true,
|
|
37
|
+
body: 'This is the unfiltered version of the paid essay. It runs about 3,000 ' +
|
|
38
|
+
'words and gets into the parts I keep out of the free posts. Subscribe ' +
|
|
39
|
+
'to read the rest. Sebastn handles the checkout — we never touch your card.',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export default function PostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
44
|
+
const { slug } = use(params);
|
|
45
|
+
const post = SAMPLE_POSTS[slug];
|
|
46
|
+
const { isSignedIn } = useAuth();
|
|
47
|
+
|
|
48
|
+
// Stub: real apps fetch the user's subscription state from your
|
|
49
|
+
// backend (which watches Sebastn webhooks for the source of truth).
|
|
50
|
+
const hasActiveSubscription = false;
|
|
51
|
+
|
|
52
|
+
if (!post) {
|
|
53
|
+
return (
|
|
54
|
+
<div style=\{{ padding: 'var(--space-12)', textAlign: 'center' }}>
|
|
55
|
+
<p>Post not found.</p>
|
|
56
|
+
<Link href="/" style=\{{ color: 'var(--primary)' }}>← Home</Link>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const showPaywall = post.gated && (!isSignedIn || !hasActiveSubscription);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<article style=\{{ maxWidth: '40rem', margin: '0 auto', padding: 'var(--space-8) var(--space-4)' }}>
|
|
65
|
+
<Link href="/" style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem', display: 'inline-block', marginBottom: 'var(--space-4)' }}>
|
|
66
|
+
← All posts
|
|
67
|
+
</Link>
|
|
68
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2.25rem', lineHeight: 1.2, marginBottom: 'var(--space-4)' }}>
|
|
69
|
+
{post.title}
|
|
70
|
+
</h1>
|
|
71
|
+
|
|
72
|
+
<div style=\{{ color: 'var(--fg)', lineHeight: 1.8, fontSize: '1.0625rem' }}>
|
|
73
|
+
{showPaywall ? (
|
|
74
|
+
<>
|
|
75
|
+
<p style=\{{ marginBottom: 'var(--space-4)' }}>{post.body.slice(0, 200)}…</p>
|
|
76
|
+
<Paywall />
|
|
77
|
+
</>
|
|
78
|
+
) : (
|
|
79
|
+
<p>{post.body}</p>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
</article>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function Paywall() {
|
|
87
|
+
return (
|
|
88
|
+
<div style=\{{
|
|
89
|
+
marginTop: 'var(--space-8)',
|
|
90
|
+
padding: 'var(--space-6)',
|
|
91
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
92
|
+
border: '2px solid var(--accent)',
|
|
93
|
+
background: 'var(--surface-sub)',
|
|
94
|
+
textAlign: 'center',
|
|
95
|
+
}}>
|
|
96
|
+
<h3 style=\{{ fontFamily: 'var(--font-wordmark)', marginBottom: 'var(--space-2)' }}>
|
|
97
|
+
This post is for subscribers
|
|
98
|
+
</h3>
|
|
99
|
+
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-4)' }}>
|
|
100
|
+
Get access to every paid essay, plus the newsletter.
|
|
101
|
+
</p>
|
|
102
|
+
<button
|
|
103
|
+
onClick={() => { /* TODO: open Sebastn checkout */ }}
|
|
104
|
+
style=\{{
|
|
105
|
+
padding: 'var(--space-3) var(--space-6)',
|
|
106
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
107
|
+
border: 'none',
|
|
108
|
+
background: 'var(--primary)',
|
|
109
|
+
color: 'var(--bg)',
|
|
110
|
+
fontWeight: 600,
|
|
111
|
+
fontSize: '1rem',
|
|
112
|
+
cursor: 'pointer',
|
|
113
|
+
}}
|
|
114
|
+
>
|
|
115
|
+
Subscribe — $5/mo
|
|
116
|
+
</button>
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Plain template — Tailwind v4 PostCSS plugin is required even though
|
|
3
|
+
* the plain scaffold doesn't use Tailwind utility classes. The auth
|
|
4
|
+
* page CSS shipped by `@githat/nextjs/styles` is processed through
|
|
5
|
+
* @tailwindcss/postcss at build time. Drop this config and the
|
|
6
|
+
* auth pages render unstyled.
|
|
7
|
+
*/
|
|
8
|
+
const config = {
|
|
9
|
+
plugins: {
|
|
10
|
+
'@tailwindcss/postcss': {},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default config;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { authProxy } from '@githat/nextjs/proxy';
|
|
2
|
+
|
|
3
|
+
export const proxy = authProxy({
|
|
4
|
+
publicRoutes: ['/', '/sign-in', '/sign-up'{{#if includeForgotPassword}}, '/forgot-password', '/reset-password'{{/if}}{{#if includeEmailVerification}}, '/verify-email'{{/if}}],
|
|
5
|
+
signInUrl: '/sign-in',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const config = {
|
|
9
|
+
matcher: ['/((?!_next|api|.*\\..*).*)'],
|
|
10
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": { "@/*": ["./*"] }
|
|
18
|
+
},
|
|
19
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
20
|
+
"exclude": ["node_modules"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SignInForm } from '@githat/nextjs';
|
|
2
|
+
|
|
3
|
+
export default function SignInPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main {{#if useTailwind}}className="flex items-center justify-center min-h-screen bg-[#09090b]"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#09090b' }}{{/if}}>
|
|
6
|
+
<SignInForm signUpUrl="/sign-up" {{#if includeForgotPassword}}forgotPasswordUrl="/forgot-password"{{/if}} />
|
|
7
|
+
</main>
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { SignUpForm } from '@githat/nextjs';
|
|
2
|
+
|
|
3
|
+
export default function SignUpPage() {
|
|
4
|
+
return (
|
|
5
|
+
<main {{#if useTailwind}}className="flex items-center justify-center min-h-screen bg-[#09090b]"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#09090b' }}{{/if}}>
|
|
6
|
+
<SignUpForm signInUrl="/sign-in" />
|
|
7
|
+
</main>
|
|
8
|
+
);
|
|
9
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { listEntities } from '../../../../src/lib/db';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generic data-table view for any entity in your DB.
|
|
6
|
+
*
|
|
7
|
+
* Server component — runs `listEntities(entityName)` (which you
|
|
8
|
+
* implement in src/lib/db.ts) and renders rows as a basic table.
|
|
9
|
+
* Auth-gating is enforced by the middleware in proxy.ts; if you
|
|
10
|
+
* need entity-level RBAC, check `verifyToken` claims here.
|
|
11
|
+
*/
|
|
12
|
+
export default async function EntityPage({
|
|
13
|
+
params,
|
|
14
|
+
}: {
|
|
15
|
+
params: Promise<{ entity: string }>;
|
|
16
|
+
}) {
|
|
17
|
+
const { entity } = await params;
|
|
18
|
+
const rows = await listEntities(entity);
|
|
19
|
+
|
|
20
|
+
const columns = rows.length > 0 ? Object.keys(rows[0]) : ['id'];
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div style=\{{ padding: 'var(--space-8) var(--space-4)', maxWidth: '64rem', margin: '0 auto' }}>
|
|
24
|
+
<Link href="/admin" style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem', display: 'inline-block', marginBottom: 'var(--space-4)' }}>
|
|
25
|
+
← Admin
|
|
26
|
+
</Link>
|
|
27
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-2)' }}>
|
|
28
|
+
{entity}
|
|
29
|
+
</h1>
|
|
30
|
+
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-6)' }}>
|
|
31
|
+
{rows.length} row{rows.length === 1 ? '' : 's'}
|
|
32
|
+
</p>
|
|
33
|
+
|
|
34
|
+
<div style=\{{ overflowX: 'auto', borderRadius: 'var(--radius-md, 0.5rem)', border: '1px solid var(--border)' }}>
|
|
35
|
+
<table style=\{{ width: '100%', borderCollapse: 'collapse', background: 'var(--surface)' }}>
|
|
36
|
+
<thead>
|
|
37
|
+
<tr style=\{{ borderBottom: '1px solid var(--border)', background: 'var(--surface-sub)' }}>
|
|
38
|
+
{columns.map((c) => (
|
|
39
|
+
<th key={c} style=\{{
|
|
40
|
+
padding: 'var(--space-3) var(--space-4)',
|
|
41
|
+
textAlign: 'left',
|
|
42
|
+
fontSize: '0.75rem',
|
|
43
|
+
fontWeight: 600,
|
|
44
|
+
textTransform: 'uppercase',
|
|
45
|
+
letterSpacing: '0.05em',
|
|
46
|
+
color: 'var(--fg-muted)',
|
|
47
|
+
}}>
|
|
48
|
+
{c}
|
|
49
|
+
</th>
|
|
50
|
+
))}
|
|
51
|
+
</tr>
|
|
52
|
+
</thead>
|
|
53
|
+
<tbody>
|
|
54
|
+
{rows.map((row) => (
|
|
55
|
+
<tr key={String(row.id)} style=\{{ borderBottom: '1px solid var(--border)' }}>
|
|
56
|
+
{columns.map((c) => (
|
|
57
|
+
<td key={c} style=\{{ padding: 'var(--space-3) var(--space-4)', fontSize: '0.875rem' }}>
|
|
58
|
+
{String(row[c])}
|
|
59
|
+
</td>
|
|
60
|
+
))}
|
|
61
|
+
</tr>
|
|
62
|
+
))}
|
|
63
|
+
</tbody>
|
|
64
|
+
</table>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { useAuth } from '@githat/nextjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Auth-gated admin home — overview of every entity in your DB.
|
|
8
|
+
*
|
|
9
|
+
* The list is hardcoded for the starter. Real apps generate it
|
|
10
|
+
* from your schema (drizzle, prisma) or list it server-side via
|
|
11
|
+
* `listEntities()` from src/lib/db.ts.
|
|
12
|
+
*/
|
|
13
|
+
const ENTITIES = ['users', 'orders', 'invoices', 'products'];
|
|
14
|
+
|
|
15
|
+
export default function AdminPage() {
|
|
16
|
+
const { isSignedIn, isLoading } = useAuth();
|
|
17
|
+
|
|
18
|
+
if (isLoading) return <div style=\{{ padding: 'var(--space-8)', color: 'var(--fg-muted)' }}>Loading…</div>;
|
|
19
|
+
if (!isSignedIn) {
|
|
20
|
+
return (
|
|
21
|
+
<div style=\{{ padding: 'var(--space-8)', textAlign: 'center' }}>
|
|
22
|
+
<p>Sign in to access admin.</p>
|
|
23
|
+
<Link href="/sign-in" style=\{{ color: 'var(--primary)' }}>Sign in →</Link>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div style=\{{ padding: 'var(--space-8) var(--space-4)', maxWidth: '48rem', margin: '0 auto' }}>
|
|
30
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-6)' }}>
|
|
31
|
+
Admin
|
|
32
|
+
</h1>
|
|
33
|
+
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-6)' }}>
|
|
34
|
+
Pick an entity to browse. Edit src/lib/db.ts to point at your
|
|
35
|
+
real database.
|
|
36
|
+
</p>
|
|
37
|
+
<ul style=\{{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 'var(--space-3)' }}>
|
|
38
|
+
{ENTITIES.map((e) => (
|
|
39
|
+
<li key={e}>
|
|
40
|
+
<Link href={`/admin/data/${e}`} style=\{{
|
|
41
|
+
display: 'block',
|
|
42
|
+
padding: 'var(--space-4)',
|
|
43
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
44
|
+
border: '1px solid var(--border)',
|
|
45
|
+
background: 'var(--surface)',
|
|
46
|
+
color: 'var(--fg)',
|
|
47
|
+
textDecoration: 'none',
|
|
48
|
+
}}>
|
|
49
|
+
<div style=\{{ fontWeight: 600 }}>{e}</div>
|
|
50
|
+
<div style=\{{ fontSize: '0.75rem', color: 'var(--fg-muted)', marginTop: 'var(--space-1)' }}>
|
|
51
|
+
Browse {e} →
|
|
52
|
+
</div>
|
|
53
|
+
</Link>
|
|
54
|
+
</li>
|
|
55
|
+
))}
|
|
56
|
+
</ul>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Tailwind v4 — required because @githat/nextjs/styles is processed
|
|
3
|
+
* through @tailwindcss/postcss. Plain doesn't ship utility classes,
|
|
4
|
+
* but the import is needed for the auth pages to render styled.
|
|
5
|
+
*/
|
|
6
|
+
@import "tailwindcss";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Plain template — self-contained globals.
|
|
10
|
+
*
|
|
11
|
+
* Defines the minimum CSS variables a GitHat app uses for layout and
|
|
12
|
+
* the auth-page styling that ships with @githat/nextjs/styles.
|
|
13
|
+
* Override these in your own files when you want a real theme.
|
|
14
|
+
*
|
|
15
|
+
* Light theme by default; flip --bg/--fg for dark.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
:root {
|
|
19
|
+
/* Surface */
|
|
20
|
+
--bg: #ffffff;
|
|
21
|
+
--surface: #fafafa;
|
|
22
|
+
--surface-sub: #f4f4f5;
|
|
23
|
+
|
|
24
|
+
/* Borders */
|
|
25
|
+
--border: #e5e7eb;
|
|
26
|
+
|
|
27
|
+
/* Foreground */
|
|
28
|
+
--fg: #0a0a0a;
|
|
29
|
+
--fg-muted: #525252;
|
|
30
|
+
--fg-subtle: #737373;
|
|
31
|
+
|
|
32
|
+
/* Brand — change these two to re-skin the whole auth flow */
|
|
33
|
+
--primary: #6366f1;
|
|
34
|
+
--accent: #f59e0b;
|
|
35
|
+
|
|
36
|
+
/* Semantic */
|
|
37
|
+
--success: #16a34a;
|
|
38
|
+
--warn: #d97706;
|
|
39
|
+
--danger: #dc2626;
|
|
40
|
+
|
|
41
|
+
/* Spacing — used by @githat/nextjs/styles */
|
|
42
|
+
--space-1: 0.25rem;
|
|
43
|
+
--space-2: 0.5rem;
|
|
44
|
+
--space-3: 0.75rem;
|
|
45
|
+
--space-4: 1rem;
|
|
46
|
+
--space-6: 1.5rem;
|
|
47
|
+
--space-8: 2rem;
|
|
48
|
+
|
|
49
|
+
/* Radius */
|
|
50
|
+
--radius: 0.5rem;
|
|
51
|
+
--radius-md: 0.5rem;
|
|
52
|
+
--radius-lg: 0.75rem;
|
|
53
|
+
|
|
54
|
+
/* Fonts */
|
|
55
|
+
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
56
|
+
--font-wordmark: 'Instrument Serif', Georgia, serif;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (prefers-color-scheme: dark) {
|
|
60
|
+
:root {
|
|
61
|
+
--bg: #0a0a0a;
|
|
62
|
+
--surface: #18181b;
|
|
63
|
+
--surface-sub: #27272a;
|
|
64
|
+
--border: #3f3f46;
|
|
65
|
+
--fg: #fafafa;
|
|
66
|
+
--fg-muted: #a1a1aa;
|
|
67
|
+
--fg-subtle: #71717a;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
* {
|
|
72
|
+
box-sizing: border-box;
|
|
73
|
+
margin: 0;
|
|
74
|
+
padding: 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
body {
|
|
78
|
+
font-family: var(--font-sans);
|
|
79
|
+
background: var(--bg);
|
|
80
|
+
color: var(--fg);
|
|
81
|
+
line-height: 1.5;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
a {
|
|
85
|
+
color: inherit;
|
|
86
|
+
text-decoration: none;
|
|
87
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { GitHatProvider } from '@githat/nextjs';
|
|
2
|
+
import '@githat/nextjs/styles';
|
|
3
|
+
import './globals.css';
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
title: '{{businessName}}',
|
|
7
|
+
description: '{{description}}',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default function RootLayout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
|
|
11
|
+
return (
|
|
12
|
+
<html lang="en">
|
|
13
|
+
<body>
|
|
14
|
+
{/*
|
|
15
|
+
Plain template: no @githat/ui dep, no Wordmark. The
|
|
16
|
+
full-kit (`nextjs`) template uses @githat/ui for the
|
|
17
|
+
shared design system; the plain scaffold is the smallest
|
|
18
|
+
working app, so we avoid extra deps.
|
|
19
|
+
*/}
|
|
20
|
+
<GitHatProvider config=\{{
|
|
21
|
+
publishableKey: process.env.NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY || '',
|
|
22
|
+
signInUrl: '/sign-in',
|
|
23
|
+
signUpUrl: '/sign-up',
|
|
24
|
+
afterSignInUrl: '/',
|
|
25
|
+
afterSignOutUrl: '/',
|
|
26
|
+
}}>
|
|
27
|
+
<header style=\{{
|
|
28
|
+
padding: '1rem 1.5rem',
|
|
29
|
+
borderBottom: '1px solid var(--border, #e5e7eb)',
|
|
30
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
31
|
+
}}>
|
|
32
|
+
<a href="/" style=\{{ textDecoration: 'none', color: 'inherit', fontWeight: 600 }}>
|
|
33
|
+
{{businessName}}
|
|
34
|
+
</a>
|
|
35
|
+
</header>
|
|
36
|
+
<main>{children}</main>
|
|
37
|
+
</GitHatProvider>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { SignInButton, useAuth } from '@githat/nextjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Dashboard template homepage.
|
|
8
|
+
*
|
|
9
|
+
* The pitch: you already have a database (Postgres, MySQL,
|
|
10
|
+
* DynamoDB, anything). You want auth-gated admin tooling on top of
|
|
11
|
+
* it without building auth from scratch.
|
|
12
|
+
*
|
|
13
|
+
* Replaces Retool + Auth0 + Vercel. Demonstrates GitHat as a
|
|
14
|
+
* "drop auth on your existing data" platform.
|
|
15
|
+
*/
|
|
16
|
+
export default function Home() {
|
|
17
|
+
const { isSignedIn } = useAuth();
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)' }}>
|
|
21
|
+
<section style=\{{
|
|
22
|
+
padding: 'var(--space-12) var(--space-4)',
|
|
23
|
+
textAlign: 'center',
|
|
24
|
+
maxWidth: '40rem',
|
|
25
|
+
margin: '0 auto',
|
|
26
|
+
}}>
|
|
27
|
+
<h1 style=\{{
|
|
28
|
+
fontFamily: 'var(--font-wordmark, Georgia, serif)',
|
|
29
|
+
fontSize: 'clamp(2rem, 5vw, 3rem)',
|
|
30
|
+
lineHeight: 1.1,
|
|
31
|
+
marginBottom: 'var(--space-3)',
|
|
32
|
+
}}>
|
|
33
|
+
{{businessName}}
|
|
34
|
+
</h1>
|
|
35
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '1.125rem', marginBottom: 'var(--space-6)' }}>
|
|
36
|
+
Auth-gated admin UI for your existing database. Bring your
|
|
37
|
+
Postgres / MySQL / DynamoDB — we handle who can see it.
|
|
38
|
+
</p>
|
|
39
|
+
{!isSignedIn ? (
|
|
40
|
+
<SignInButton />
|
|
41
|
+
) : (
|
|
42
|
+
<Link href="/admin" style=\{{
|
|
43
|
+
display: 'inline-block',
|
|
44
|
+
padding: 'var(--space-3) var(--space-6)',
|
|
45
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
46
|
+
background: 'var(--primary)',
|
|
47
|
+
color: 'var(--bg)',
|
|
48
|
+
fontWeight: 600,
|
|
49
|
+
textDecoration: 'none',
|
|
50
|
+
}}>
|
|
51
|
+
Open dashboard →
|
|
52
|
+
</Link>
|
|
53
|
+
)}
|
|
54
|
+
</section>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Plain template — Tailwind v4 PostCSS plugin is required even though
|
|
3
|
+
* the plain scaffold doesn't use Tailwind utility classes. The auth
|
|
4
|
+
* page CSS shipped by `@githat/nextjs/styles` is processed through
|
|
5
|
+
* @tailwindcss/postcss at build time. Drop this config and the
|
|
6
|
+
* auth pages render unstyled.
|
|
7
|
+
*/
|
|
8
|
+
const config = {
|
|
9
|
+
plugins: {
|
|
10
|
+
'@tailwindcss/postcss': {},
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default config;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { authProxy } from '@githat/nextjs/proxy';
|
|
2
|
+
|
|
3
|
+
export const proxy = authProxy({
|
|
4
|
+
publicRoutes: ['/', '/sign-in', '/sign-up'{{#if includeForgotPassword}}, '/forgot-password', '/reset-password'{{/if}}{{#if includeEmailVerification}}, '/verify-email'{{/if}}],
|
|
5
|
+
signInUrl: '/sign-in',
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const config = {
|
|
9
|
+
matcher: ['/((?!_next|api|.*\\..*).*)'],
|
|
10
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database adapter — bring your own.
|
|
3
|
+
*
|
|
4
|
+
* The dashboard template stays database-agnostic on purpose. Pick
|
|
5
|
+
* whatever fits and replace the stubs below:
|
|
6
|
+
*
|
|
7
|
+
* Postgres: `import { Pool } from 'pg';`
|
|
8
|
+
* DynamoDB: `import { DynamoDBClient } from '@aws-sdk/client-dynamodb';`
|
|
9
|
+
* MySQL: `import mysql from 'mysql2/promise';`
|
|
10
|
+
* Drizzle/Prisma: drop in your existing client
|
|
11
|
+
*
|
|
12
|
+
* Set DATABASE_URL in .env.local.
|
|
13
|
+
*
|
|
14
|
+
* The `listEntities` and `getEntity` shapes are what the routes
|
|
15
|
+
* under /admin/data expect — keep the function names so the UI
|
|
16
|
+
* keeps working when you swap the body.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export interface EntityRow {
|
|
20
|
+
id: string;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function listEntities(entity: string): Promise<EntityRow[]> {
|
|
25
|
+
// TODO: replace with your real query, e.g.
|
|
26
|
+
// const { rows } = await pool.query('SELECT * FROM ' + entity + ' LIMIT 100');
|
|
27
|
+
// return rows;
|
|
28
|
+
void entity;
|
|
29
|
+
return [
|
|
30
|
+
{ id: '1', name: 'Sample row 1', createdAt: new Date().toISOString() },
|
|
31
|
+
{ id: '2', name: 'Sample row 2', createdAt: new Date().toISOString() },
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function getEntity(entity: string, id: string): Promise<EntityRow | null> {
|
|
36
|
+
// TODO: replace with your real query
|
|
37
|
+
void entity;
|
|
38
|
+
return { id, name: 'Sample row', createdAt: new Date().toISOString() };
|
|
39
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": { "@/*": ["./*"] }
|
|
18
|
+
},
|
|
19
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
20
|
+
"exclude": ["node_modules"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@{{projectName}}/api",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "tsx watch src/index.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@githat/nextjs": "^0.2.6",
|
|
14
|
+
"cors": "^2.8.5",
|
|
15
|
+
"express": "^5.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/cors": "^2.8.17",
|
|
19
|
+
"@types/express": "^5.0.0",
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"tsx": "^4.19.0",
|
|
22
|
+
"typescript": "^5.9.0"
|
|
23
|
+
}
|
|
24
|
+
}
|