create-githat-app 1.0.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.
Files changed (55) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +259 -0
  3. package/bin/index.js +6 -0
  4. package/dist/cli.js +623 -0
  5. package/package.json +42 -0
  6. package/templates/base/.env.example.hbs +13 -0
  7. package/templates/base/.env.local.hbs +10 -0
  8. package/templates/base/.gitignore.hbs +11 -0
  9. package/templates/base/githat/api/agents.ts.hbs +17 -0
  10. package/templates/base/githat/api/auth.ts.hbs +38 -0
  11. package/templates/base/githat/api/client.ts.hbs +92 -0
  12. package/templates/base/githat/api/mcp.ts.hbs +21 -0
  13. package/templates/base/githat/api/orgs.ts.hbs +34 -0
  14. package/templates/base/githat/api/types.ts.hbs +70 -0
  15. package/templates/base/githat/api/users.ts.hbs +14 -0
  16. package/templates/base/githat/auth/guard.tsx.hbs +53 -0
  17. package/templates/base/githat/auth/index.ts.hbs +3 -0
  18. package/templates/base/githat/config.ts.hbs +18 -0
  19. package/templates/base/githat/dashboard/agents.tsx.hbs +121 -0
  20. package/templates/base/githat/dashboard/apps.tsx.hbs +53 -0
  21. package/templates/base/githat/dashboard/layout.tsx.hbs +83 -0
  22. package/templates/base/githat/dashboard/mcp-servers.tsx.hbs +111 -0
  23. package/templates/base/githat/dashboard/members.tsx.hbs +123 -0
  24. package/templates/base/githat/dashboard/overview.tsx.hbs +37 -0
  25. package/templates/base/githat/dashboard/settings.tsx.hbs +87 -0
  26. package/templates/nextjs/app/(auth)/forgot-password/page.tsx.hbs +63 -0
  27. package/templates/nextjs/app/(auth)/sign-in/page.tsx.hbs +9 -0
  28. package/templates/nextjs/app/(auth)/sign-up/page.tsx.hbs +9 -0
  29. package/templates/nextjs/app/(auth)/verify-email/page.tsx.hbs +58 -0
  30. package/templates/nextjs/app/dashboard/agents/page.tsx.hbs +9 -0
  31. package/templates/nextjs/app/dashboard/apps/page.tsx.hbs +9 -0
  32. package/templates/nextjs/app/dashboard/layout.tsx.hbs +28 -0
  33. package/templates/nextjs/app/dashboard/mcp/page.tsx.hbs +9 -0
  34. package/templates/nextjs/app/dashboard/members/page.tsx.hbs +9 -0
  35. package/templates/nextjs/app/dashboard/page.tsx.hbs +30 -0
  36. package/templates/nextjs/app/dashboard/settings/page.tsx.hbs +9 -0
  37. package/templates/nextjs/app/globals.css.hbs +20 -0
  38. package/templates/nextjs/app/layout.tsx.hbs +33 -0
  39. package/templates/nextjs/app/page.tsx.hbs +18 -0
  40. package/templates/nextjs/middleware.ts.hbs +10 -0
  41. package/templates/nextjs/next.config.ts.hbs +5 -0
  42. package/templates/nextjs/postcss.config.mjs.hbs +9 -0
  43. package/templates/nextjs/tsconfig.json.hbs +21 -0
  44. package/templates/react-vite/index.html.hbs +12 -0
  45. package/templates/react-vite/src/App.tsx.hbs +46 -0
  46. package/templates/react-vite/src/index.css.hbs +20 -0
  47. package/templates/react-vite/src/main.tsx.hbs +30 -0
  48. package/templates/react-vite/src/pages/Dashboard.tsx.hbs +68 -0
  49. package/templates/react-vite/src/pages/ForgotPassword.tsx.hbs +58 -0
  50. package/templates/react-vite/src/pages/Home.tsx.hbs +18 -0
  51. package/templates/react-vite/src/pages/SignIn.tsx.hbs +9 -0
  52. package/templates/react-vite/src/pages/SignUp.tsx.hbs +9 -0
  53. package/templates/react-vite/src/pages/VerifyEmail.tsx.hbs +51 -0
  54. package/templates/react-vite/tsconfig.json.hbs +16 -0
  55. package/templates/react-vite/vite.config.ts.hbs +14 -0
@@ -0,0 +1,111 @@
1
+ {{#if includeMcpModule}}
2
+ 'use client';
3
+
4
+ import { useState, useEffect } from 'react';
5
+ import { githatApi } from '../api/client{{#unless typescript}}.js{{/unless}}';
6
+ {{#if typescript}}
7
+ import type { McpServer } from '../api/types';
8
+ {{/if}}
9
+
10
+ export function DashboardMcpServers() {
11
+ const [servers, setServers] = useState{{#if typescript}}<McpServer[]>{{/if}}([]);
12
+ const [loading, setLoading] = useState(true);
13
+ const [domain, setDomain] = useState('');
14
+ const [registering, setRegistering] = useState(false);
15
+ const [error, setError] = useState('');
16
+
17
+ const loadServers = () => {
18
+ setLoading(true);
19
+ githatApi.get{{#if typescript}}<{ servers: McpServer[] }>{{/if}}('/mcp/servers')
20
+ .then((data) => setServers(data.servers || []))
21
+ .catch(() => {})
22
+ .finally(() => setLoading(false));
23
+ };
24
+
25
+ useEffect(() => { loadServers(); }, []);
26
+
27
+ const handleRegister = async (e{{#if typescript}}: React.FormEvent{{/if}}) => {
28
+ e.preventDefault();
29
+ if (!domain.trim()) return;
30
+ setRegistering(true);
31
+ setError('');
32
+ try {
33
+ await githatApi.post('/mcp/servers', { domain: domain.trim() });
34
+ setDomain('');
35
+ loadServers();
36
+ } catch {
37
+ setError('Failed to register server. Check your domain and try again.');
38
+ } finally {
39
+ setRegistering(false);
40
+ }
41
+ };
42
+
43
+ const handleDelete = async (id{{#if typescript}}: string{{/if}}) => {
44
+ if (!confirm('Remove this MCP server?')) return;
45
+ try {
46
+ await githatApi.del(`/mcp/servers/${id}`);
47
+ loadServers();
48
+ } catch {
49
+ setError('Failed to remove server.');
50
+ }
51
+ };
52
+
53
+ if (loading) {
54
+ return <p style=\{{ color: '#71717a' }}>Loading MCP servers...</p>;
55
+ }
56
+
57
+ return (
58
+ <div>
59
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '1.5rem' }}>MCP Servers</h1>
60
+
61
+ <form onSubmit={handleRegister} style=\{{ display: 'flex', gap: '0.5rem', marginBottom: '1.5rem' }}>
62
+ <input
63
+ type="text"
64
+ value={domain}
65
+ onChange={(e) => setDomain(e.target.value)}
66
+ placeholder="mcp.yourcompany.com"
67
+ required
68
+ style=\{{ flex: 1, padding: '0.625rem 0.75rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.375rem', color: '#fafafa', outline: 'none' }}
69
+ />
70
+ <button
71
+ type="submit"
72
+ disabled={registering}
73
+ style=\{{ padding: '0.625rem 1.25rem', background: '#7c3aed', color: '#fff', border: 'none', borderRadius: '0.375rem', fontWeight: 600, cursor: 'pointer', opacity: registering ? 0.6 : 1 }}
74
+ >
75
+ {registering ? 'Registering...' : 'Register'}
76
+ </button>
77
+ </form>
78
+
79
+ {error && <p style=\{{ color: '#ef4444', fontSize: '0.875rem', marginBottom: '1rem' }}>{error}</p>}
80
+
81
+ {servers.length === 0 ? (
82
+ <div style=\{{ padding: '3rem', textAlign: 'center', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.5rem' }}>
83
+ <p style=\{{ color: '#71717a' }}>No MCP servers registered yet.</p>
84
+ </div>
85
+ ) : (
86
+ <div style=\{{ display: 'grid', gap: '0.5rem' }}>
87
+ {servers.map((server) => (
88
+ <div key={server.id} style=\{{ padding: '1rem 1.5rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.5rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
89
+ <div>
90
+ <p style=\{{ fontWeight: 600, color: '#fafafa' }}>{server.domain}</p>
91
+ <p style=\{{ fontSize: '0.875rem', color: '#71717a' }}>Client ID: {server.clientId}</p>
92
+ </div>
93
+ <div style=\{{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
94
+ <span style=\{{ fontSize: '0.75rem', padding: '0.25rem 0.75rem', borderRadius: '9999px', background: server.verified ? '#064e3b' : '#451a03', color: server.verified ? '#34d399' : '#f59e0b' }}>
95
+ {server.verified ? 'Verified' : 'Pending'}
96
+ </span>
97
+ <button
98
+ onClick={() => handleDelete(server.id)}
99
+ style=\{{ fontSize: '0.75rem', padding: '0.25rem 0.5rem', background: 'transparent', border: '1px solid #3f3f46', borderRadius: '0.25rem', color: '#ef4444', cursor: 'pointer' }}
100
+ >
101
+ Remove
102
+ </button>
103
+ </div>
104
+ </div>
105
+ ))}
106
+ </div>
107
+ )}
108
+ </div>
109
+ );
110
+ }
111
+ {{/if}}
@@ -0,0 +1,123 @@
1
+ {{#if includeOrgManagement}}
2
+ 'use client';
3
+
4
+ import { useState, useEffect } from 'react';
5
+ import { useAuth } from '@githat/nextjs';
6
+ import { orgsApi } from '../api/orgs{{#unless typescript}}.js{{/unless}}';
7
+ {{#if typescript}}
8
+ import type { ApiOrgMember } from '../api/types';
9
+ {{/if}}
10
+
11
+ export function DashboardMembers() {
12
+ const { org } = useAuth();
13
+ const [members, setMembers] = useState{{#if typescript}}<ApiOrgMember[]>{{/if}}([]);
14
+ const [loading, setLoading] = useState(true);
15
+ const [inviteEmail, setInviteEmail] = useState('');
16
+ const [inviteRole, setInviteRole] = useState{{#if typescript}}<'admin' | 'member'>{{/if}}('member');
17
+ const [inviting, setInviting] = useState(false);
18
+ const [error, setError] = useState('');
19
+
20
+ const loadMembers = () => {
21
+ if (!org) return;
22
+ setLoading(true);
23
+ orgsApi.listMembers(org.id)
24
+ .then((data) => setMembers(data.members || []))
25
+ .catch(() => {})
26
+ .finally(() => setLoading(false));
27
+ };
28
+
29
+ useEffect(() => { loadMembers(); }, [org]);
30
+
31
+ const handleInvite = async (e{{#if typescript}}: React.FormEvent{{/if}}) => {
32
+ e.preventDefault();
33
+ if (!org || !inviteEmail.trim()) return;
34
+ setInviting(true);
35
+ setError('');
36
+ try {
37
+ await orgsApi.inviteMember(org.id, inviteEmail.trim(), inviteRole);
38
+ setInviteEmail('');
39
+ loadMembers();
40
+ } catch {
41
+ setError('Failed to send invite. Please try again.');
42
+ } finally {
43
+ setInviting(false);
44
+ }
45
+ };
46
+
47
+ const handleRemove = async (userId{{#if typescript}}: string{{/if}}) => {
48
+ if (!org || !confirm('Remove this member?')) return;
49
+ try {
50
+ await orgsApi.removeMember(org.id, userId);
51
+ loadMembers();
52
+ } catch {
53
+ setError('Failed to remove member.');
54
+ }
55
+ };
56
+
57
+ if (!org) {
58
+ return <p style=\{{ color: '#71717a' }}>Select an organization to manage members.</p>;
59
+ }
60
+
61
+ if (loading) {
62
+ return <p style=\{{ color: '#71717a' }}>Loading members...</p>;
63
+ }
64
+
65
+ return (
66
+ <div>
67
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '1.5rem' }}>Members</h1>
68
+
69
+ <form onSubmit={handleInvite} style=\{{ display: 'flex', gap: '0.5rem', marginBottom: '1.5rem', flexWrap: 'wrap' }}>
70
+ <input
71
+ type="email"
72
+ value={inviteEmail}
73
+ onChange={(e) => setInviteEmail(e.target.value)}
74
+ placeholder="colleague@company.com"
75
+ required
76
+ style=\{{ flex: 1, minWidth: '12rem', padding: '0.625rem 0.75rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.375rem', color: '#fafafa', outline: 'none' }}
77
+ />
78
+ <select
79
+ value={inviteRole}
80
+ onChange={(e) => setInviteRole(e.target.value{{#if typescript}} as 'admin' | 'member'{{/if}})}
81
+ style=\{{ padding: '0.625rem 0.75rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.375rem', color: '#fafafa' }}
82
+ >
83
+ <option value="member">Member</option>
84
+ <option value="admin">Admin</option>
85
+ </select>
86
+ <button
87
+ type="submit"
88
+ disabled={inviting}
89
+ style=\{{ padding: '0.625rem 1.25rem', background: '#7c3aed', color: '#fff', border: 'none', borderRadius: '0.375rem', fontWeight: 600, cursor: 'pointer', opacity: inviting ? 0.6 : 1 }}
90
+ >
91
+ {inviting ? 'Inviting...' : 'Invite'}
92
+ </button>
93
+ </form>
94
+
95
+ {error && <p style=\{{ color: '#ef4444', fontSize: '0.875rem', marginBottom: '1rem' }}>{error}</p>}
96
+
97
+ <div style=\{{ display: 'grid', gap: '0.5rem' }}>
98
+ {members.map((member) => (
99
+ <div key={member.userId} style=\{{ padding: '1rem 1.5rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.5rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
100
+ <div>
101
+ <p style=\{{ fontWeight: 600, color: '#fafafa' }}>{member.name}</p>
102
+ <p style=\{{ fontSize: '0.875rem', color: '#71717a' }}>{member.email}</p>
103
+ </div>
104
+ <div style=\{{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
105
+ <span style=\{{ fontSize: '0.75rem', padding: '0.25rem 0.75rem', borderRadius: '9999px', background: '#1e1e2e', color: member.role === 'owner' ? '#7c3aed' : '#a1a1aa' }}>
106
+ {member.role}
107
+ </span>
108
+ {member.role !== 'owner' && (
109
+ <button
110
+ onClick={() => handleRemove(member.userId)}
111
+ style=\{{ fontSize: '0.75rem', padding: '0.25rem 0.5rem', background: 'transparent', border: '1px solid #3f3f46', borderRadius: '0.25rem', color: '#ef4444', cursor: 'pointer' }}
112
+ >
113
+ Remove
114
+ </button>
115
+ )}
116
+ </div>
117
+ </div>
118
+ ))}
119
+ </div>
120
+ </div>
121
+ );
122
+ }
123
+ {{/if}}
@@ -0,0 +1,37 @@
1
+ {{#unless includeDashboard}}{{else}}
2
+ 'use client';
3
+
4
+ import { useAuth } from '@githat/nextjs';
5
+
6
+ export function DashboardOverview() {
7
+ const { user, org } = useAuth();
8
+
9
+ return (
10
+ <div>
11
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}>
12
+ Welcome{user?.name ? `, ${user.name}` : ''}
13
+ </h1>
14
+ {org && (
15
+ <p style=\{{ color: '#a1a1aa', marginBottom: '2rem' }}>
16
+ Organization: <strong style=\{{ color: '#7c3aed' }}>{org.name}</strong> ({org.role})
17
+ </p>
18
+ )}
19
+
20
+ <div style=\{{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(16rem, 1fr))', gap: '1rem' }}>
21
+ <StatCard title="Status" value="Active" />
22
+ <StatCard title="Plan" value={org?.tier || 'Free'} />
23
+ <StatCard title="Identity" value="GitHat" />
24
+ </div>
25
+ </div>
26
+ );
27
+ }
28
+
29
+ function StatCard({ title, value }{{#if typescript}}: { title: string; value: string }{{/if}}) {
30
+ return (
31
+ <div style=\{{ padding: '1.5rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.5rem' }}>
32
+ <p style=\{{ fontSize: '0.875rem', color: '#71717a', marginBottom: '0.25rem' }}>{title}</p>
33
+ <p style=\{{ fontSize: '1.25rem', fontWeight: 600, color: '#fafafa' }}>{value}</p>
34
+ </div>
35
+ );
36
+ }
37
+ {{/unless}}
@@ -0,0 +1,87 @@
1
+ {{#if includeOrgManagement}}
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+ import { useAuth } from '@githat/nextjs';
6
+ import { orgsApi } from '../api/orgs{{#unless typescript}}.js{{/unless}}';
7
+
8
+ export function DashboardSettings() {
9
+ const { org } = useAuth();
10
+ const [name, setName] = useState(org?.name || '');
11
+ const [brandColor, setBrandColor] = useState(org?.brandColor || '#7c3aed');
12
+ const [saving, setSaving] = useState(false);
13
+ const [saved, setSaved] = useState(false);
14
+ const [error, setError] = useState('');
15
+
16
+ if (!org) {
17
+ return <p style=\{{ color: '#71717a' }}>Select an organization to view settings.</p>;
18
+ }
19
+
20
+ const handleSave = async (e{{#if typescript}}: React.FormEvent{{/if}}) => {
21
+ e.preventDefault();
22
+ setSaving(true);
23
+ setError('');
24
+ setSaved(false);
25
+ try {
26
+ await orgsApi.update(org.id, { name, brandColor });
27
+ setSaved(true);
28
+ } catch {
29
+ setError('Failed to save settings.');
30
+ } finally {
31
+ setSaving(false);
32
+ }
33
+ };
34
+
35
+ return (
36
+ <div>
37
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '1.5rem' }}>Settings</h1>
38
+ <form onSubmit={handleSave} style=\{{ maxWidth: '32rem', display: 'grid', gap: '1.5rem' }}>
39
+ <div>
40
+ <label style=\{{ fontSize: '0.875rem', color: '#71717a', display: 'block', marginBottom: '0.375rem' }}>Organization Name</label>
41
+ <input
42
+ value={name}
43
+ onChange={(e) => setName(e.target.value)}
44
+ style=\{{ width: '100%', padding: '0.625rem 0.75rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.375rem', color: '#fafafa', outline: 'none' }}
45
+ />
46
+ </div>
47
+ <SettingField label="Slug" value={org.slug} />
48
+ <SettingField label="Tier" value={org.tier} />
49
+ <div>
50
+ <label style=\{{ fontSize: '0.875rem', color: '#71717a', display: 'block', marginBottom: '0.375rem' }}>Brand Color</label>
51
+ <div style=\{{ display: 'flex', alignItems: 'center', gap: '0.75rem' }}>
52
+ <input
53
+ type="color"
54
+ value={brandColor}
55
+ onChange={(e) => setBrandColor(e.target.value)}
56
+ style=\{{ width: '2.5rem', height: '2.5rem', border: '1px solid #1e1e2e', borderRadius: '0.375rem', background: 'transparent', cursor: 'pointer' }}
57
+ />
58
+ <span style=\{{ color: '#a1a1aa', fontSize: '0.875rem', fontFamily: 'monospace' }}>{brandColor}</span>
59
+ </div>
60
+ </div>
61
+
62
+ {error && <p style=\{{ color: '#ef4444', fontSize: '0.875rem' }}>{error}</p>}
63
+ {saved && <p style=\{{ color: '#34d399', fontSize: '0.875rem' }}>Settings saved.</p>}
64
+
65
+ <button
66
+ type="submit"
67
+ disabled={saving}
68
+ style=\{{ padding: '0.625rem 1.25rem', background: '#7c3aed', color: '#fff', border: 'none', borderRadius: '0.375rem', fontWeight: 600, cursor: 'pointer', opacity: saving ? 0.6 : 1, justifySelf: 'start' }}
69
+ >
70
+ {saving ? 'Saving...' : 'Save changes'}
71
+ </button>
72
+ </form>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ function SettingField({ label, value }{{#if typescript}}: { label: string; value: string }{{/if}}) {
78
+ return (
79
+ <div>
80
+ <label style=\{{ fontSize: '0.875rem', color: '#71717a', display: 'block', marginBottom: '0.375rem' }}>{label}</label>
81
+ <div style=\{{ padding: '0.625rem 0.75rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.375rem', color: '#fafafa' }}>
82
+ {value}
83
+ </div>
84
+ </div>
85
+ );
86
+ }
87
+ {{/if}}
@@ -0,0 +1,63 @@
1
+ {{#if includeForgotPassword}}
2
+ 'use client';
3
+
4
+ import { useState } from 'react';
5
+ {{#if includeGithatFolder}}
6
+ import { authApi } from '../../../githat/api/auth{{#unless typescript}}.js{{/unless}}';
7
+ {{/if}}
8
+
9
+ export default function ForgotPasswordPage() {
10
+ const [email, setEmail] = useState('');
11
+ const [sent, setSent] = useState(false);
12
+ const [error, setError] = useState('');
13
+
14
+ const handleSubmit = async (e{{#if typescript}}: React.FormEvent{{/if}}) => {
15
+ e.preventDefault();
16
+ setError('');
17
+ try {
18
+ {{#if includeGithatFolder}}
19
+ await authApi.forgotPassword(email);
20
+ {{else}}
21
+ await fetch('{{apiUrl}}/auth/forgot-password', {
22
+ method: 'POST',
23
+ headers: { 'Content-Type': 'application/json' },
24
+ body: JSON.stringify({ email }),
25
+ });
26
+ {{/if}}
27
+ setSent(true);
28
+ } catch (err) {
29
+ setError('Something went wrong. Please try again.');
30
+ }
31
+ };
32
+
33
+ return (
34
+ <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}}>
35
+ <div style=\{{ width: '100%', maxWidth: '24rem', padding: '2rem' }}>
36
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}>Reset password</h1>
37
+ {sent ? (
38
+ <p style=\{{ color: '#a1a1aa' }}>Check your email for a reset link.</p>
39
+ ) : (
40
+ <form onSubmit={handleSubmit}>
41
+ <p style=\{{ color: '#a1a1aa', marginBottom: '1.5rem' }}>Enter your email to receive a reset link.</p>
42
+ {error && <p style=\{{ color: '#ef4444', marginBottom: '1rem', fontSize: '0.875rem' }}>{error}</p>}
43
+ <input
44
+ type="email"
45
+ value={email}
46
+ onChange={(e) => setEmail(e.target.value)}
47
+ placeholder="you@example.com"
48
+ required
49
+ style=\{{ width: '100%', padding: '0.625rem 0.75rem', background: '#111113', border: '1px solid #1e1e2e', borderRadius: '0.375rem', color: '#fafafa', marginBottom: '1rem', outline: 'none' }}
50
+ />
51
+ <button
52
+ type="submit"
53
+ style=\{{ width: '100%', padding: '0.625rem', background: '#7c3aed', color: '#fff', border: 'none', borderRadius: '0.375rem', fontWeight: 600, cursor: 'pointer' }}
54
+ >
55
+ Send reset link
56
+ </button>
57
+ </form>
58
+ )}
59
+ </div>
60
+ </main>
61
+ );
62
+ }
63
+ {{/if}}
@@ -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,58 @@
1
+ {{#if includeEmailVerification}}
2
+ 'use client';
3
+
4
+ import { useEffect, useState } from 'react';
5
+ {{#if includeGithatFolder}}
6
+ import { authApi } from '../../../githat/api/auth{{#unless typescript}}.js{{/unless}}';
7
+ {{/if}}
8
+
9
+ export default function VerifyEmailPage() {
10
+ const [status, setStatus] = useState{{#if typescript}}<'loading' | 'success' | 'error'>{{/if}}('loading');
11
+
12
+ useEffect(() => {
13
+ const params = new URLSearchParams(window.location.search);
14
+ const token = params.get('token');
15
+ if (!token) {
16
+ setStatus('error');
17
+ return;
18
+ }
19
+
20
+ (async () => {
21
+ try {
22
+ {{#if includeGithatFolder}}
23
+ await authApi.verifyEmail(token);
24
+ {{else}}
25
+ await fetch('{{apiUrl}}/auth/verify-email', {
26
+ method: 'POST',
27
+ headers: { 'Content-Type': 'application/json' },
28
+ body: JSON.stringify({ token }),
29
+ });
30
+ {{/if}}
31
+ setStatus('success');
32
+ } catch {
33
+ setStatus('error');
34
+ }
35
+ })();
36
+ }, []);
37
+
38
+ return (
39
+ <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}}>
40
+ <div style=\{{ textAlign: 'center' }}>
41
+ {status === 'loading' && <p style=\{{ color: '#a1a1aa' }}>Verifying your email...</p>}
42
+ {status === 'success' && (
43
+ <>
44
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}>Email verified!</h1>
45
+ <p style=\{{ color: '#a1a1aa' }}>You can now <a href="/sign-in" style=\{{ color: '#7c3aed' }}>sign in</a>.</p>
46
+ </>
47
+ )}
48
+ {status === 'error' && (
49
+ <>
50
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}>Verification failed</h1>
51
+ <p style=\{{ color: '#a1a1aa' }}>The link may have expired. <a href="/sign-up" style=\{{ color: '#7c3aed' }}>Try again</a>.</p>
52
+ </>
53
+ )}
54
+ </div>
55
+ </main>
56
+ );
57
+ }
58
+ {{/if}}
@@ -0,0 +1,9 @@
1
+ {{#if includeAgentModule}}
2
+ {{#if includeGithatFolder}}
3
+ import { DashboardAgents } from '../../../githat/dashboard/agents{{#unless typescript}}.jsx{{/unless}}';
4
+
5
+ export default function AgentsPage() {
6
+ return <DashboardAgents />;
7
+ }
8
+ {{/if}}
9
+ {{/if}}
@@ -0,0 +1,9 @@
1
+ {{#if includeDashboard}}
2
+ {{#if includeGithatFolder}}
3
+ import { DashboardApps } from '../../../githat/dashboard/apps{{#unless typescript}}.jsx{{/unless}}';
4
+
5
+ export default function AppsPage() {
6
+ return <DashboardApps />;
7
+ }
8
+ {{/if}}
9
+ {{/if}}
@@ -0,0 +1,28 @@
1
+ {{#if includeDashboard}}
2
+ {{#if includeGithatFolder}}
3
+ import { DashboardLayout } from '../../githat/dashboard/layout{{#unless typescript}}.jsx{{/unless}}';
4
+
5
+ export default function Layout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
6
+ return <DashboardLayout>{children}</DashboardLayout>;
7
+ }
8
+ {{else}}
9
+ import { ProtectedRoute, UserButton, OrgSwitcher } from '@githat/nextjs';
10
+
11
+ export default function DashboardLayout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
12
+ return (
13
+ <ProtectedRoute>
14
+ <div style=\{{ minHeight: '100vh', background: '#09090b' }}>
15
+ <header style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '1rem 2rem', borderBottom: '1px solid #1e1e2e' }}>
16
+ <h2 style=\{{ fontSize: '1.125rem', fontWeight: 600, color: '#fafafa' }}>{{businessName}}</h2>
17
+ <div style=\{{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
18
+ <OrgSwitcher />
19
+ <UserButton />
20
+ </div>
21
+ </header>
22
+ <main style=\{{ padding: '2rem' }}>{children}</main>
23
+ </div>
24
+ </ProtectedRoute>
25
+ );
26
+ }
27
+ {{/if}}
28
+ {{/if}}
@@ -0,0 +1,9 @@
1
+ {{#if includeMcpModule}}
2
+ {{#if includeGithatFolder}}
3
+ import { DashboardMcpServers } from '../../../githat/dashboard/mcp-servers{{#unless typescript}}.jsx{{/unless}}';
4
+
5
+ export default function McpPage() {
6
+ return <DashboardMcpServers />;
7
+ }
8
+ {{/if}}
9
+ {{/if}}
@@ -0,0 +1,9 @@
1
+ {{#if includeOrgManagement}}
2
+ {{#if includeGithatFolder}}
3
+ import { DashboardMembers } from '../../../githat/dashboard/members{{#unless typescript}}.jsx{{/unless}}';
4
+
5
+ export default function MembersPage() {
6
+ return <DashboardMembers />;
7
+ }
8
+ {{/if}}
9
+ {{/if}}
@@ -0,0 +1,30 @@
1
+ {{#if includeDashboard}}
2
+ {{#if includeGithatFolder}}
3
+ import { DashboardOverview } from '../../githat/dashboard/overview{{#unless typescript}}.jsx{{/unless}}';
4
+
5
+ export default function DashboardPage() {
6
+ return <DashboardOverview />;
7
+ }
8
+ {{else}}
9
+ 'use client';
10
+
11
+ import { useAuth } from '@githat/nextjs';
12
+
13
+ export default function DashboardPage() {
14
+ const { user, org } = useAuth();
15
+
16
+ return (
17
+ <div>
18
+ <h1 style=\{{ fontSize: '1.5rem', fontWeight: 600, color: '#fafafa', marginBottom: '1rem' }}>
19
+ Welcome{user?.name ? `, ${user.name}` : ''}
20
+ </h1>
21
+ {org && (
22
+ <p style=\{{ color: '#a1a1aa' }}>
23
+ Organization: <strong style=\{{ color: '#7c3aed' }}>{org.name}</strong> ({org.role})
24
+ </p>
25
+ )}
26
+ </div>
27
+ );
28
+ }
29
+ {{/if}}
30
+ {{/if}}
@@ -0,0 +1,9 @@
1
+ {{#if includeOrgManagement}}
2
+ {{#if includeGithatFolder}}
3
+ import { DashboardSettings } from '../../../githat/dashboard/settings{{#unless typescript}}.jsx{{/unless}}';
4
+
5
+ export default function SettingsPage() {
6
+ return <DashboardSettings />;
7
+ }
8
+ {{/if}}
9
+ {{/if}}
@@ -0,0 +1,20 @@
1
+ {{#if useTailwind}}
2
+ @import "tailwindcss";
3
+ {{/if}}
4
+
5
+ * {
6
+ box-sizing: border-box;
7
+ margin: 0;
8
+ padding: 0;
9
+ }
10
+
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
13
+ background: #09090b;
14
+ color: #fafafa;
15
+ }
16
+
17
+ a {
18
+ color: inherit;
19
+ text-decoration: none;
20
+ }