create-githat-app 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +4 -3
- package/package.json +6 -2
- package/templates/agent/app/(auth)/forgot-password/page.tsx.hbs +11 -0
- package/templates/agent/app/(auth)/reset-password/page.tsx.hbs +39 -0
- package/templates/agent/app/(auth)/verify-email/page.tsx.hbs +41 -0
- package/templates/agent/app/admin/agent/page.tsx.hbs +159 -62
- package/templates/agent/app/admin/layout.tsx.hbs +82 -0
- package/templates/agent/app/admin/mcp/page.tsx.hbs +156 -0
- package/templates/agent/app/dashboard/agents/page.tsx.hbs +9 -0
- package/templates/agent/app/dashboard/layout.tsx.hbs +9 -0
- package/templates/agent/app/dashboard/mcp/page.tsx.hbs +9 -0
- package/templates/agent/app/dashboard/page.tsx.hbs +128 -0
- package/templates/agent/app/globals.css.hbs +14 -9
- package/templates/agent/app/layout.tsx.hbs +7 -2
- package/templates/agent/app/page.tsx.hbs +127 -70
- package/templates/agent/app/verify/agent/page.tsx.hbs +124 -0
- package/templates/agent/next.config.ts.hbs +4 -5
- package/templates/agent/public/HERO_IMAGE.md +23 -0
- package/templates/base/README.md.hbs +2 -1
- package/templates/base/githat/api/agents.ts.hbs +6 -6
- package/templates/base/githat/auth/guard.tsx.hbs +6 -6
- package/templates/base/githat/config.ts.hbs +7 -9
- package/templates/base/githat/dashboard/layout.tsx.hbs +6 -6
- package/templates/base/githat/dashboard/overview.tsx.hbs +106 -16
- package/templates/classroom/app/layout.tsx.hbs +6 -1
- package/templates/classroom/app/projects/[id]/feedback/feedback-content.tsx.hbs +153 -0
- package/templates/classroom/app/projects/[id]/feedback/page.tsx.hbs +10 -149
- package/templates/classroom/app/projects/[id]/present/page.tsx.hbs +11 -104
- package/templates/classroom/app/projects/[id]/present/presenter-content.tsx.hbs +118 -0
- package/templates/classroom/next.config.ts.hbs +4 -5
- package/templates/content/app/layout.tsx.hbs +6 -1
- package/templates/content/app/posts/[slug]/page.tsx.hbs +10 -86
- package/templates/content/app/posts/[slug]/post-content.tsx.hbs +93 -0
- package/templates/content/next.config.ts.hbs +4 -5
- package/templates/dashboard/app/admin/data/[entity]/page.tsx.hbs +15 -2
- package/templates/dashboard/app/layout.tsx.hbs +6 -1
- package/templates/dashboard/next.config.ts.hbs +4 -5
- package/templates/fullstack/apps-web-nextjs/app/layout.tsx.hbs +6 -1
- package/templates/fullstack/apps-web-nextjs/next.config.ts.hbs +5 -5
- package/templates/marketplace/app/layout.tsx.hbs +6 -1
- package/templates/marketplace/next.config.ts.hbs +4 -5
- package/templates/nextjs/app/(auth)/forgot-password/page.tsx.hbs +2 -54
- package/templates/nextjs/app/(auth)/reset-password/page.tsx.hbs +8 -75
- package/templates/nextjs/app/globals.css.hbs +71 -1
- package/templates/nextjs/app/layout.tsx.hbs +9 -3
- package/templates/nextjs/app/page.tsx.hbs +6 -3
- package/templates/nextjs/next.config.ts.hbs +4 -5
- package/templates/plain/app/layout.tsx.hbs +6 -1
- package/templates/plain/next.config.ts.hbs +4 -5
- package/templates/portfolio/app/layout.tsx.hbs +6 -1
- package/templates/portfolio/next.config.ts.hbs +4 -5
- package/templates/saas/app/layout.tsx.hbs +6 -1
- package/templates/saas/next.config.ts.hbs +4 -5
- package/templates/agent/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/agent/proxy.ts.hbs +0 -10
- package/templates/classroom/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/classroom/proxy.ts.hbs +0 -10
- package/templates/content/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/content/proxy.ts.hbs +0 -10
- package/templates/dashboard/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/dashboard/proxy.ts.hbs +0 -10
- package/templates/fullstack/apps-web-nextjs/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/marketplace/app/(shop)/[slug]/p/[productId]/page.tsx.hbs +0 -99
- package/templates/marketplace/app/(shop)/[slug]/page.tsx.hbs +0 -90
- package/templates/marketplace/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/marketplace/proxy.ts.hbs +0 -10
- package/templates/nextjs/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/nextjs/proxy.ts.hbs +0 -10
- package/templates/plain/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/plain/proxy.ts.hbs +0 -10
- package/templates/portfolio/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/portfolio/proxy.ts.hbs +0 -10
- package/templates/saas/app/api/githat/[...path]/route.ts.hbs +0 -21
- package/templates/saas/proxy.ts.hbs +0 -10
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
4
|
import { useAuth } from '@githat/nextjs';
|
|
5
|
-
{{#
|
|
5
|
+
{{#ifNext framework}}
|
|
6
6
|
import { useRouter } from 'next/navigation';
|
|
7
7
|
{{else}}
|
|
8
8
|
import { useNavigate } from 'react-router-dom';
|
|
9
|
-
{{/
|
|
9
|
+
{{/ifNext}}
|
|
10
10
|
import { githatConfig } from '../config{{#unless typescript}}.js{{/unless}}';
|
|
11
11
|
|
|
12
12
|
export function AuthGuard({ children, requiredRole }{{#if typescript}}: {
|
|
@@ -14,11 +14,11 @@ export function AuthGuard({ children, requiredRole }{{#if typescript}}: {
|
|
|
14
14
|
requiredRole?: 'owner' | 'admin' | 'member';
|
|
15
15
|
}{{/if}}) {
|
|
16
16
|
const { isSignedIn, isLoading, org } = useAuth();
|
|
17
|
-
{{#
|
|
17
|
+
{{#ifNext framework}}
|
|
18
18
|
const router = useRouter();
|
|
19
19
|
{{else}}
|
|
20
20
|
const navigate = useNavigate();
|
|
21
|
-
{{/
|
|
21
|
+
{{/ifNext}}
|
|
22
22
|
|
|
23
23
|
if (isLoading) {
|
|
24
24
|
return (
|
|
@@ -29,11 +29,11 @@ export function AuthGuard({ children, requiredRole }{{#if typescript}}: {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
if (!isSignedIn) {
|
|
32
|
-
{{#
|
|
32
|
+
{{#ifNext framework}}
|
|
33
33
|
router.push(githatConfig.signInUrl);
|
|
34
34
|
{{else}}
|
|
35
35
|
navigate(githatConfig.signInUrl);
|
|
36
|
-
{{/
|
|
36
|
+
{{/ifNext}}
|
|
37
37
|
return null;
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
{{#if includeGithatFolder}}
|
|
2
2
|
export const githatConfig = {
|
|
3
3
|
appName: '{{businessName}}',
|
|
4
|
-
publishableKey: {{#
|
|
5
|
-
// For
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
// cookies.
|
|
12
|
-
apiUrl: {{#ifEquals framework "nextjs"}}process.env.NEXT_PUBLIC_GITHAT_API_URL || '/api/githat'{{else}}import.meta.env.VITE_GITHAT_API_URL || '{{apiUrl}}'{{/ifEquals}},
|
|
4
|
+
publishableKey: {{#ifNext framework}}process.env.NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY{{else}}import.meta.env.VITE_GITHAT_PUBLISHABLE_KEY{{/ifNext}} || '',
|
|
5
|
+
// For Next.js-shaped frameworks we point at the same-origin proxy
|
|
6
|
+
// mounted at app/api/githat/[...path]/route.ts — that proxy forwards
|
|
7
|
+
// to api.githat.io and re-emits Set-Cookie on this app's domain so
|
|
8
|
+
// cookies are visible to proxy.ts and getAuth(). For react-vite we
|
|
9
|
+
// hit api.githat.io directly (browser-token flows only).
|
|
10
|
+
apiUrl: {{#ifNext framework}}process.env.NEXT_PUBLIC_GITHAT_API_URL || '/api/githat'{{else}}import.meta.env.VITE_GITHAT_API_URL || '{{apiUrl}}'{{/ifNext}},
|
|
13
11
|
signInUrl: '/sign-in',
|
|
14
12
|
signUpUrl: '/sign-up',
|
|
15
13
|
afterSignInUrl: '/dashboard',
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
import { UserButton, OrgSwitcher } from '@githat/nextjs';
|
|
5
5
|
import { AuthGuard } from '../auth/guard{{#unless typescript}}.jsx{{/unless}}';
|
|
6
6
|
import { githatConfig } from '../config{{#unless typescript}}.js{{/unless}}';
|
|
7
|
-
{{#
|
|
7
|
+
{{#ifNext framework}}
|
|
8
8
|
import Link from 'next/link';
|
|
9
9
|
import { usePathname } from 'next/navigation';
|
|
10
10
|
{{else}}
|
|
11
11
|
import { Link, useLocation } from 'react-router-dom';
|
|
12
|
-
{{/
|
|
12
|
+
{{/ifNext}}
|
|
13
13
|
|
|
14
14
|
const navItems = [
|
|
15
15
|
{ label: 'Overview', href: '/dashboard' },
|
|
@@ -27,11 +27,11 @@ const navItems = [
|
|
|
27
27
|
];
|
|
28
28
|
|
|
29
29
|
export function DashboardLayout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
|
|
30
|
-
{{#
|
|
30
|
+
{{#ifNext framework}}
|
|
31
31
|
const pathname = usePathname();
|
|
32
32
|
{{else}}
|
|
33
33
|
const { pathname } = useLocation();
|
|
34
|
-
{{/
|
|
34
|
+
{{/ifNext}}
|
|
35
35
|
|
|
36
36
|
return (
|
|
37
37
|
<AuthGuard>
|
|
@@ -45,11 +45,11 @@ export function DashboardLayout({ children }{{#if typescript}}: { children: Reac
|
|
|
45
45
|
{navItems.map((item) => (
|
|
46
46
|
<Link
|
|
47
47
|
key={item.href}
|
|
48
|
-
{{#
|
|
48
|
+
{{#ifNext framework}}
|
|
49
49
|
href={item.href}
|
|
50
50
|
{{else}}
|
|
51
51
|
to={item.href}
|
|
52
|
-
{{/
|
|
52
|
+
{{/ifNext}}
|
|
53
53
|
style=\{{
|
|
54
54
|
padding: '0.5rem 0.75rem',
|
|
55
55
|
borderRadius: '0.375rem',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{{#unless includeDashboard}}{{else}}
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
|
+
import Link from 'next/link';
|
|
4
5
|
import { useAuth } from '@githat/nextjs';
|
|
5
6
|
|
|
6
7
|
export function DashboardOverview() {
|
|
@@ -8,29 +9,118 @@ export function DashboardOverview() {
|
|
|
8
9
|
|
|
9
10
|
return (
|
|
10
11
|
<div>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
<div style=\{{ marginBottom: '2rem' }}>
|
|
13
|
+
<h1 style=\{{ fontSize: '1.5rem', fontWeight: 700, color: '#fafafa', marginBottom: '0.375rem' }}>
|
|
14
|
+
Welcome back{user?.name ? `, ${user.name}` : ''}
|
|
15
|
+
</h1>
|
|
16
|
+
{org && (
|
|
17
|
+
<p style=\{{ color: '#71717a', fontSize: '0.875rem' }}>
|
|
18
|
+
{org.name} · <span style=\{{ color: '#a1a1aa' }}>{org.role}</span>
|
|
19
|
+
</p>
|
|
20
|
+
)}
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
{/* Stats row */}
|
|
24
|
+
<div style=\{{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(14rem, 1fr))', gap: '1rem', marginBottom: '2.5rem' }}>
|
|
25
|
+
<StatCard label="Status" value="Active" sub="Account health" />
|
|
26
|
+
<StatCard label="Plan" value={org?.tier ?? 'Free'} sub="Current tier" />
|
|
27
|
+
<StatCard label="Identity" value="GitHat" sub="Auth provider" />
|
|
28
|
+
{{#if includeOrgManagement}}
|
|
29
|
+
<StatCard label="Members" value={String(org?.memberCount ?? 1)} sub="Team size" />
|
|
30
|
+
{{/if}}
|
|
31
|
+
</div>
|
|
19
32
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
33
|
+
{/* Quick actions */}
|
|
34
|
+
<div style=\{{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(20rem, 1fr))', gap: '1rem' }}>
|
|
35
|
+
{{#if includeOrgManagement}}
|
|
36
|
+
<ActionCard
|
|
37
|
+
title="Invite team members"
|
|
38
|
+
desc="Add your business partner, accountant, or employee to your org."
|
|
39
|
+
cta="Invite"
|
|
40
|
+
href="/dashboard/members"
|
|
41
|
+
accent="#6366f1"
|
|
42
|
+
/>
|
|
43
|
+
<ActionCard
|
|
44
|
+
title="Organization settings"
|
|
45
|
+
desc="Update your org name, billing plan, and member permissions."
|
|
46
|
+
cta="Settings"
|
|
47
|
+
href="/dashboard/settings"
|
|
48
|
+
accent="#8b5cf6"
|
|
49
|
+
/>
|
|
50
|
+
{{/if}}
|
|
51
|
+
{{#if includeMcpModule}}
|
|
52
|
+
<ActionCard
|
|
53
|
+
title="Connect an MCP server"
|
|
54
|
+
desc="Hook your data into any AI assistant via the Model Context Protocol."
|
|
55
|
+
cta="Connect"
|
|
56
|
+
href="/dashboard/mcp"
|
|
57
|
+
accent="#0ea5e9"
|
|
58
|
+
/>
|
|
59
|
+
{{/if}}
|
|
60
|
+
{{#if includeAgentModule}}
|
|
61
|
+
<ActionCard
|
|
62
|
+
title="Deploy an AI agent"
|
|
63
|
+
desc="Register a wallet-bound autonomous agent with capability scoping and a kill switch."
|
|
64
|
+
cta="Deploy"
|
|
65
|
+
href="/dashboard/agents"
|
|
66
|
+
accent="#10b981"
|
|
67
|
+
/>
|
|
68
|
+
{{/if}}
|
|
69
|
+
<ActionCard
|
|
70
|
+
title="Manage apps"
|
|
71
|
+
desc="View and rotate your publishable keys, configure allowed origins."
|
|
72
|
+
cta="View apps"
|
|
73
|
+
href="/dashboard/apps"
|
|
74
|
+
accent="#f59e0b"
|
|
75
|
+
/>
|
|
24
76
|
</div>
|
|
25
77
|
</div>
|
|
26
78
|
);
|
|
27
79
|
}
|
|
28
80
|
|
|
29
|
-
function StatCard({
|
|
81
|
+
function StatCard({ label, value, sub }{{#if typescript}}: { label: string; value: string; sub: string }{{/if}}) {
|
|
82
|
+
return (
|
|
83
|
+
<div style=\{{
|
|
84
|
+
background: '#111113',
|
|
85
|
+
border: '1px solid #1e1e2e',
|
|
86
|
+
borderRadius: '0.75rem',
|
|
87
|
+
padding: '1.5rem',
|
|
88
|
+
}}>
|
|
89
|
+
<p style=\{{ fontSize: '0.8125rem', color: '#71717a', marginBottom: '0.375rem' }}>{label}</p>
|
|
90
|
+
<p style=\{{ fontSize: '1.5rem', fontWeight: 700, color: '#fafafa', marginBottom: '0.25rem' }}>{value}</p>
|
|
91
|
+
<p style=\{{ fontSize: '0.75rem', color: '#52525b' }}>{sub}</p>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function ActionCard({ title, desc, cta, href, accent }{{#if typescript}}: {
|
|
97
|
+
title: string; desc: string; cta: string; href: string; accent: string;
|
|
98
|
+
}{{/if}}) {
|
|
30
99
|
return (
|
|
31
|
-
<div style=\{{
|
|
32
|
-
|
|
33
|
-
|
|
100
|
+
<div style=\{{
|
|
101
|
+
background: '#111113',
|
|
102
|
+
border: '1px solid #1e1e2e',
|
|
103
|
+
borderRadius: '0.75rem',
|
|
104
|
+
padding: '1.5rem',
|
|
105
|
+
display: 'flex',
|
|
106
|
+
flexDirection: 'column',
|
|
107
|
+
gap: '0.75rem',
|
|
108
|
+
}}>
|
|
109
|
+
<h3 style=\{{ fontSize: '0.9375rem', fontWeight: 600, color: '#fafafa' }}>{title}</h3>
|
|
110
|
+
<p style=\{{ fontSize: '0.875rem', color: '#71717a', lineHeight: 1.6, flex: 1 }}>{desc}</p>
|
|
111
|
+
<Link href={href} style=\{{
|
|
112
|
+
display: 'inline-block',
|
|
113
|
+
background: accent,
|
|
114
|
+
color: '#fff',
|
|
115
|
+
textDecoration: 'none',
|
|
116
|
+
padding: '0.5rem 1rem',
|
|
117
|
+
borderRadius: '0.375rem',
|
|
118
|
+
fontSize: '0.875rem',
|
|
119
|
+
fontWeight: 600,
|
|
120
|
+
alignSelf: 'flex-start',
|
|
121
|
+
}}>
|
|
122
|
+
{cta} →
|
|
123
|
+
</Link>
|
|
34
124
|
</div>
|
|
35
125
|
);
|
|
36
126
|
}
|
|
@@ -19,7 +19,12 @@ export default function RootLayout({ children }{{#if typescript}}: { children: R
|
|
|
19
19
|
*/}
|
|
20
20
|
<GitHatProvider config=\{{
|
|
21
21
|
publishableKey: process.env.NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY || '',
|
|
22
|
-
apiUrl: '
|
|
22
|
+
apiUrl: 'https://api.githat.io',
|
|
23
|
+
{{#if typescript}}
|
|
24
|
+
tokenStorage: 'localStorage' as const,
|
|
25
|
+
{{else}}
|
|
26
|
+
tokenStorage: 'localStorage',
|
|
27
|
+
{{/if}}
|
|
23
28
|
signInUrl: '/sign-in',
|
|
24
29
|
signUpUrl: '/sign-up',
|
|
25
30
|
afterSignInUrl: '/',
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
|
|
6
|
+
{{#if typescript}}
|
|
7
|
+
interface FeedbackContentProps {
|
|
8
|
+
id: string;
|
|
9
|
+
}
|
|
10
|
+
{{/if}}
|
|
11
|
+
|
|
12
|
+
export default function FeedbackContent({ id }{{#if typescript}}: FeedbackContentProps{{/if}}) {
|
|
13
|
+
const [submitted, setSubmitted] = useState(false);
|
|
14
|
+
const [oneThing, setOneThing] = useState('');
|
|
15
|
+
const [stuck, setStuck] = useState('');
|
|
16
|
+
const [scale, setScale] = useState(7);
|
|
17
|
+
|
|
18
|
+
if (submitted) {
|
|
19
|
+
return (
|
|
20
|
+
<div style=\{{
|
|
21
|
+
background: 'var(--bg)',
|
|
22
|
+
color: 'var(--fg)',
|
|
23
|
+
minHeight: 'calc(100vh - 64px)',
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
padding: 'var(--space-4)',
|
|
28
|
+
}}>
|
|
29
|
+
<div style=\{{ maxWidth: '32rem', textAlign: 'center' }}>
|
|
30
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-3)' }}>
|
|
31
|
+
¡Gracias!
|
|
32
|
+
</h1>
|
|
33
|
+
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-4)' }}>
|
|
34
|
+
The presenter sees your feedback live on their screen.
|
|
35
|
+
You can submit again with a new thought any time.
|
|
36
|
+
</p>
|
|
37
|
+
<button
|
|
38
|
+
onClick={() => { setSubmitted(false); setOneThing(''); setStuck(''); setScale(7); }}
|
|
39
|
+
style=\{{
|
|
40
|
+
padding: 'var(--space-3) var(--space-5)',
|
|
41
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
42
|
+
background: 'var(--primary)',
|
|
43
|
+
color: 'var(--bg)',
|
|
44
|
+
border: 'none',
|
|
45
|
+
fontWeight: 600,
|
|
46
|
+
cursor: 'pointer',
|
|
47
|
+
}}
|
|
48
|
+
>
|
|
49
|
+
Add another thought
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)', padding: 'var(--space-8) var(--space-4)' }}>
|
|
58
|
+
<div style=\{{ maxWidth: '32rem', margin: '0 auto' }}>
|
|
59
|
+
<Link href={`/projects/${id}`} style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem', display: 'inline-block', marginBottom: 'var(--space-4)' }}>
|
|
60
|
+
← Project
|
|
61
|
+
</Link>
|
|
62
|
+
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '1.75rem', marginBottom: 'var(--space-2)' }}>
|
|
63
|
+
Tell them what you think
|
|
64
|
+
</h1>
|
|
65
|
+
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-6)' }}>
|
|
66
|
+
No account, no email. Just three quick questions while
|
|
67
|
+
they're presenting.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
<form
|
|
71
|
+
onSubmit={(e) => {
|
|
72
|
+
e.preventDefault();
|
|
73
|
+
/* TODO: POST /api/projects/[id]/feedback { oneThing, stuck, scale } */
|
|
74
|
+
setSubmitted(true);
|
|
75
|
+
}}
|
|
76
|
+
style=\{{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}
|
|
77
|
+
>
|
|
78
|
+
<Field label="One thing that landed for you">
|
|
79
|
+
<input
|
|
80
|
+
type="text"
|
|
81
|
+
required
|
|
82
|
+
value={oneThing}
|
|
83
|
+
onChange={(e) => setOneThing(e.target.value)}
|
|
84
|
+
placeholder="The thing you'll remember tomorrow"
|
|
85
|
+
style=\{{
|
|
86
|
+
width: '100%',
|
|
87
|
+
padding: 'var(--space-3)',
|
|
88
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
89
|
+
border: '1px solid var(--border)',
|
|
90
|
+
background: 'var(--surface)',
|
|
91
|
+
color: 'var(--fg)',
|
|
92
|
+
fontSize: '1rem',
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
</Field>
|
|
96
|
+
|
|
97
|
+
<Field label="One thing you'd want them to dig into more">
|
|
98
|
+
<textarea
|
|
99
|
+
value={stuck}
|
|
100
|
+
onChange={(e) => setStuck(e.target.value)}
|
|
101
|
+
rows={3}
|
|
102
|
+
placeholder="A question, a doubt, a what-if"
|
|
103
|
+
style=\{{
|
|
104
|
+
width: '100%',
|
|
105
|
+
padding: 'var(--space-3)',
|
|
106
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
107
|
+
border: '1px solid var(--border)',
|
|
108
|
+
background: 'var(--surface)',
|
|
109
|
+
color: 'var(--fg)',
|
|
110
|
+
fontSize: '1rem',
|
|
111
|
+
fontFamily: 'inherit',
|
|
112
|
+
resize: 'vertical',
|
|
113
|
+
}}
|
|
114
|
+
/>
|
|
115
|
+
</Field>
|
|
116
|
+
|
|
117
|
+
<Field label={`How likely are you to share this with someone? (${scale}/10)`}>
|
|
118
|
+
<input
|
|
119
|
+
type="range"
|
|
120
|
+
min={1}
|
|
121
|
+
max={10}
|
|
122
|
+
value={scale}
|
|
123
|
+
onChange={(e) => setScale(Number(e.target.value))}
|
|
124
|
+
style=\{{ width: '100%' }}
|
|
125
|
+
/>
|
|
126
|
+
</Field>
|
|
127
|
+
|
|
128
|
+
<button type="submit" style=\{{
|
|
129
|
+
padding: 'var(--space-3) var(--space-5)',
|
|
130
|
+
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
131
|
+
background: 'var(--primary)',
|
|
132
|
+
color: 'var(--bg)',
|
|
133
|
+
border: 'none',
|
|
134
|
+
fontWeight: 600,
|
|
135
|
+
fontSize: '1rem',
|
|
136
|
+
cursor: 'pointer',
|
|
137
|
+
}}>
|
|
138
|
+
Send feedback
|
|
139
|
+
</button>
|
|
140
|
+
</form>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function Field({ label, children }{{#if typescript}}: { label: string; children: React.ReactNode }{{/if}}) {
|
|
147
|
+
return (
|
|
148
|
+
<label style=\{{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)' }}>
|
|
149
|
+
<span style=\{{ fontSize: '0.875rem', fontWeight: 600 }}>{label}</span>
|
|
150
|
+
{children}
|
|
151
|
+
</label>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -1,159 +1,20 @@
|
|
|
1
|
-
'
|
|
2
|
-
|
|
3
|
-
import { use, useState } from 'react';
|
|
4
|
-
import Link from 'next/link';
|
|
1
|
+
import FeedbackContent from './feedback-content{{#unless typescript}}.jsx{{/unless}}';
|
|
5
2
|
|
|
6
3
|
/**
|
|
7
4
|
* Audience feedback form — `/projects/[id]/feedback`.
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* The form questions are deliberately short — long forms kill
|
|
14
|
-
* response rates and the value of this template is volume of
|
|
15
|
-
* feedback over time, not depth per response.
|
|
6
|
+
* generateStaticParams tells Next.js which ids to pre-render at build
|
|
7
|
+
* time (required for `output: "export"`). FeedbackContent is a client
|
|
8
|
+
* component that renders the form with React state.
|
|
16
9
|
*/
|
|
17
|
-
export default function FeedbackPage({ params }: { params: Promise<{ id: string }> }) {
|
|
18
|
-
const { id } = use(params);
|
|
19
|
-
const [submitted, setSubmitted] = useState(false);
|
|
20
|
-
const [oneThing, setOneThing] = useState('');
|
|
21
|
-
const [stuck, setStuck] = useState('');
|
|
22
|
-
const [scale, setScale] = useState(7);
|
|
23
|
-
|
|
24
|
-
if (submitted) {
|
|
25
|
-
return (
|
|
26
|
-
<div style=\{{
|
|
27
|
-
background: 'var(--bg)',
|
|
28
|
-
color: 'var(--fg)',
|
|
29
|
-
minHeight: 'calc(100vh - 64px)',
|
|
30
|
-
display: 'flex',
|
|
31
|
-
alignItems: 'center',
|
|
32
|
-
justifyContent: 'center',
|
|
33
|
-
padding: 'var(--space-4)',
|
|
34
|
-
}}>
|
|
35
|
-
<div style=\{{ maxWidth: '32rem', textAlign: 'center' }}>
|
|
36
|
-
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-3)' }}>
|
|
37
|
-
¡Gracias!
|
|
38
|
-
</h1>
|
|
39
|
-
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-4)' }}>
|
|
40
|
-
The presenter sees your feedback live on their screen.
|
|
41
|
-
You can submit again with a new thought any time.
|
|
42
|
-
</p>
|
|
43
|
-
<button
|
|
44
|
-
onClick={() => { setSubmitted(false); setOneThing(''); setStuck(''); setScale(7); }}
|
|
45
|
-
style=\{{
|
|
46
|
-
padding: 'var(--space-3) var(--space-5)',
|
|
47
|
-
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
48
|
-
background: 'var(--primary)',
|
|
49
|
-
color: 'var(--bg)',
|
|
50
|
-
border: 'none',
|
|
51
|
-
fontWeight: 600,
|
|
52
|
-
cursor: 'pointer',
|
|
53
|
-
}}
|
|
54
|
-
>
|
|
55
|
-
Add another thought
|
|
56
|
-
</button>
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return (
|
|
63
|
-
<div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)', padding: 'var(--space-8) var(--space-4)' }}>
|
|
64
|
-
<div style=\{{ maxWidth: '32rem', margin: '0 auto' }}>
|
|
65
|
-
<Link href={`/projects/${id}`} style=\{{ color: 'var(--fg-muted)', fontSize: '0.875rem', display: 'inline-block', marginBottom: 'var(--space-4)' }}>
|
|
66
|
-
← Project
|
|
67
|
-
</Link>
|
|
68
|
-
<h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '1.75rem', marginBottom: 'var(--space-2)' }}>
|
|
69
|
-
Tell them what you think
|
|
70
|
-
</h1>
|
|
71
|
-
<p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-6)' }}>
|
|
72
|
-
No account, no email. Just three quick questions while
|
|
73
|
-
they're presenting.
|
|
74
|
-
</p>
|
|
75
|
-
|
|
76
|
-
<form
|
|
77
|
-
onSubmit={(e) => {
|
|
78
|
-
e.preventDefault();
|
|
79
|
-
/* TODO: POST /api/projects/[id]/feedback { oneThing, stuck, scale } */
|
|
80
|
-
setSubmitted(true);
|
|
81
|
-
}}
|
|
82
|
-
style=\{{ display: 'flex', flexDirection: 'column', gap: 'var(--space-5)' }}
|
|
83
|
-
>
|
|
84
|
-
<Field label="One thing that landed for you">
|
|
85
|
-
<input
|
|
86
|
-
type="text"
|
|
87
|
-
required
|
|
88
|
-
value={oneThing}
|
|
89
|
-
onChange={(e) => setOneThing(e.target.value)}
|
|
90
|
-
placeholder="The thing you'll remember tomorrow"
|
|
91
|
-
style=\{{
|
|
92
|
-
width: '100%',
|
|
93
|
-
padding: 'var(--space-3)',
|
|
94
|
-
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
95
|
-
border: '1px solid var(--border)',
|
|
96
|
-
background: 'var(--surface)',
|
|
97
|
-
color: 'var(--fg)',
|
|
98
|
-
fontSize: '1rem',
|
|
99
|
-
}}
|
|
100
|
-
/>
|
|
101
|
-
</Field>
|
|
102
|
-
|
|
103
|
-
<Field label="One thing you'd want them to dig into more">
|
|
104
|
-
<textarea
|
|
105
|
-
value={stuck}
|
|
106
|
-
onChange={(e) => setStuck(e.target.value)}
|
|
107
|
-
rows={3}
|
|
108
|
-
placeholder="A question, a doubt, a what-if"
|
|
109
|
-
style=\{{
|
|
110
|
-
width: '100%',
|
|
111
|
-
padding: 'var(--space-3)',
|
|
112
|
-
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
113
|
-
border: '1px solid var(--border)',
|
|
114
|
-
background: 'var(--surface)',
|
|
115
|
-
color: 'var(--fg)',
|
|
116
|
-
fontSize: '1rem',
|
|
117
|
-
fontFamily: 'inherit',
|
|
118
|
-
resize: 'vertical',
|
|
119
|
-
}}
|
|
120
|
-
/>
|
|
121
|
-
</Field>
|
|
122
10
|
|
|
123
|
-
|
|
124
|
-
<input
|
|
125
|
-
type="range"
|
|
126
|
-
min={1}
|
|
127
|
-
max={10}
|
|
128
|
-
value={scale}
|
|
129
|
-
onChange={(e) => setScale(Number(e.target.value))}
|
|
130
|
-
style=\{{ width: '100%' }}
|
|
131
|
-
/>
|
|
132
|
-
</Field>
|
|
11
|
+
const SAMPLE_PROJECT_IDS = ['sustainable-bodega', 'after-school-tutor', 'community-fridge'];
|
|
133
12
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
borderRadius: 'var(--radius-md, 0.5rem)',
|
|
137
|
-
background: 'var(--primary)',
|
|
138
|
-
color: 'var(--bg)',
|
|
139
|
-
border: 'none',
|
|
140
|
-
fontWeight: 600,
|
|
141
|
-
fontSize: '1rem',
|
|
142
|
-
cursor: 'pointer',
|
|
143
|
-
}}>
|
|
144
|
-
Send feedback
|
|
145
|
-
</button>
|
|
146
|
-
</form>
|
|
147
|
-
</div>
|
|
148
|
-
</div>
|
|
149
|
-
);
|
|
13
|
+
export function generateStaticParams() {
|
|
14
|
+
return SAMPLE_PROJECT_IDS.map((id) => ({ id }));
|
|
150
15
|
}
|
|
151
16
|
|
|
152
|
-
function
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<span style=\{{ fontSize: '0.875rem', fontWeight: 600 }}>{label}</span>
|
|
156
|
-
{children}
|
|
157
|
-
</label>
|
|
158
|
-
);
|
|
17
|
+
export default async function FeedbackPage({ params }: { params: Promise<{ id: string }> }) {
|
|
18
|
+
const { id } = await params;
|
|
19
|
+
return <FeedbackContent id={id} />;
|
|
159
20
|
}
|