create-githat-app 1.8.5 → 1.8.7

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 (46) hide show
  1. package/dist/cli.js +2 -2
  2. package/package.json +1 -1
  3. package/templates/agent/app/account/activity/page.tsx.hbs +176 -0
  4. package/templates/agent/app/account/files/page.tsx.hbs +241 -0
  5. package/templates/agent/app/account/security/page.tsx.hbs +46 -0
  6. package/templates/agent/app/account/sessions/page.tsx.hbs +180 -0
  7. package/templates/base/README.md.hbs +111 -0
  8. package/templates/classroom/app/account/activity/page.tsx.hbs +176 -0
  9. package/templates/classroom/app/account/files/page.tsx.hbs +241 -0
  10. package/templates/classroom/app/account/security/page.tsx.hbs +46 -0
  11. package/templates/classroom/app/account/sessions/page.tsx.hbs +180 -0
  12. package/templates/content/app/account/activity/page.tsx.hbs +176 -0
  13. package/templates/content/app/account/files/page.tsx.hbs +241 -0
  14. package/templates/content/app/account/security/page.tsx.hbs +46 -0
  15. package/templates/content/app/account/sessions/page.tsx.hbs +180 -0
  16. package/templates/dashboard/app/account/activity/page.tsx.hbs +176 -0
  17. package/templates/dashboard/app/account/files/page.tsx.hbs +241 -0
  18. package/templates/dashboard/app/account/security/page.tsx.hbs +46 -0
  19. package/templates/dashboard/app/account/sessions/page.tsx.hbs +180 -0
  20. package/templates/marketplace/app/account/activity/page.tsx.hbs +176 -0
  21. package/templates/marketplace/app/account/files/page.tsx.hbs +241 -0
  22. package/templates/marketplace/app/account/security/page.tsx.hbs +46 -0
  23. package/templates/marketplace/app/account/sessions/page.tsx.hbs +180 -0
  24. package/templates/marketplace/app/admin/page.tsx.hbs +19 -19
  25. package/templates/marketplace/app/cart/page.tsx.hbs +23 -21
  26. package/templates/marketplace/app/layout.tsx.hbs +7 -7
  27. package/templates/marketplace/app/page.tsx.hbs +82 -31
  28. package/templates/marketplace/app/sell/page.tsx.hbs +17 -18
  29. package/templates/marketplace/src/data/products.ts.hbs +64 -0
  30. package/templates/marketplace/src/lib/categories.ts.hbs +17 -23
  31. package/templates/nextjs/app/account/activity/page.tsx.hbs +176 -0
  32. package/templates/nextjs/app/account/files/page.tsx.hbs +241 -0
  33. package/templates/nextjs/app/account/security/page.tsx.hbs +46 -0
  34. package/templates/nextjs/app/account/sessions/page.tsx.hbs +180 -0
  35. package/templates/plain/app/account/activity/page.tsx.hbs +176 -0
  36. package/templates/plain/app/account/files/page.tsx.hbs +241 -0
  37. package/templates/plain/app/account/security/page.tsx.hbs +46 -0
  38. package/templates/plain/app/account/sessions/page.tsx.hbs +180 -0
  39. package/templates/portfolio/app/account/activity/page.tsx.hbs +176 -0
  40. package/templates/portfolio/app/account/files/page.tsx.hbs +241 -0
  41. package/templates/portfolio/app/account/security/page.tsx.hbs +46 -0
  42. package/templates/portfolio/app/account/sessions/page.tsx.hbs +180 -0
  43. package/templates/saas/app/account/activity/page.tsx.hbs +176 -0
  44. package/templates/saas/app/account/files/page.tsx.hbs +241 -0
  45. package/templates/saas/app/account/security/page.tsx.hbs +46 -0
  46. package/templates/saas/app/account/sessions/page.tsx.hbs +180 -0
@@ -0,0 +1,180 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import { useSessions } from '@githat/nextjs';
5
+ import type { Session } from '@githat/nextjs';
6
+
7
+ /**
8
+ * /account/sessions — Active session management for {{businessName}}.
9
+ *
10
+ * Lists all active sessions with device/IP/country info.
11
+ * Users can revoke individual sessions or sign out everywhere else.
12
+ */
13
+ export default function SessionsPage() {
14
+ const { list, revoke, revokeAll } = useSessions();
15
+ const [sessions, setSessions] = useState<Session[]>([]);
16
+ const [loading, setLoading] = useState(true);
17
+ const [error, setError] = useState<string | null>(null);
18
+ const [revoking, setRevoking] = useState<string | null>(null);
19
+ const [revokingAll, setRevokingAll] = useState(false);
20
+
21
+ async function load() {
22
+ setLoading(true);
23
+ setError(null);
24
+ try {
25
+ const { sessions: data } = await list();
26
+ setSessions(data);
27
+ } catch (err: unknown) {
28
+ setError(err instanceof Error ? err.message : 'Failed to load sessions');
29
+ } finally {
30
+ setLoading(false);
31
+ }
32
+ }
33
+
34
+ useEffect(() => { load(); }, []);
35
+
36
+ async function handleRevoke(sessionId: string) {
37
+ setRevoking(sessionId);
38
+ try {
39
+ await revoke(sessionId);
40
+ setSessions((prev) => prev.filter((s) => s.id !== sessionId));
41
+ } catch (err: unknown) {
42
+ setError(err instanceof Error ? err.message : 'Failed to revoke session');
43
+ } finally {
44
+ setRevoking(null);
45
+ }
46
+ }
47
+
48
+ async function handleRevokeAll() {
49
+ if (!confirm('Sign out of all other devices?')) return;
50
+ setRevokingAll(true);
51
+ try {
52
+ await revokeAll();
53
+ setSessions((prev) => prev.filter((s) => s.current));
54
+ } catch (err: unknown) {
55
+ setError(err instanceof Error ? err.message : 'Failed to revoke sessions');
56
+ } finally {
57
+ setRevokingAll(false);
58
+ }
59
+ }
60
+
61
+ const otherCount = sessions.filter((s) => !s.current).length;
62
+
63
+ return (
64
+ <main
65
+ style=\{{
66
+ maxWidth: '720px',
67
+ margin: '0 auto',
68
+ padding: 'var(--space-8, 2rem) var(--space-4, 1rem)',
69
+ color: 'var(--fg, #111)',
70
+ }}
71
+ >
72
+ <header style=\{{ marginBottom: '1.5rem' }}>
73
+ <div style=\{{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.25rem' }}>
74
+ <a href="/account/security" style=\{{ color: 'var(--fg-muted, #666)', textDecoration: 'none', fontSize: '0.875rem' }}>
75
+ Security
76
+ </a>
77
+ <span style=\{{ color: 'var(--fg-muted, #666)' }}>›</span>
78
+ <span style=\{{ fontSize: '0.875rem' }}>Active sessions</span>
79
+ </div>
80
+ <h1 style=\{{ fontSize: '1.875rem', fontWeight: 700, margin: 0 }}>Active sessions</h1>
81
+ <p style=\{{ color: 'var(--fg-muted, #666)', marginTop: '0.25rem' }}>
82
+ Devices signed in to your {{businessName}} account.
83
+ </p>
84
+ </header>
85
+
86
+ {error && (
87
+ <div style=\{{ padding: '0.75rem 1rem', background: '#fef2f2', border: '1px solid #fecaca', borderRadius: '6px', color: '#dc2626', marginBottom: '1rem' }}>
88
+ {error}
89
+ </div>
90
+ )}
91
+
92
+ {otherCount > 0 && (
93
+ <div style=\{{ marginBottom: '1.5rem' }}>
94
+ <button
95
+ onClick={handleRevokeAll}
96
+ disabled={revokingAll}
97
+ style=\{{
98
+ padding: '0.5rem 1.25rem',
99
+ background: '#fef2f2',
100
+ color: '#dc2626',
101
+ border: '1px solid #fecaca',
102
+ borderRadius: '6px',
103
+ cursor: revokingAll ? 'not-allowed' : 'pointer',
104
+ fontWeight: 500,
105
+ }}
106
+ >
107
+ {revokingAll ? 'Signing out...' : `Sign out everywhere else (${otherCount})`}
108
+ </button>
109
+ </div>
110
+ )}
111
+
112
+ {loading ? (
113
+ <p style=\{{ color: 'var(--fg-muted, #666)' }}>Loading sessions...</p>
114
+ ) : sessions.length === 0 ? (
115
+ <p style=\{{ color: 'var(--fg-muted, #666)' }}>No active sessions found.</p>
116
+ ) : (
117
+ <div style=\{{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
118
+ {sessions.map((session) => (
119
+ <div
120
+ key={session.id}
121
+ style=\{{
122
+ padding: '1rem 1.25rem',
123
+ border: session.current ? '1px solid #6366f1' : '1px solid var(--border, #e5e7eb)',
124
+ borderRadius: '8px',
125
+ display: 'flex',
126
+ justifyContent: 'space-between',
127
+ alignItems: 'flex-start',
128
+ gap: '1rem',
129
+ }}
130
+ >
131
+ <div style=\{{ flex: 1, minWidth: 0 }}>
132
+ <div style=\{{ display: 'flex', alignItems: 'center', gap: '0.5rem', marginBottom: '0.25rem' }}>
133
+ <span style=\{{ fontWeight: 600, fontSize: '0.9rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
134
+ {session.userAgent || 'Unknown device'}
135
+ </span>
136
+ {session.current && (
137
+ <span style=\{{
138
+ padding: '0.1rem 0.5rem',
139
+ background: '#ede9fe',
140
+ color: '#6366f1',
141
+ borderRadius: '999px',
142
+ fontSize: '0.75rem',
143
+ fontWeight: 600,
144
+ whiteSpace: 'nowrap',
145
+ }}>
146
+ This device
147
+ </span>
148
+ )}
149
+ </div>
150
+ <div style=\{{ fontSize: '0.8rem', color: 'var(--fg-muted, #666)', display: 'flex', gap: '0.75rem', flexWrap: 'wrap' }}>
151
+ {session.ipAddress && <span>{session.ipAddress}</span>}
152
+ {session.country && <span>{session.country}</span>}
153
+ <span>Last active {new Date(session.lastActiveAt).toLocaleDateString()}</span>
154
+ </div>
155
+ </div>
156
+ {!session.current && (
157
+ <button
158
+ onClick={() => handleRevoke(session.id)}
159
+ disabled={revoking === session.id}
160
+ style=\{{
161
+ padding: '0.375rem 0.875rem',
162
+ background: 'transparent',
163
+ border: '1px solid var(--border, #e5e7eb)',
164
+ borderRadius: '6px',
165
+ cursor: revoking === session.id ? 'not-allowed' : 'pointer',
166
+ fontSize: '0.875rem',
167
+ whiteSpace: 'nowrap',
168
+ flexShrink: 0,
169
+ }}
170
+ >
171
+ {revoking === session.id ? 'Revoking...' : 'Revoke'}
172
+ </button>
173
+ )}
174
+ </div>
175
+ ))}
176
+ </div>
177
+ )}
178
+ </main>
179
+ );
180
+ }
@@ -5,15 +5,15 @@ import { useAuth } from '@githat/nextjs';
5
5
  /**
6
6
  * Seller dashboard — `/admin`.
7
7
  *
8
- * Auth-gated: a logged-in colmadero lands here to manage products,
9
- * orders, hours, and bank payouts. This is a placeholder shell —
10
- * wire the data fetches in your own backend.
8
+ * Auth-gated: a signed-in seller lands here to manage products,
9
+ * orders, and bank payouts. This is a placeholder shell —
10
+ * wire the data fetches to your own backend.
11
11
  *
12
- * The four sections below mirror what a real bodega owner asks first:
13
- * 1. ¿Quién me debe? (today's pending orders)
14
- * 2. ¿Qué se está vendiendo? (this week's revenue)
15
- * 3. ¿Qué tengo en stock? (inventory)
16
- * 4. ¿Cuándo me pagan? (next payout date)
12
+ * The four tiles below reflect what a seller typically wants to know:
13
+ * 1. Pending orders (how many need fulfilling today)
14
+ * 2. Revenue this week
15
+ * 3. Active products (in stock)
16
+ * 4. Next payout date (via Sebastn)
17
17
  */
18
18
  export default function AdminPage() {
19
19
  const { isSignedIn, user, isLoading } = useAuth();
@@ -29,10 +29,10 @@ export default function AdminPage() {
29
29
  <div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)' }}>
30
30
  <div style=\{{ maxWidth: '64rem', margin: '0 auto', padding: 'var(--space-8) var(--space-4)' }}>
31
31
  <h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-2)' }}>
32
- Hola, {user?.name || 'colmadero'}.
32
+ Welcome back, {user?.name || 'seller'}.
33
33
  </h1>
34
34
  <p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-8)' }}>
35
- Esto es lo que está pasando hoy en tu colmado.
35
+ Here's what's happening in your store today.
36
36
  </p>
37
37
 
38
38
  <div style=\{{
@@ -40,17 +40,17 @@ export default function AdminPage() {
40
40
  gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))',
41
41
  gap: 'var(--space-4)',
42
42
  }}>
43
- <Stat label="Pedidos pendientes" value="—" hint="Today's open orders" />
44
- <Stat label="Ventas esta semana" value="$—" hint="This week's revenue" />
45
- <Stat label="Productos activos" value="—" hint="In stock right now" />
46
- <Stat label="Próximo pago" value="—" hint="Next bank payout" />
43
+ <Stat label="Pending orders" value="—" hint="Today's open orders" />
44
+ <Stat label="Revenue this week" value="$—" hint="This week's sales" />
45
+ <Stat label="Active products" value="—" hint="In stock right now" />
46
+ <Stat label="Next payout" value="—" hint="Next bank payout via Sebastn" />
47
47
  </div>
48
48
 
49
49
  <p style=\{{ marginTop: 'var(--space-12)', fontSize: '0.875rem', color: 'var(--fg-subtle)' }}>
50
50
  This dashboard is a starter shell — wire each tile to your
51
51
  real data layer (Postgres, DynamoDB, Sebastn webhooks).
52
- See <code>src/lib/anon-session.ts</code> for the cart-side
53
- model.
52
+ Products are configured in <code>src/data/products.ts</code>.
53
+ See <code>src/lib/anon-session.ts</code> for the guest cart model.
54
54
  </p>
55
55
  </div>
56
56
  </div>
@@ -75,7 +75,7 @@ function Stat({ label, value, hint }: { label: string; value: string; hint: stri
75
75
  function Loading() {
76
76
  return (
77
77
  <div style=\{{ display: 'flex', minHeight: 'calc(100vh - 64px)', alignItems: 'center', justifyContent: 'center', color: 'var(--fg-muted)' }}>
78
- Un momentico
78
+ Loading
79
79
  </div>
80
80
  );
81
81
  }
@@ -84,9 +84,9 @@ function SignInPrompt() {
84
84
  return (
85
85
  <div style=\{{ display: 'flex', minHeight: 'calc(100vh - 64px)', alignItems: 'center', justifyContent: 'center' }}>
86
86
  <div style=\{{ textAlign: 'center', maxWidth: '24rem' }}>
87
- <h2 style=\{{ fontFamily: 'var(--font-wordmark)', marginBottom: 'var(--space-3)' }}>Inicia sesión</h2>
87
+ <h2 style=\{{ fontFamily: 'var(--font-wordmark)', marginBottom: 'var(--space-3)' }}>Sign in to continue</h2>
88
88
  <p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-4)' }}>
89
- Sign in to see your colmado's dashboard.
89
+ Sign in to see your store's dashboard.
90
90
  </p>
91
91
  <a href="/sign-in" style=\{{ color: 'var(--primary)' }}>Sign in →</a>
92
92
  </div>
@@ -5,28 +5,30 @@ import Link from 'next/link';
5
5
  import { SignInButton, SignUpButton, useAuth } from '@githat/nextjs';
6
6
 
7
7
  /**
8
- * Tu funda — anonymous-aware cart.
8
+ * Cart page — anonymous-aware.
9
9
  *
10
10
  * Three checkout choices, in the order they should appear:
11
11
  * 1. Continue as guest (the DEFAULT) — primary button.
12
12
  * 2. Sign in (existing GitHat user).
13
- * 3. Sign up (new GitHat user, gets cart history).
13
+ * 3. Sign up (new GitHat user, gets order history).
14
14
  *
15
- * The order matters: the colmado UX promise is "you don't have to
16
- * make an account to shop." If you're tempted to make sign-up the
17
- * primary button "for the data," re-read CULTURE.md.
15
+ * The order matters: the UX promise is "you don't have to make an
16
+ * account to shop." Guest checkout is the primary path.
18
17
  *
19
18
  * Real cart line items should come from your backend (read by the
20
- * anon-session id from `src/lib/anon-session.ts`). The hard-coded
21
- * sample below is just the shell.
19
+ * anon-session id from `src/lib/anon-session.ts`). The sample below
20
+ * is just a placeholder shell — replace sampleItems with a fetch or
21
+ * a cart state hook connected to your data layer.
22
22
  */
23
23
  export default function CartPage() {
24
24
  const { isSignedIn } = useAuth();
25
25
  const [emailForReceipt, setEmailForReceipt] = useState('');
26
+
27
+ // TODO: replace with real cart items from your backend or cart state
26
28
  const sampleItems = [
27
- { id: '1', name: 'Plátanos verdes (3)', price: 1.5, store: "Doña Yolanda" },
28
- { id: '2', name: 'Presidente fría (6-pack)', price: 9.0, store: "Don Tito" },
29
- { id: '3', name: 'Recarga Claro $5', price: 5.0, store: "Doña Yolanda" },
29
+ { id: '1', name: 'Sample product 1', price: 9.99, store: 'Store A' },
30
+ { id: '2', name: 'Sample product 2', price: 14.99, store: 'Store B' },
31
+ { id: '3', name: 'Sample product 3', price: 4.99, store: 'Store A' },
30
32
  ];
31
33
  const total = sampleItems.reduce((s, i) => s + i.price, 0);
32
34
 
@@ -34,10 +36,10 @@ export default function CartPage() {
34
36
  <div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)' }}>
35
37
  <div style=\{{ maxWidth: '40rem', margin: '0 auto', padding: 'var(--space-8) var(--space-4)' }}>
36
38
  <h1 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '2rem', marginBottom: 'var(--space-2)' }}>
37
- Tu funda
39
+ Your cart
38
40
  </h1>
39
41
  <p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-6)' }}>
40
- Your bag. Edit, then choose how you want to pay.
42
+ Review your items, then choose how you want to pay.
41
43
  </p>
42
44
 
43
45
  <ul style=\{{ listStyle: 'none', display: 'flex', flexDirection: 'column', gap: 'var(--space-3)', marginBottom: 'var(--space-6)' }}>
@@ -53,7 +55,7 @@ export default function CartPage() {
53
55
  }}>
54
56
  <div>
55
57
  <div style=\{{ fontWeight: 600 }}>{it.name}</div>
56
- <div style=\{{ fontSize: '0.75rem', color: 'var(--fg-muted)' }}>De tu colmadero, {it.store}</div>
58
+ <div style=\{{ fontSize: '0.75rem', color: 'var(--fg-muted)' }}>From {it.store}</div>
57
59
  </div>
58
60
  <div style=\{{ fontWeight: 600 }}>${it.price.toFixed(2)}</div>
59
61
  </li>
@@ -80,9 +82,9 @@ export default function CartPage() {
80
82
  border: '1px solid var(--border)',
81
83
  background: 'var(--surface-sub)',
82
84
  }}>
83
- <h2 style=\{{ fontSize: '1.25rem', marginBottom: 'var(--space-2)' }}>¿Cómo lo pagamos?</h2>
85
+ <h2 style=\{{ fontSize: '1.25rem', marginBottom: 'var(--space-2)' }}>How would you like to pay?</h2>
84
86
  <p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-4)', fontSize: '0.875rem' }}>
85
- How would you like to pay? You don't need an account.
87
+ You don't need an account to check out.
86
88
  </p>
87
89
 
88
90
  {!isSignedIn && (
@@ -93,7 +95,7 @@ export default function CartPage() {
93
95
  style=\{{ marginBottom: 'var(--space-4)' }}
94
96
  >
95
97
  <label htmlFor="email" style=\{{ display: 'block', marginBottom: 'var(--space-2)', fontSize: '0.875rem' }}>
96
- Email para el recibo (we'll send the receipt and that's it)
98
+ Email for your receipt (we'll send the receipt and that's it)
97
99
  </label>
98
100
  <div style=\{{ display: 'flex', gap: 'var(--space-2)' }}>
99
101
  <input
@@ -102,7 +104,7 @@ export default function CartPage() {
102
104
  required
103
105
  value={emailForReceipt}
104
106
  onChange={(e) => setEmailForReceipt(e.target.value)}
105
- placeholder="tu@email.com"
107
+ placeholder="you@email.com"
106
108
  style=\{{
107
109
  flex: 1,
108
110
  padding: 'var(--space-3)',
@@ -121,16 +123,16 @@ export default function CartPage() {
121
123
  fontWeight: 600,
122
124
  cursor: 'pointer',
123
125
  }}>
124
- Pagar como invitado
126
+ Check out as guest
125
127
  </button>
126
128
  </div>
127
129
  <p style=\{{ marginTop: 'var(--space-2)', fontSize: '0.75rem', color: 'var(--fg-subtle)' }}>
128
- Continue as guest. No password, no account, no saved data.
130
+ No password, no account, no saved data.
129
131
  </p>
130
132
  </form>
131
133
 
132
134
  <div style=\{{ display: 'flex', gap: 'var(--space-3)', flexWrap: 'wrap', alignItems: 'center', marginTop: 'var(--space-4)', paddingTop: 'var(--space-4)', borderTop: '1px dashed var(--border)' }}>
133
- <span style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)' }}>O si quieres guardar tus datos:</span>
135
+ <span style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)' }}>Or save your details for next time:</span>
134
136
  <SignInButton />
135
137
  <SignUpButton />
136
138
  </div>
@@ -147,7 +149,7 @@ export default function CartPage() {
147
149
  fontWeight: 600,
148
150
  textDecoration: 'none',
149
151
  }}>
150
- Pagar
152
+ Check out
151
153
  </Link>
152
154
  )}
153
155
  </section>
@@ -4,8 +4,8 @@ import Link from 'next/link';
4
4
  import './globals.css';
5
5
 
6
6
  export const metadata = {
7
- title: '{{businessName}} — el colmado de tu barrio',
8
- description: '{{description}}',
7
+ title: '{{businessName}} — your marketplace',
8
+ description: '{{businessName}} — shop local, built with GitHat.',
9
9
  };
10
10
 
11
11
  /**
@@ -13,15 +13,15 @@ export const metadata = {
13
13
  *
14
14
  * Header has three slots, in order of importance:
15
15
  * 1. Wordmark (always visible)
16
- * 2. Tu fundacart icon, anonymous-aware
16
+ * 2. Cart link — anonymous-aware
17
17
  * 3. "Save my stuff" / Sign in — low contrast, never a gate
18
18
  *
19
- * The "Vende en {{businessName}}" link sits in the footer, not the
19
+ * The "Sell on {{businessName}}" link sits in the footer, not the
20
20
  * header. Sellers are a small minority of visitors; shoppers come first.
21
21
  */
22
22
  export default function RootLayout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
23
23
  return (
24
- <html lang="es">
24
+ <html lang="en">
25
25
  <body>
26
26
  <GitHatProvider config=\{{
27
27
  publishableKey: process.env.NEXT_PUBLIC_GITHAT_PUBLISHABLE_KEY || '',
@@ -54,7 +54,7 @@ export default function RootLayout({ children }{{#if typescript}}: { children: R
54
54
  </Link>
55
55
  <nav style=\{{ display: 'flex', alignItems: 'center', gap: 'var(--space-4, 1rem)', fontSize: '0.875rem' }}>
56
56
  <Link href="/cart" style=\{{ color: 'var(--fg, inherit)', textDecoration: 'none' }}>
57
- Tu funda
57
+ Cart
58
58
  </Link>
59
59
  <Link href="/sign-in" style=\{{ color: 'var(--fg-subtle, #71717a)', textDecoration: 'none' }}>
60
60
  Save my stuff
@@ -70,7 +70,7 @@ export default function RootLayout({ children }{{#if typescript}}: { children: R
70
70
  textAlign: 'center',
71
71
  }}>
72
72
  <Link href="/sell" style=\{{ color: 'var(--fg-muted, #525252)', textDecoration: 'none' }}>
73
- ¿Tienes un colmado? Vende aquí
73
+ Sell on {{businessName}}
74
74
  </Link>
75
75
  <span style=\{{ display: 'block', marginTop: 'var(--space-2, 0.5rem)' }}>
76
76
  {{businessName}} — built on GitHat + Sebastn.
@@ -4,26 +4,27 @@ import { useState } from 'react';
4
4
  import Link from 'next/link';
5
5
  import { useAuth } from '@githat/nextjs';
6
6
  import { CATEGORIES } from '../src/lib/categories';
7
+ import { getAvailableProducts } from '../src/data/products';
7
8
 
8
9
  /**
9
- * Marketplace homepage — colmado-flavored.
10
+ * Marketplace homepage.
10
11
  *
11
12
  * Anonymous-first: visitors browse and search without signing in.
12
13
  * The header has a low-contrast "Save my stuff" link that opens
13
- * GitHat sign-in, but it's never a gate. See CULTURE.md for the
14
- * cultural framing — this is a colmado, not a checkout terminal.
14
+ * GitHat sign-in, but it's never a gate.
15
15
  *
16
- * Real product data should come from your backend (Postgres,
17
- * DynamoDB, Sebastn-stored inventory) this file just renders
18
- * the shell + seed categories.
16
+ * Product data comes from src/data/products.ts edit that file to
17
+ * add your products. When you're ready for a real backend, replace
18
+ * getAvailableProducts() with a fetch to your API.
19
19
  */
20
20
  export default function Home() {
21
21
  const { isSignedIn } = useAuth();
22
22
  const [query, setQuery] = useState('');
23
+ const featuredProducts = getAvailableProducts().slice(0, 6);
23
24
 
24
25
  return (
25
26
  <div style=\{{ background: 'var(--bg)', color: 'var(--fg)', minHeight: 'calc(100vh - 64px)' }}>
26
- {/* Hero — bilingual, warm, anti-Amazon */}
27
+ {/* Hero */}
27
28
  <section style=\{{
28
29
  padding: 'var(--space-8) var(--space-4)',
29
30
  background: 'linear-gradient(180deg, var(--surface-sub), var(--bg))',
@@ -35,22 +36,18 @@ export default function Home() {
35
36
  lineHeight: 1.1,
36
37
  marginBottom: 'var(--space-3)',
37
38
  }}>
38
- Cerquita de ti, todo lo que necesitas.
39
+ {{businessName}}
39
40
  </h1>
40
41
  <p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-6)', fontSize: '1.125rem' }}>
41
- {{businessName}} el colmado de tu barrio, ahora en tu teléfono.
42
- <br />
43
- <span style=\{{ fontSize: '0.875rem', opacity: 0.7 }}>
44
- Pídelo y te lo llevamos. <em>Order it, we'll bring it to you.</em>
45
- </span>
42
+ Shop local. Browse products, add to your cart, and check out — no account needed.
46
43
  </p>
47
44
 
48
45
  <form
49
- onSubmit={(e) => { e.preventDefault(); window.location.href = `/buscar?q=${encodeURIComponent(query)}`; }}
46
+ onSubmit={(e) => { e.preventDefault(); window.location.href = `/search?q=${encodeURIComponent(query)}`; }}
50
47
  style=\{{ maxWidth: '32rem', margin: '0 auto' }}
51
48
  >
52
49
  <label htmlFor="q" style=\{{ display: 'block', textAlign: 'left', marginBottom: 'var(--space-2)', fontSize: '0.875rem', color: 'var(--fg-muted)' }}>
53
- Busca lo que te haga falta
50
+ Search for anything
54
51
  </label>
55
52
  <div style=\{{ display: 'flex', gap: 'var(--space-2)' }}>
56
53
  <input
@@ -58,7 +55,7 @@ export default function Home() {
58
55
  type="search"
59
56
  value={query}
60
57
  onChange={(e) => setQuery(e.target.value)}
61
- placeholder="Plátano, Presidente fría, recargas Claro…"
58
+ placeholder="Search products…"
62
59
  style=\{{
63
60
  flex: 1,
64
61
  padding: 'var(--space-3) var(--space-4)',
@@ -78,7 +75,7 @@ export default function Home() {
78
75
  fontWeight: 600,
79
76
  cursor: 'pointer',
80
77
  }}>
81
- Buscar
78
+ Search
82
79
  </button>
83
80
  </div>
84
81
  </form>
@@ -86,16 +83,17 @@ export default function Home() {
86
83
  {isSignedIn && (
87
84
  <p style=\{{ marginTop: 'var(--space-6)', fontSize: '0.875rem' }}>
88
85
  <Link href="/cart" style=\{{ color: 'var(--primary)' }}>
89
- Lo de siempre
86
+ Your usual order
90
87
  </Link>
91
- <span style=\{{ color: 'var(--fg-subtle)' }}> (your usual order, one click away)</span>
88
+ <span style=\{{ color: 'var(--fg-subtle)' }}> (one click away)</span>
92
89
  </p>
93
90
  )}
94
91
  </section>
95
92
 
93
+ {/* Categories */}
96
94
  <section style=\{{ padding: 'var(--space-8) var(--space-4)' }}>
97
95
  <div style=\{{ maxWidth: '64rem', margin: '0 auto' }}>
98
- <h2 style=\{{ fontSize: '1.5rem', marginBottom: 'var(--space-4)' }}>Categorías</h2>
96
+ <h2 style=\{{ fontSize: '1.5rem', marginBottom: 'var(--space-4)' }}>Categories</h2>
99
97
  <div style=\{{
100
98
  display: 'grid',
101
99
  gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
@@ -104,7 +102,7 @@ export default function Home() {
104
102
  {CATEGORIES.map((cat) => (
105
103
  <Link
106
104
  key={cat.slug}
107
- href={`/buscar?cat=${cat.slug}`}
105
+ href={`/search?cat=${cat.slug}`}
108
106
  style=\{{
109
107
  display: 'flex',
110
108
  flexDirection: 'column',
@@ -120,15 +118,68 @@ export default function Home() {
120
118
  }}
121
119
  >
122
120
  <span style=\{{ fontSize: '2rem' }} aria-hidden>{cat.emoji}</span>
123
- <span style=\{{ fontWeight: 600, fontSize: '0.875rem' }}>{cat.es}</span>
124
- <span style=\{{ fontSize: '0.75rem', color: 'var(--fg-muted)' }}>{cat.en}</span>
121
+ <span style=\{{ fontWeight: 600, fontSize: '0.875rem' }}>{cat.label}</span>
125
122
  </Link>
126
123
  ))}
127
124
  </div>
128
125
  </div>
129
126
  </section>
130
127
 
131
- <section style=\{{ padding: 'var(--space-8) var(--space-4)', background: 'var(--surface-sub)' }}>
128
+ {/* Featured products pulled from src/data/products.ts */}
129
+ {featuredProducts.length > 0 && (
130
+ <section style=\{{ padding: 'var(--space-8) var(--space-4)', background: 'var(--surface-sub)' }}>
131
+ <div style=\{{ maxWidth: '64rem', margin: '0 auto' }}>
132
+ <h2 style=\{{ fontSize: '1.5rem', marginBottom: 'var(--space-4)' }}>Featured products</h2>
133
+ <div style=\{{
134
+ display: 'grid',
135
+ gridTemplateColumns: 'repeat(auto-fill, minmax(220px, 1fr))',
136
+ gap: 'var(--space-4)',
137
+ }}>
138
+ {featuredProducts.map((product) => (
139
+ <div
140
+ key={product.id}
141
+ style=\{{
142
+ padding: 'var(--space-4)',
143
+ borderRadius: 'var(--radius-md, 0.5rem)',
144
+ border: '1px solid var(--border)',
145
+ background: 'var(--surface)',
146
+ display: 'flex',
147
+ flexDirection: 'column',
148
+ gap: 'var(--space-2)',
149
+ }}
150
+ >
151
+ <div style=\{{ fontWeight: 600 }}>{product.name}</div>
152
+ <div style=\{{ fontSize: '0.875rem', color: 'var(--fg-muted)', flex: 1 }}>{product.description}</div>
153
+ <div style=\{{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
154
+ <span style=\{{ fontWeight: 600, fontSize: '1.125rem' }}>${product.price.toFixed(2)}</span>
155
+ <button
156
+ onClick={() => {/* TODO: add to cart */}}
157
+ style=\{{
158
+ padding: 'var(--space-2) var(--space-4)',
159
+ borderRadius: 'var(--radius-md, 0.5rem)',
160
+ border: 'none',
161
+ background: 'var(--primary)',
162
+ color: 'var(--bg)',
163
+ fontWeight: 600,
164
+ cursor: 'pointer',
165
+ fontSize: '0.875rem',
166
+ }}
167
+ >
168
+ Add to cart
169
+ </button>
170
+ </div>
171
+ </div>
172
+ ))}
173
+ </div>
174
+ <p style=\{{ marginTop: 'var(--space-4)', fontSize: '0.75rem', color: 'var(--fg-subtle)' }}>
175
+ Products are loaded from <code>src/data/products.ts</code>. Edit that file to add your own.
176
+ </p>
177
+ </div>
178
+ </section>
179
+ )}
180
+
181
+ {/* Value props */}
182
+ <section style=\{{ padding: 'var(--space-8) var(--space-4)' }}>
132
183
  <div style=\{{
133
184
  maxWidth: '64rem',
134
185
  margin: '0 auto',
@@ -138,11 +189,11 @@ export default function Home() {
138
189
  }}>
139
190
  <div>
140
191
  <h3 style=\{{ fontFamily: 'var(--font-wordmark)', fontSize: '1.5rem', marginBottom: 'var(--space-2)' }}>
141
- ¿Tienes un colmado?
192
+ Have a shop? Sell here.
142
193
  </h3>
143
194
  <p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-3)' }}>
144
- Pon tu tienda online en cinco minutos. Tu propia página,
145
- tus precios, tus pedidos. Te quedas con el 96%.
195
+ Get your products online in five minutes. Your page, your prices,
196
+ your orders. You keep 96%.
146
197
  </p>
147
198
  <Link href="/sell" style=\{{
148
199
  display: 'inline-block',
@@ -153,7 +204,7 @@ export default function Home() {
153
204
  fontWeight: 600,
154
205
  textDecoration: 'none',
155
206
  }}>
156
- Vende en {{businessName}}
207
+ Start selling
157
208
  </Link>
158
209
  </div>
159
210
  <div>
@@ -161,9 +212,9 @@ export default function Home() {
161
212
  Buying without signing in?
162
213
  </h3>
163
214
  <p style=\{{ color: 'var(--fg-muted)', marginBottom: 'var(--space-3)' }}>
164
- Adelante. You don't need an account to browse, add to your
165
- funda, or check out. Sign up only if you want to save
166
- addresses, see past orders, or use store credit.
215
+ Go ahead. You don't need an account to browse, add to your
216
+ cart, or check out. Sign up only if you want to save
217
+ your address, see past orders, or use store credit.
167
218
  </p>
168
219
  {!isSignedIn && (
169
220
  <Link href="/sign-in" style=\{{ fontSize: '0.875rem', color: 'var(--primary)' }}>