create-githat-app 1.0.14 → 1.0.16
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/cli.js +72 -18
- package/package.json +1 -1
- 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 +7 -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/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 +7 -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 +7 -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 +7 -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/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 +7 -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/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 +7 -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/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 +7 -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,106 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { useAuth } from '@githat/nextjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Admin dashboard — auth-gated.
|
|
8
|
+
*
|
|
9
|
+
* Three tiles a B2B operator looks at on Monday morning:
|
|
10
|
+
* 1. Active members on the org
|
|
11
|
+
* 2. Subscription status (trial / active / past-due / canceled)
|
|
12
|
+
* 3. Usage against plan limits (seats, storage, API calls)
|
|
13
|
+
*
|
|
14
|
+
* Tile data is hardcoded in the starter — wire to your data layer.
|
|
15
|
+
* Real org info comes from useGitHat().org once the user has joined
|
|
16
|
+
* or created an org.
|
|
17
|
+
*/
|
|
18
|
+
export default function AdminPage() {
|
|
19
|
+
const { isSignedIn, user, isLoading } = useAuth();
|
|
20
|
+
|
|
21
|
+
if (isLoading) return <Loading />;
|
|
22
|
+
if (!isSignedIn) return <SignInPrompt />;
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)' }}>
|
|
26
|
+
<div style=\{{ maxWidth: '64rem', margin: '0 auto', padding: 'var(--space-8) var(--space-4)' }}>
|
|
27
|
+
<header style=\{{ marginBottom: 'var(--space-8)' }}>
|
|
28
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-2)' }}>
|
|
29
|
+
Welcome back{user?.name ? `, ${user.name}` : ''}
|
|
30
|
+
</h1>
|
|
31
|
+
<p style=\{{ color: 'var(--fg-muted)' }}>
|
|
32
|
+
Your org dashboard. Wire each tile to real data.
|
|
33
|
+
</p>
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
<div style=\{{
|
|
37
|
+
display: 'grid',
|
|
38
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
|
|
39
|
+
gap: 'var(--space-4)',
|
|
40
|
+
marginBottom: 'var(--space-8)',
|
|
41
|
+
}}>
|
|
42
|
+
<Tile label="Members" value="—" hint="Active org members" />
|
|
43
|
+
<Tile label="Plan" value="Trial" hint="14 days remaining" />
|
|
44
|
+
<Tile label="Usage" value="—" hint="API calls this month" />
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<nav style=\{{
|
|
48
|
+
display: 'grid',
|
|
49
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
|
|
50
|
+
gap: 'var(--space-3)',
|
|
51
|
+
}}>
|
|
52
|
+
<AdminLink href="/admin/team" title="Team" hint="Invite members, set roles" />
|
|
53
|
+
<AdminLink href="/admin/billing" title="Billing" hint="Subscription, invoices, payment method" />
|
|
54
|
+
<AdminLink href="/admin/settings" title="Settings" hint="Org name, custom domain, branding" />
|
|
55
|
+
</nav>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Tile({ label, value, hint }: { label: string; value: string; hint: string }) {
|
|
62
|
+
return (
|
|
63
|
+
<div style=\{{
|
|
64
|
+
padding: 'var(--space-5)',
|
|
65
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
66
|
+
border: '1px solid var(--border)',
|
|
67
|
+
background: 'var(--surface)',
|
|
68
|
+
}}>
|
|
69
|
+
<div style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)', marginBottom: 'var(--space-1)' }}>{label}</div>
|
|
70
|
+
<div style=\{{ fontSize: '2rem', fontWeight: 600 }}>{value}</div>
|
|
71
|
+
<div style=\{{ fontSize: '0.75rem', color: 'var(--fg-subtle)', marginTop: 'var(--space-1)' }}>{hint}</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function AdminLink({ href, title, hint }: { href: string; title: string; hint: string }) {
|
|
77
|
+
return (
|
|
78
|
+
<Link href={href} style=\{{
|
|
79
|
+
display: 'block',
|
|
80
|
+
padding: 'var(--space-5)',
|
|
81
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
82
|
+
border: '1px solid var(--border)',
|
|
83
|
+
background: 'var(--surface)',
|
|
84
|
+
color: 'var(--fg)',
|
|
85
|
+
textDecoration: 'none',
|
|
86
|
+
}}>
|
|
87
|
+
<div style=\{{ fontWeight: 600, marginBottom: 'var(--space-1)' }}>{title} →</div>
|
|
88
|
+
<div style=\{{ fontSize: '0.75rem', color: 'var(--fg-muted)' }}>{hint}</div>
|
|
89
|
+
</Link>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function Loading() {
|
|
94
|
+
return <div style=\{{ display: 'flex', minHeight: 'calc(100vh - 64px)', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-muted)' }}>Loading…</div>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function SignInPrompt() {
|
|
98
|
+
return (
|
|
99
|
+
<div style=\{{ display: 'flex', minHeight: 'calc(100vh - 64px)', alignItems: 'center', justifyContent: 'center' }}>
|
|
100
|
+
<div style=\{{ textAlign: 'center' }}>
|
|
101
|
+
<h2 style=\{{ marginBottom: 'var(--space-3)' }}>Sign in to continue</h2>
|
|
102
|
+
<Link href="/sign-in" style=\{{ color: 'var(--primary)' }}>Sign in →</Link>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { useAuth } from '@githat/nextjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Team management — invite members, change roles.
|
|
9
|
+
*
|
|
10
|
+
* GitHat's API has the primitives (POST /org/{id}/invite, PATCH
|
|
11
|
+
* /org/{id}/members/{userId}/role). The starter renders the form
|
|
12
|
+
* shell; wire it to your /api/team route or directly to GitHat's
|
|
13
|
+
* REST endpoints depending on your architecture.
|
|
14
|
+
*/
|
|
15
|
+
export default function TeamPage() {
|
|
16
|
+
const { isSignedIn, isLoading } = useAuth();
|
|
17
|
+
const [email, setEmail] = useState('');
|
|
18
|
+
const [role, setRole] = useState<'admin' | 'member'>('member');
|
|
19
|
+
|
|
20
|
+
if (isLoading) return <div style=\{{ padding: 'var(--space-8)', color: 'var(--fg-muted)' }}>Loading…</div>;
|
|
21
|
+
if (!isSignedIn) {
|
|
22
|
+
return (
|
|
23
|
+
<div style=\{{ padding: 'var(--space-8)', textAlign: 'center' }}>
|
|
24
|
+
<p>Sign in to manage your team.</p>
|
|
25
|
+
<Link href="/sign-in" style=\{{ color: 'var(--primary)' }}>Sign in →</Link>
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const sampleMembers = [
|
|
31
|
+
{ id: '1', email: 'you@example.com', role: 'owner', name: 'You' },
|
|
32
|
+
{ id: '2', email: 'jane@example.com', role: 'admin', name: 'Jane Cooper' },
|
|
33
|
+
{ id: '3', email: 'kai@example.com', role: 'member', name: 'Kai Lee' },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)' }}>
|
|
38
|
+
<div style=\{{ maxWidth: '48rem', margin: '0 auto', padding: 'var(--space-8) var(--space-4)' }}>
|
|
39
|
+
<Link href="/admin" style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem', display: 'inline-block', marginBottom: 'var(--space-4)' }}>
|
|
40
|
+
← Dashboard
|
|
41
|
+
</Link>
|
|
42
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-6)' }}>
|
|
43
|
+
Team
|
|
44
|
+
</h1>
|
|
45
|
+
|
|
46
|
+
<section style=\{{
|
|
47
|
+
padding: 'var(--space-5)',
|
|
48
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
49
|
+
border: '1px solid var(--border)',
|
|
50
|
+
background: 'var(--surface)',
|
|
51
|
+
marginBottom: 'var(--space-6)',
|
|
52
|
+
}}>
|
|
53
|
+
<h2 style=\{{ fontSize: '1.125rem', marginBottom: 'var(--space-3)' }}>Invite member</h2>
|
|
54
|
+
<form
|
|
55
|
+
onSubmit={(e) => { e.preventDefault(); /* TODO: POST /api/team/invite { email, role } */ }}
|
|
56
|
+
style=\{{ display: 'flex', gap: 'var(--space-2)', flexWrap: 'wrap' }}
|
|
57
|
+
>
|
|
58
|
+
<input
|
|
59
|
+
type="email"
|
|
60
|
+
required
|
|
61
|
+
value={email}
|
|
62
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
63
|
+
placeholder="teammate@example.com"
|
|
64
|
+
style=\{{
|
|
65
|
+
flex: '1 1 200px',
|
|
66
|
+
padding: 'var(--space-3)',
|
|
67
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
68
|
+
border: '1px solid var(--border)',
|
|
69
|
+
background: 'var(--bg)',
|
|
70
|
+
color: 'var(--fg)',
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
<select
|
|
74
|
+
value={role}
|
|
75
|
+
onChange={(e) => setRole(e.target.value as 'admin' | 'member')}
|
|
76
|
+
style=\{{
|
|
77
|
+
padding: 'var(--space-3)',
|
|
78
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
79
|
+
border: '1px solid var(--border)',
|
|
80
|
+
background: 'var(--bg)',
|
|
81
|
+
color: 'var(--fg)',
|
|
82
|
+
}}
|
|
83
|
+
>
|
|
84
|
+
<option value="member">Member</option>
|
|
85
|
+
<option value="admin">Admin</option>
|
|
86
|
+
</select>
|
|
87
|
+
<button type="submit" style=\{{
|
|
88
|
+
padding: 'var(--space-3) var(--space-5)',
|
|
89
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
90
|
+
border: 'none',
|
|
91
|
+
background: 'var(--primary)',
|
|
92
|
+
color: 'var(--bg)',
|
|
93
|
+
fontWeight: 600,
|
|
94
|
+
cursor: 'pointer',
|
|
95
|
+
}}>
|
|
96
|
+
Send invite
|
|
97
|
+
</button>
|
|
98
|
+
</form>
|
|
99
|
+
</section>
|
|
100
|
+
|
|
101
|
+
<h2 style=\{{ fontSize: '1.125rem', marginBottom: 'var(--space-3)' }}>Members</h2>
|
|
102
|
+
<ul style=\{{ listStyle: 'none', padding: 0, margin: 0 }}>
|
|
103
|
+
{sampleMembers.map((m) => (
|
|
104
|
+
<li key={m.id} style=\{{
|
|
105
|
+
display: 'flex',
|
|
106
|
+
alignItems: 'center',
|
|
107
|
+
justifyContent: 'space-between',
|
|
108
|
+
padding: 'var(--space-3) var(--space-4)',
|
|
109
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
110
|
+
border: '1px solid var(--border)',
|
|
111
|
+
background: 'var(--surface)',
|
|
112
|
+
marginBottom: 'var(--space-2)',
|
|
113
|
+
}}>
|
|
114
|
+
<div>
|
|
115
|
+
<div style=\{{ fontWeight: 600 }}>{m.name}</div>
|
|
116
|
+
<div style=\{{ fontSize: '0.75rem', color: 'var(--fg-muted)' }}>{m.email}</div>
|
|
117
|
+
</div>
|
|
118
|
+
<span style=\{{
|
|
119
|
+
padding: 'var(--space-1) var(--space-3)',
|
|
120
|
+
borderRadius: 'var(--radius-full, 9999px)',
|
|
121
|
+
background: m.role === 'owner' ? 'var(--primary)' : 'var(--surface-sub)',
|
|
122
|
+
color: m.role === 'owner' ? 'var(--bg)' : 'var(--fg)',
|
|
123
|
+
fontSize: '0.75rem',
|
|
124
|
+
fontWeight: 600,
|
|
125
|
+
}}>
|
|
126
|
+
{m.role}
|
|
127
|
+
</span>
|
|
128
|
+
</li>
|
|
129
|
+
))}
|
|
130
|
+
</ul>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -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,108 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { SignUpButton, useAuth } from '@githat/nextjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SaaS landing page.
|
|
8
|
+
*
|
|
9
|
+
* Replaces Clerk + Stripe + Vercel + a starter kit. Demonstrates:
|
|
10
|
+
* - GitHat orgs & RBAC out of the box (owner / admin / member)
|
|
11
|
+
* - Sebastn subscription billing wired to /admin/billing
|
|
12
|
+
* - One-stack deploy (this same project deploys to GitHat hosting)
|
|
13
|
+
*
|
|
14
|
+
* The homepage is the conversion surface; the signed-in product
|
|
15
|
+
* lives under /admin.
|
|
16
|
+
*/
|
|
17
|
+
export default function Home() {
|
|
18
|
+
const { isSignedIn } = useAuth();
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)' }}>
|
|
22
|
+
<section style=\{{
|
|
23
|
+
padding: 'var(--space-12) var(--space-4)',
|
|
24
|
+
textAlign: 'center',
|
|
25
|
+
maxWidth: '48rem',
|
|
26
|
+
margin: '0 auto',
|
|
27
|
+
}}>
|
|
28
|
+
<h1 style=\{{
|
|
29
|
+
fontFamily: 'var(--font-wordmark, Georgia, serif)',
|
|
30
|
+
fontSize: 'clamp(2rem, 5vw, 3rem)',
|
|
31
|
+
lineHeight: 1.1,
|
|
32
|
+
marginBottom: 'var(--space-3)',
|
|
33
|
+
}}>
|
|
34
|
+
{{businessName}}
|
|
35
|
+
</h1>
|
|
36
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '1.25rem', marginBottom: 'var(--space-6)' }}>
|
|
37
|
+
The fastest way to ship a B2B product. Auth, teams, RBAC, and
|
|
38
|
+
subscription billing — wired up before you write a line.
|
|
39
|
+
</p>
|
|
40
|
+
<div style=\{{ display: 'flex', gap: 'var(--space-3)', justifyContent: 'center', flexWrap: 'wrap' }}>
|
|
41
|
+
{!isSignedIn ? (
|
|
42
|
+
<>
|
|
43
|
+
<SignUpButton />
|
|
44
|
+
<Link href="/pricing" style=\{{ alignSelf: 'center', color: 'var(--primary)' }}>
|
|
45
|
+
See pricing →
|
|
46
|
+
</Link>
|
|
47
|
+
</>
|
|
48
|
+
) : (
|
|
49
|
+
<Link href="/admin" style=\{{
|
|
50
|
+
padding: 'var(--space-3) var(--space-6)',
|
|
51
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
52
|
+
background: 'var(--primary)',
|
|
53
|
+
color: 'var(--bg)',
|
|
54
|
+
fontWeight: 600,
|
|
55
|
+
textDecoration: 'none',
|
|
56
|
+
}}>
|
|
57
|
+
Open dashboard →
|
|
58
|
+
</Link>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<section style=\{{ padding: 'var(--space-8) var(--space-4)', background: 'var(--surface-sub)' }}>
|
|
64
|
+
<div style=\{{
|
|
65
|
+
maxWidth: '64rem',
|
|
66
|
+
margin: '0 auto',
|
|
67
|
+
display: 'grid',
|
|
68
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
|
|
69
|
+
gap: 'var(--space-6)',
|
|
70
|
+
}}>
|
|
71
|
+
<Feature emoji="🏢" title="Orgs & teams" body="Create organisations, invite teammates, assign roles. RBAC built in (owner, admin, member)." />
|
|
72
|
+
<Feature emoji="💳" title="Subscriptions" body="Sebastn-powered billing — Stripe Checkout under the hood. Trials, plan changes, dunning, invoices, all handled." />
|
|
73
|
+
<Feature emoji="🚀" title="Ship in a weekend" body="Auth + payments + hosting are this one stack. No Vercel + Clerk + Stripe + a billing service to glue together." />
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
<section style=\{{ padding: 'var(--space-12) var(--space-4)', textAlign: 'center', maxWidth: '48rem', margin: '0 auto' }}>
|
|
78
|
+
<h2 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '1.75rem', marginBottom: 'var(--space-3)' }}>
|
|
79
|
+
Simple pricing
|
|
80
|
+
</h2>
|
|
81
|
+
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-6)' }}>
|
|
82
|
+
Free tier for the team trying us out. Paid tiers for the team that's growing.
|
|
83
|
+
Three plans, no hidden fees.
|
|
84
|
+
</p>
|
|
85
|
+
<Link href="/pricing" style=\{{
|
|
86
|
+
display: 'inline-block',
|
|
87
|
+
padding: 'var(--space-3) var(--space-6)',
|
|
88
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
89
|
+
border: '1px solid var(--border)',
|
|
90
|
+
color: 'var(--fg)',
|
|
91
|
+
textDecoration: 'none',
|
|
92
|
+
}}>
|
|
93
|
+
Compare plans →
|
|
94
|
+
</Link>
|
|
95
|
+
</section>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function Feature({ emoji, title, body }: { emoji: string; title: string; body: string }) {
|
|
101
|
+
return (
|
|
102
|
+
<div>
|
|
103
|
+
<div style=\{{ fontSize: '2rem', marginBottom: 'var(--space-2)' }} aria-hidden>{emoji}</div>
|
|
104
|
+
<h3 style=\{{ fontWeight: 600, marginBottom: 'var(--space-2)' }}>{title}</h3>
|
|
105
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem' }}>{body}</p>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { SignUpButton } from '@githat/nextjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pricing page — three tiers, monthly + annual toggle.
|
|
8
|
+
*
|
|
9
|
+
* Real apps wire each "Choose plan" button to a Sebastn checkout
|
|
10
|
+
* URL (or call /api/billing/checkout against your backend, which
|
|
11
|
+
* in turn calls Sebastn). The starter ships hardcoded prices so
|
|
12
|
+
* the page renders before any billing is wired.
|
|
13
|
+
*
|
|
14
|
+
* Tier IDs (sebastn_price_id) belong in your env, not in source —
|
|
15
|
+
* see /admin/billing for how the runtime page reads them.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const TIERS = [
|
|
19
|
+
{
|
|
20
|
+
name: 'Starter',
|
|
21
|
+
price: 0,
|
|
22
|
+
blurb: 'For teams trying us out.',
|
|
23
|
+
features: ['1 org', '5 members', 'Email support', '1 GB storage'],
|
|
24
|
+
cta: 'Sign up free',
|
|
25
|
+
href: '/sign-up',
|
|
26
|
+
highlight: false,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'Team',
|
|
30
|
+
price: 29,
|
|
31
|
+
blurb: 'For growing teams that need more.',
|
|
32
|
+
features: ['Unlimited orgs', '50 members', 'Priority support', '50 GB storage', 'API access'],
|
|
33
|
+
cta: 'Start 14-day trial',
|
|
34
|
+
href: '/sign-up?plan=team',
|
|
35
|
+
highlight: true,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: 'Business',
|
|
39
|
+
price: 99,
|
|
40
|
+
blurb: 'For teams that need SSO and audit logs.',
|
|
41
|
+
features: ['Everything in Team', 'SSO (SAML)', 'Audit log', 'SLA support', 'Custom contract'],
|
|
42
|
+
cta: 'Contact sales',
|
|
43
|
+
href: '/contact',
|
|
44
|
+
highlight: false,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
export default function PricingPage() {
|
|
49
|
+
return (
|
|
50
|
+
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)', padding: 'var(--space-12) var(--space-4)' }}>
|
|
51
|
+
<div style=\{{ maxWidth: '64rem', margin: '0 auto' }}>
|
|
52
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2.5rem', textAlign: 'center', marginBottom: 'var(--space-3)' }}>
|
|
53
|
+
Pricing
|
|
54
|
+
</h1>
|
|
55
|
+
<p style=\{{ color: 'var(--fg-muted)', textAlign: 'center', marginBottom: 'var(--space-8)' }}>
|
|
56
|
+
Free tier is real free. Upgrade when your team outgrows it.
|
|
57
|
+
</p>
|
|
58
|
+
|
|
59
|
+
<div style=\{{
|
|
60
|
+
display: 'grid',
|
|
61
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(260px, 1fr))',
|
|
62
|
+
gap: 'var(--space-4)',
|
|
63
|
+
}}>
|
|
64
|
+
{TIERS.map((tier) => (
|
|
65
|
+
<div key={tier.name} style=\{{
|
|
66
|
+
padding: 'var(--space-6)',
|
|
67
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
68
|
+
border: tier.highlight ? '2px solid var(--primary)' : '1px solid var(--border)',
|
|
69
|
+
background: 'var(--surface)',
|
|
70
|
+
display: 'flex',
|
|
71
|
+
flexDirection: 'column',
|
|
72
|
+
}}>
|
|
73
|
+
{tier.highlight && (
|
|
74
|
+
<div style=\{{
|
|
75
|
+
alignSelf: 'flex-start',
|
|
76
|
+
padding: 'var(--space-1) var(--space-2)',
|
|
77
|
+
borderRadius: 'var(--radius-sm, 0.25rem)',
|
|
78
|
+
background: 'var(--primary)',
|
|
79
|
+
color: 'var(--bg)',
|
|
80
|
+
fontSize: '0.75rem',
|
|
81
|
+
fontWeight: 600,
|
|
82
|
+
marginBottom: 'var(--space-3)',
|
|
83
|
+
}}>
|
|
84
|
+
MOST POPULAR
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
<h2 style=\{{ fontSize: '1.25rem', fontWeight: 600, marginBottom: 'var(--space-2)' }}>
|
|
88
|
+
{tier.name}
|
|
89
|
+
</h2>
|
|
90
|
+
<div style=\{{ marginBottom: 'var(--space-3)' }}>
|
|
91
|
+
<span style=\{{ fontSize: '2.5rem', fontWeight: 700 }}>
|
|
92
|
+
${tier.price}
|
|
93
|
+
</span>
|
|
94
|
+
{tier.price > 0 && (
|
|
95
|
+
<span style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)' }}>/mo</span>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
<p style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem', marginBottom: 'var(--space-4)' }}>
|
|
99
|
+
{tier.blurb}
|
|
100
|
+
</p>
|
|
101
|
+
<ul style=\{{ listStyle: 'none', padding: 0, margin: 0, marginBottom: 'var(--space-4)', flex: 1 }}>
|
|
102
|
+
{tier.features.map((f) => (
|
|
103
|
+
<li key={f} style=\{{ padding: 'var(--space-1) 0', fontSize: '0.875rem' }}>
|
|
104
|
+
✓ {f}
|
|
105
|
+
</li>
|
|
106
|
+
))}
|
|
107
|
+
</ul>
|
|
108
|
+
{tier.name === 'Starter' ? (
|
|
109
|
+
<SignUpButton />
|
|
110
|
+
) : (
|
|
111
|
+
<Link href={tier.href} style=\{{
|
|
112
|
+
display: 'inline-block',
|
|
113
|
+
padding: 'var(--space-3) var(--space-4)',
|
|
114
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
115
|
+
background: tier.highlight ? 'var(--primary)' : 'var(--surface-sub)',
|
|
116
|
+
color: tier.highlight ? 'var(--bg)' : 'var(--fg)',
|
|
117
|
+
textDecoration: 'none',
|
|
118
|
+
fontWeight: 600,
|
|
119
|
+
textAlign: 'center',
|
|
120
|
+
border: tier.highlight ? 'none' : '1px solid var(--border)',
|
|
121
|
+
}}>
|
|
122
|
+
{tier.cta}
|
|
123
|
+
</Link>
|
|
124
|
+
)}
|
|
125
|
+
</div>
|
|
126
|
+
))}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
@@ -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
|
+
}
|