@pylonsync/create-pylon 0.3.274 → 0.3.275

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 (323) hide show
  1. package/bin/create-pylon.js +80 -0
  2. package/package.json +1 -1
  3. package/templates/ARCHETYPES.md +339 -0
  4. package/templates/agency/.env.example +12 -0
  5. package/templates/agency/AGENTS.md +61 -0
  6. package/templates/agency/README.md +90 -0
  7. package/templates/agency/app/auth-form.tsx +129 -0
  8. package/templates/agency/app/contact-form.tsx +258 -0
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +286 -0
  10. package/templates/agency/app/dashboard/page.tsx +70 -0
  11. package/templates/agency/app/error.tsx +26 -0
  12. package/templates/agency/app/globals.css +148 -0
  13. package/templates/agency/app/layout.tsx +174 -0
  14. package/templates/agency/app/login/page.tsx +39 -0
  15. package/templates/agency/app/not-found.tsx +19 -0
  16. package/templates/agency/app/page.tsx +207 -0
  17. package/templates/agency/app/robots.ts +12 -0
  18. package/templates/agency/app/sitemap.ts +9 -0
  19. package/templates/agency/app.ts +135 -0
  20. package/templates/agency/components/marketing.tsx +148 -0
  21. package/templates/agency/components/section-scroller.tsx +35 -0
  22. package/templates/agency/components/ui/button.tsx +56 -0
  23. package/templates/agency/components/ui/card.tsx +90 -0
  24. package/templates/agency/components.json +20 -0
  25. package/templates/agency/functions/bookInquiry.ts +42 -0
  26. package/templates/agency/functions/declineInquiry.ts +41 -0
  27. package/templates/agency/functions/inquiriesForOwner.ts +31 -0
  28. package/templates/agency/functions/seedCapacity.ts +26 -0
  29. package/templates/agency/functions/setCapacity.ts +32 -0
  30. package/templates/agency/functions/submitInquiry.ts +55 -0
  31. package/templates/agency/gitignore +10 -0
  32. package/templates/agency/lib/agency.ts +27 -0
  33. package/templates/agency/lib/owner.ts +26 -0
  34. package/templates/agency/lib/site.config.ts +239 -0
  35. package/templates/agency/lib/utils.ts +10 -0
  36. package/templates/agency/package.json +34 -0
  37. package/templates/agency/tsconfig.json +18 -0
  38. package/templates/ai-chat/.env.example +33 -0
  39. package/templates/ai-chat/AGENTS.md +61 -0
  40. package/templates/ai-chat/README.md +99 -0
  41. package/templates/ai-chat/app/auth-form.tsx +124 -0
  42. package/templates/ai-chat/app/chat-client.tsx +414 -0
  43. package/templates/ai-chat/app/error.tsx +26 -0
  44. package/templates/ai-chat/app/globals.css +148 -0
  45. package/templates/ai-chat/app/layout.tsx +75 -0
  46. package/templates/ai-chat/app/login/page.tsx +39 -0
  47. package/templates/ai-chat/app/not-found.tsx +19 -0
  48. package/templates/ai-chat/app/page.tsx +23 -0
  49. package/templates/ai-chat/app.ts +121 -0
  50. package/templates/ai-chat/components.json +20 -0
  51. package/templates/ai-chat/gitignore +10 -0
  52. package/templates/ai-chat/lib/site.config.ts +103 -0
  53. package/templates/ai-chat/lib/utils.ts +10 -0
  54. package/templates/ai-chat/package.json +34 -0
  55. package/templates/ai-chat/tsconfig.json +18 -0
  56. package/templates/ai-studio/.env.example +19 -0
  57. package/templates/ai-studio/AGENTS.md +61 -0
  58. package/templates/ai-studio/README.md +83 -0
  59. package/templates/ai-studio/app/auth-form.tsx +124 -0
  60. package/templates/ai-studio/app/error.tsx +26 -0
  61. package/templates/ai-studio/app/globals.css +148 -0
  62. package/templates/ai-studio/app/layout.tsx +75 -0
  63. package/templates/ai-studio/app/login/page.tsx +39 -0
  64. package/templates/ai-studio/app/not-found.tsx +19 -0
  65. package/templates/ai-studio/app/page.tsx +34 -0
  66. package/templates/ai-studio/app/studio-client.tsx +214 -0
  67. package/templates/ai-studio/app.ts +108 -0
  68. package/templates/ai-studio/components.json +20 -0
  69. package/templates/ai-studio/functions/_getGeneration.ts +25 -0
  70. package/templates/ai-studio/functions/_updateGeneration.ts +37 -0
  71. package/templates/ai-studio/functions/generate.ts +42 -0
  72. package/templates/ai-studio/functions/pollGeneration.ts +134 -0
  73. package/templates/ai-studio/gitignore +10 -0
  74. package/templates/ai-studio/lib/site.config.ts +80 -0
  75. package/templates/ai-studio/lib/studio.ts +52 -0
  76. package/templates/ai-studio/lib/utils.ts +10 -0
  77. package/templates/ai-studio/package.json +34 -0
  78. package/templates/ai-studio/tsconfig.json +18 -0
  79. package/templates/creator/.env.example +12 -0
  80. package/templates/creator/AGENTS.md +61 -0
  81. package/templates/creator/README.md +67 -0
  82. package/templates/creator/app/auth-form.tsx +129 -0
  83. package/templates/creator/app/dashboard/dashboard-client.tsx +297 -0
  84. package/templates/creator/app/dashboard/page.tsx +70 -0
  85. package/templates/creator/app/error.tsx +26 -0
  86. package/templates/creator/app/globals.css +148 -0
  87. package/templates/creator/app/layout.tsx +160 -0
  88. package/templates/creator/app/login/page.tsx +39 -0
  89. package/templates/creator/app/newsletter-signup.tsx +162 -0
  90. package/templates/creator/app/not-found.tsx +19 -0
  91. package/templates/creator/app/page.tsx +160 -0
  92. package/templates/creator/app/robots.ts +12 -0
  93. package/templates/creator/app/sitemap.ts +9 -0
  94. package/templates/creator/app.ts +134 -0
  95. package/templates/creator/components/marketing.tsx +148 -0
  96. package/templates/creator/components/section-scroller.tsx +35 -0
  97. package/templates/creator/components/ui/button.tsx +56 -0
  98. package/templates/creator/components/ui/card.tsx +90 -0
  99. package/templates/creator/components.json +20 -0
  100. package/templates/creator/functions/subscribe.ts +82 -0
  101. package/templates/creator/functions/subscriberStats.ts +75 -0
  102. package/templates/creator/gitignore +10 -0
  103. package/templates/creator/lib/owner.ts +26 -0
  104. package/templates/creator/lib/site.config.ts +173 -0
  105. package/templates/creator/lib/stats.ts +30 -0
  106. package/templates/creator/lib/utils.ts +10 -0
  107. package/templates/creator/package.json +34 -0
  108. package/templates/creator/tsconfig.json +18 -0
  109. package/templates/default/app/layout.tsx +26 -27
  110. package/templates/default/app/page.tsx +90 -274
  111. package/templates/default/lib/products.ts +9 -122
  112. package/templates/default/lib/site.config.ts +739 -0
  113. package/templates/default/lib/site.ts +14 -261
  114. package/templates/directory/.env.example +12 -0
  115. package/templates/directory/AGENTS.md +61 -0
  116. package/templates/directory/README.md +80 -0
  117. package/templates/directory/app/auth-form.tsx +129 -0
  118. package/templates/directory/app/dashboard/dashboard-client.tsx +205 -0
  119. package/templates/directory/app/dashboard/page.tsx +70 -0
  120. package/templates/directory/app/directory-browse.tsx +328 -0
  121. package/templates/directory/app/error.tsx +26 -0
  122. package/templates/directory/app/globals.css +148 -0
  123. package/templates/directory/app/layout.tsx +171 -0
  124. package/templates/directory/app/login/page.tsx +39 -0
  125. package/templates/directory/app/not-found.tsx +19 -0
  126. package/templates/directory/app/page.tsx +50 -0
  127. package/templates/directory/app/robots.ts +12 -0
  128. package/templates/directory/app/sitemap.ts +9 -0
  129. package/templates/directory/app/submit/page.tsx +30 -0
  130. package/templates/directory/app/submit-form.tsx +151 -0
  131. package/templates/directory/app.ts +146 -0
  132. package/templates/directory/components/marketing.tsx +148 -0
  133. package/templates/directory/components/section-scroller.tsx +35 -0
  134. package/templates/directory/components/ui/button.tsx +56 -0
  135. package/templates/directory/components/ui/card.tsx +90 -0
  136. package/templates/directory/components.json +20 -0
  137. package/templates/directory/functions/approveSubmission.ts +45 -0
  138. package/templates/directory/functions/rejectSubmission.ts +20 -0
  139. package/templates/directory/functions/seedListings.ts +33 -0
  140. package/templates/directory/functions/submissionsForOwner.ts +29 -0
  141. package/templates/directory/functions/submitListing.ts +63 -0
  142. package/templates/directory/functions/upvote.ts +24 -0
  143. package/templates/directory/gitignore +10 -0
  144. package/templates/directory/lib/directory.ts +45 -0
  145. package/templates/directory/lib/owner.ts +26 -0
  146. package/templates/directory/lib/site.config.ts +130 -0
  147. package/templates/directory/lib/utils.ts +10 -0
  148. package/templates/directory/package.json +34 -0
  149. package/templates/directory/tsconfig.json +18 -0
  150. package/templates/local-service/.env.example +12 -0
  151. package/templates/local-service/AGENTS.md +61 -0
  152. package/templates/local-service/README.md +82 -0
  153. package/templates/local-service/app/auth-form.tsx +129 -0
  154. package/templates/local-service/app/booking-widget.tsx +399 -0
  155. package/templates/local-service/app/dashboard/dashboard-client.tsx +304 -0
  156. package/templates/local-service/app/dashboard/page.tsx +63 -0
  157. package/templates/local-service/app/error.tsx +26 -0
  158. package/templates/local-service/app/globals.css +148 -0
  159. package/templates/local-service/app/layout.tsx +151 -0
  160. package/templates/local-service/app/login/page.tsx +39 -0
  161. package/templates/local-service/app/not-found.tsx +19 -0
  162. package/templates/local-service/app/page.tsx +233 -0
  163. package/templates/local-service/app/robots.ts +12 -0
  164. package/templates/local-service/app/sitemap.ts +9 -0
  165. package/templates/local-service/app.ts +131 -0
  166. package/templates/local-service/components/marketing.tsx +162 -0
  167. package/templates/local-service/components/section-scroller.tsx +35 -0
  168. package/templates/local-service/components/ui/button.tsx +56 -0
  169. package/templates/local-service/components/ui/card.tsx +90 -0
  170. package/templates/local-service/components.json +20 -0
  171. package/templates/local-service/functions/bookingsForOwner.ts +30 -0
  172. package/templates/local-service/functions/cancelBooking.ts +27 -0
  173. package/templates/local-service/functions/confirmBooking.ts +18 -0
  174. package/templates/local-service/functions/createBooking.ts +98 -0
  175. package/templates/local-service/gitignore +10 -0
  176. package/templates/local-service/lib/booking.ts +24 -0
  177. package/templates/local-service/lib/owner.ts +26 -0
  178. package/templates/local-service/lib/site.config.ts +232 -0
  179. package/templates/local-service/lib/slots.ts +97 -0
  180. package/templates/local-service/lib/utils.ts +10 -0
  181. package/templates/local-service/package.json +34 -0
  182. package/templates/local-service/tsconfig.json +18 -0
  183. package/templates/marketplace/.env.example +9 -0
  184. package/templates/marketplace/AGENTS.md +61 -0
  185. package/templates/marketplace/README.md +78 -0
  186. package/templates/marketplace/app/_components/CategoryIcon.tsx +40 -0
  187. package/templates/marketplace/app/error.tsx +26 -0
  188. package/templates/marketplace/app/globals.css +64 -0
  189. package/templates/marketplace/app/layout.tsx +60 -0
  190. package/templates/marketplace/app/listing/[id]/page.tsx +163 -0
  191. package/templates/marketplace/app/me/page.tsx +15 -0
  192. package/templates/marketplace/app/not-found.tsx +20 -0
  193. package/templates/marketplace/app/page.tsx +159 -0
  194. package/templates/marketplace/app/robots.ts +12 -0
  195. package/templates/marketplace/app/sell/page.tsx +26 -0
  196. package/templates/marketplace/app/sitemap.ts +14 -0
  197. package/templates/marketplace/app.ts +190 -0
  198. package/templates/marketplace/client/AuthNav.tsx +46 -0
  199. package/templates/marketplace/client/LiveTicker.tsx +104 -0
  200. package/templates/marketplace/client/LoginCard.tsx +130 -0
  201. package/templates/marketplace/client/MarketProvider.tsx +148 -0
  202. package/templates/marketplace/client/MyMarket.tsx +180 -0
  203. package/templates/marketplace/client/OfferPanel.tsx +355 -0
  204. package/templates/marketplace/client/SeedOnEmpty.tsx +26 -0
  205. package/templates/marketplace/client/SellForm.tsx +160 -0
  206. package/templates/marketplace/client/WatchButton.tsx +88 -0
  207. package/templates/marketplace/client/market.ts +341 -0
  208. package/templates/marketplace/functions/buyNow.ts +78 -0
  209. package/templates/marketplace/functions/makeOffer.ts +65 -0
  210. package/templates/marketplace/functions/respondToOffer.ts +62 -0
  211. package/templates/marketplace/functions/seedMarket.ts +90 -0
  212. package/templates/marketplace/gitignore +10 -0
  213. package/templates/marketplace/package.json +35 -0
  214. package/templates/marketplace/tsconfig.json +14 -0
  215. package/templates/marketplace/ui/badge.tsx +30 -0
  216. package/templates/marketplace/ui/button.tsx +49 -0
  217. package/templates/marketplace/ui/card.tsx +48 -0
  218. package/templates/marketplace/ui/input.tsx +17 -0
  219. package/templates/marketplace/ui/label.tsx +18 -0
  220. package/templates/marketplace/ui/textarea.tsx +17 -0
  221. package/templates/marketplace/ui/tokens.css +32 -0
  222. package/templates/marketplace/ui/utils.ts +6 -0
  223. package/templates/restaurant/.env.example +12 -0
  224. package/templates/restaurant/AGENTS.md +61 -0
  225. package/templates/restaurant/README.md +77 -0
  226. package/templates/restaurant/app/auth-form.tsx +129 -0
  227. package/templates/restaurant/app/dashboard/dashboard-client.tsx +263 -0
  228. package/templates/restaurant/app/dashboard/page.tsx +59 -0
  229. package/templates/restaurant/app/error.tsx +26 -0
  230. package/templates/restaurant/app/globals.css +148 -0
  231. package/templates/restaurant/app/layout.tsx +151 -0
  232. package/templates/restaurant/app/login/page.tsx +39 -0
  233. package/templates/restaurant/app/not-found.tsx +19 -0
  234. package/templates/restaurant/app/page.tsx +194 -0
  235. package/templates/restaurant/app/reservation-widget.tsx +359 -0
  236. package/templates/restaurant/app/robots.ts +12 -0
  237. package/templates/restaurant/app/sitemap.ts +9 -0
  238. package/templates/restaurant/app.ts +115 -0
  239. package/templates/restaurant/components/marketing.tsx +162 -0
  240. package/templates/restaurant/components/section-scroller.tsx +35 -0
  241. package/templates/restaurant/components/ui/button.tsx +56 -0
  242. package/templates/restaurant/components/ui/card.tsx +90 -0
  243. package/templates/restaurant/components.json +20 -0
  244. package/templates/restaurant/functions/cancelReservation.ts +26 -0
  245. package/templates/restaurant/functions/confirmReservation.ts +17 -0
  246. package/templates/restaurant/functions/createReservation.ts +92 -0
  247. package/templates/restaurant/functions/reservationsForOwner.ts +28 -0
  248. package/templates/restaurant/gitignore +10 -0
  249. package/templates/restaurant/lib/owner.ts +26 -0
  250. package/templates/restaurant/lib/reservation.ts +22 -0
  251. package/templates/restaurant/lib/site.config.ts +218 -0
  252. package/templates/restaurant/lib/slots.ts +55 -0
  253. package/templates/restaurant/lib/utils.ts +10 -0
  254. package/templates/restaurant/package.json +34 -0
  255. package/templates/restaurant/tsconfig.json +18 -0
  256. package/templates/shop/.env.example +32 -0
  257. package/templates/shop/AGENTS.md +61 -0
  258. package/templates/shop/README.md +102 -0
  259. package/templates/shop/app/auth-form.tsx +129 -0
  260. package/templates/shop/app/dashboard/dashboard-client.tsx +264 -0
  261. package/templates/shop/app/dashboard/page.tsx +59 -0
  262. package/templates/shop/app/error.tsx +26 -0
  263. package/templates/shop/app/globals.css +148 -0
  264. package/templates/shop/app/layout.tsx +160 -0
  265. package/templates/shop/app/login/page.tsx +39 -0
  266. package/templates/shop/app/not-found.tsx +19 -0
  267. package/templates/shop/app/page.tsx +95 -0
  268. package/templates/shop/app/robots.ts +12 -0
  269. package/templates/shop/app/shop-client.tsx +436 -0
  270. package/templates/shop/app/sitemap.ts +9 -0
  271. package/templates/shop/app/success/page.tsx +33 -0
  272. package/templates/shop/app.ts +134 -0
  273. package/templates/shop/components/marketing.tsx +96 -0
  274. package/templates/shop/components/section-scroller.tsx +35 -0
  275. package/templates/shop/components/ui/button.tsx +56 -0
  276. package/templates/shop/components/ui/card.tsx +90 -0
  277. package/templates/shop/components.json +20 -0
  278. package/templates/shop/functions/cancelOrder.ts +33 -0
  279. package/templates/shop/functions/checkout.ts +130 -0
  280. package/templates/shop/functions/fulfillOrder.ts +17 -0
  281. package/templates/shop/functions/markGroupPaid.ts +26 -0
  282. package/templates/shop/functions/ordersForOwner.ts +28 -0
  283. package/templates/shop/functions/releaseGroup.ts +36 -0
  284. package/templates/shop/functions/reserveCart.ts +87 -0
  285. package/templates/shop/functions/restockProduct.ts +23 -0
  286. package/templates/shop/functions/seedProducts.ts +30 -0
  287. package/templates/shop/functions/stripeWebhook.ts +72 -0
  288. package/templates/shop/gitignore +10 -0
  289. package/templates/shop/lib/owner.ts +26 -0
  290. package/templates/shop/lib/shop.ts +45 -0
  291. package/templates/shop/lib/site.config.ts +198 -0
  292. package/templates/shop/lib/utils.ts +10 -0
  293. package/templates/shop/package.json +35 -0
  294. package/templates/shop/tsconfig.json +18 -0
  295. package/templates/waitlist/.env.example +12 -0
  296. package/templates/waitlist/AGENTS.md +61 -0
  297. package/templates/waitlist/README.md +81 -0
  298. package/templates/waitlist/app/auth-form.tsx +129 -0
  299. package/templates/waitlist/app/dashboard/dashboard-client.tsx +297 -0
  300. package/templates/waitlist/app/dashboard/page.tsx +70 -0
  301. package/templates/waitlist/app/error.tsx +26 -0
  302. package/templates/waitlist/app/globals.css +148 -0
  303. package/templates/waitlist/app/layout.tsx +158 -0
  304. package/templates/waitlist/app/login/page.tsx +39 -0
  305. package/templates/waitlist/app/not-found.tsx +19 -0
  306. package/templates/waitlist/app/page.tsx +119 -0
  307. package/templates/waitlist/app/robots.ts +12 -0
  308. package/templates/waitlist/app/sitemap.ts +9 -0
  309. package/templates/waitlist/app/waitlist-hero.tsx +219 -0
  310. package/templates/waitlist/app.ts +134 -0
  311. package/templates/waitlist/components/marketing.tsx +96 -0
  312. package/templates/waitlist/components/ui/button.tsx +56 -0
  313. package/templates/waitlist/components/ui/card.tsx +90 -0
  314. package/templates/waitlist/components.json +20 -0
  315. package/templates/waitlist/functions/joinWaitlist.ts +82 -0
  316. package/templates/waitlist/functions/waitlistStats.ts +75 -0
  317. package/templates/waitlist/gitignore +10 -0
  318. package/templates/waitlist/lib/owner.ts +26 -0
  319. package/templates/waitlist/lib/site.config.ts +178 -0
  320. package/templates/waitlist/lib/stats.ts +30 -0
  321. package/templates/waitlist/lib/utils.ts +10 -0
  322. package/templates/waitlist/package.json +34 -0
  323. package/templates/waitlist/tsconfig.json +18 -0
@@ -0,0 +1,70 @@
1
+ import React, { use } from "react";
2
+ import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { siteConfig } from "@/lib/site.config";
4
+ import { UserMenu, AgencyDashboard } from "./dashboard-client";
5
+
6
+ export const metadata: Metadata = {
7
+ title: `Dashboard — ${siteConfig.brand.name}`,
8
+ robots: "noindex",
9
+ };
10
+
11
+ // `app/dashboard/page.tsx` → `/dashboard`. Server-side auth gate only — any
12
+ // signed-in user can load the shell. The OWNER gate (a studio is single-tenant:
13
+ // only PYLON_OWNER_EMAIL may see the inquiries) lives in the `inquiriesForOwner`
14
+ // function, which checks `ctx.env.PYLON_OWNER_EMAIL` — the authoritative server
15
+ // env. Non-owners get a clean "owner-only" card from the client when that call
16
+ // is denied. (Env-based config is read in the function, not here: an SSR page
17
+ // render can't reliably read arbitrary `process.env`.)
18
+ export default function DashboardPage({ auth, response, serverData }: PageProps) {
19
+ // Anonymous visitors and guest sessions (guest_… ids) get bounced to login —
20
+ // the dashboard is for the real, signed-in owner only.
21
+ if (!auth.user_id || auth.user_id.startsWith("guest_")) {
22
+ response.redirect("/login");
23
+ return null;
24
+ }
25
+
26
+ // The User self-read policy lets the signed-in user read their own row → the
27
+ // email shown in the account menu (and on the owner-only card).
28
+ const me = use(serverData.get<{ email?: string }>("User", auth.user_id));
29
+ const email = me?.email ?? "";
30
+
31
+ return (
32
+ <Shell email={email}>
33
+ <AgencyDashboard userEmail={email} />
34
+ </Shell>
35
+ );
36
+ }
37
+
38
+ // Dashboard chrome: a slim top bar with the logo, a link back to the public
39
+ // site, and the account menu (a client island for sign-out).
40
+ function Shell({ email, children }: { email: string; children: React.ReactNode }) {
41
+ const { brand } = siteConfig;
42
+ return (
43
+ <div className="flex min-h-screen flex-col bg-white text-zinc-900">
44
+ <header className="border-b border-zinc-200">
45
+ <div className="mx-auto flex h-14 max-w-4xl items-center justify-between px-6">
46
+ <div className="flex items-center gap-2">
47
+ <span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
48
+ {brand.letter}
49
+ </span>
50
+ <span className="text-[15px] font-semibold tracking-tight">
51
+ {brand.name} <span className="text-zinc-400">/ studio</span>
52
+ </span>
53
+ </div>
54
+ <div className="flex items-center gap-4">
55
+ <Link
56
+ href="/"
57
+ className="text-[13px] text-zinc-500 transition-colors hover:text-zinc-900"
58
+ >
59
+ View site ↗
60
+ </Link>
61
+ <UserMenu email={email} />
62
+ </div>
63
+ </div>
64
+ </header>
65
+ <main className="flex-1">
66
+ <div className="mx-auto max-w-4xl px-6 py-8">{children}</div>
67
+ </main>
68
+ </div>
69
+ );
70
+ }
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { type ErrorBoundaryProps } from "@pylonsync/react";
3
+
4
+ // `app/error.tsx` → the error boundary for this segment. Hydrated + interactive:
5
+ // `reset()` re-attempts the route. The thrown error reaches the client as
6
+ // `{ message, digest }` only — the stack stays in the dev overlay / server logs.
7
+ export default function Error({ error, reset }: ErrorBoundaryProps) {
8
+ return (
9
+ <div className="mx-auto flex min-h-[60vh] max-w-3xl flex-col items-center justify-center px-6 text-center">
10
+ <h1 className="text-2xl font-semibold tracking-tight">Something went wrong</h1>
11
+ <p className="mt-2 text-zinc-500">{error.message}</p>
12
+ {error.digest ? (
13
+ <p className="mt-1 text-xs text-zinc-400">
14
+ Reference: <code>{error.digest}</code>
15
+ </p>
16
+ ) : null}
17
+ <button
18
+ type="button"
19
+ onClick={reset}
20
+ className="mt-6 inline-flex h-10 items-center rounded-full bg-zinc-900 px-5 text-sm font-medium text-white transition-colors hover:bg-zinc-700"
21
+ >
22
+ Try again
23
+ </button>
24
+ </div>
25
+ );
26
+ }
@@ -0,0 +1,148 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ /* Tailwind v4 scans these globs for class names. Add more @source lines if you
5
+ put markup elsewhere. The @pylonsync/client line lets its components
6
+ (EnsureGuest, auth helpers) keep any classes they ship. */
7
+ @source "../app/**/*.{tsx,ts,jsx,js}";
8
+ @source "../components/**/*.{tsx,ts,jsx,js}";
9
+ @source "../lib/**/*.{tsx,ts,jsx,js}";
10
+ @source "../node_modules/@pylonsync/client/**/*.{tsx,ts,jsx,js}";
11
+
12
+ @custom-variant dark (&:where(.dark, .dark *));
13
+
14
+ /* shadcn/ui design tokens (new-york / zinc) + the marketing brand accent. The
15
+ three brand vars are defaults — app/layout.tsx overrides them from
16
+ lib/site.config.ts on <html>, so re-theming the whole page is one edit there. */
17
+ :root {
18
+ --radius: 0.625rem;
19
+ --brand: #4f46e5;
20
+ --brand-soft: #eef2ff;
21
+ --paper: #fafafa;
22
+ --background: oklch(1 0 0);
23
+ --foreground: oklch(0.141 0.005 285.823);
24
+ --card: oklch(1 0 0);
25
+ --card-foreground: oklch(0.141 0.005 285.823);
26
+ --popover: oklch(1 0 0);
27
+ --popover-foreground: oklch(0.141 0.005 285.823);
28
+ --primary: oklch(0.21 0.006 285.885);
29
+ --primary-foreground: oklch(0.985 0 0);
30
+ --secondary: oklch(0.967 0.001 286.375);
31
+ --secondary-foreground: oklch(0.21 0.006 285.885);
32
+ --muted: oklch(0.967 0.001 286.375);
33
+ --muted-foreground: oklch(0.552 0.016 285.938);
34
+ --accent: oklch(0.967 0.001 286.375);
35
+ --accent-foreground: oklch(0.21 0.006 285.885);
36
+ --destructive: oklch(0.577 0.245 27.325);
37
+ --border: oklch(0.92 0.004 286.32);
38
+ --input: oklch(0.92 0.004 286.32);
39
+ --ring: oklch(0.705 0.015 286.067);
40
+ --chart-1: oklch(0.646 0.222 41.116);
41
+ --chart-2: oklch(0.6 0.118 184.704);
42
+ --chart-3: oklch(0.398 0.07 227.392);
43
+ --chart-4: oklch(0.828 0.189 84.429);
44
+ --chart-5: oklch(0.769 0.188 70.08);
45
+ --sidebar: oklch(0.985 0 0);
46
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
47
+ --sidebar-primary: oklch(0.21 0.006 285.885);
48
+ --sidebar-primary-foreground: oklch(0.985 0 0);
49
+ --sidebar-accent: oklch(0.967 0.001 286.375);
50
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
51
+ --sidebar-border: oklch(0.92 0.004 286.32);
52
+ --sidebar-ring: oklch(0.705 0.015 286.067);
53
+ }
54
+
55
+ .dark {
56
+ --background: oklch(0.141 0.005 285.823);
57
+ --foreground: oklch(0.985 0 0);
58
+ --card: oklch(0.21 0.006 285.885);
59
+ --card-foreground: oklch(0.985 0 0);
60
+ --popover: oklch(0.21 0.006 285.885);
61
+ --popover-foreground: oklch(0.985 0 0);
62
+ --primary: oklch(0.92 0.004 286.32);
63
+ --primary-foreground: oklch(0.21 0.006 285.885);
64
+ --secondary: oklch(0.274 0.006 286.033);
65
+ --secondary-foreground: oklch(0.985 0 0);
66
+ --muted: oklch(0.274 0.006 286.033);
67
+ --muted-foreground: oklch(0.705 0.015 286.067);
68
+ --accent: oklch(0.274 0.006 286.033);
69
+ --accent-foreground: oklch(0.985 0 0);
70
+ --destructive: oklch(0.704 0.191 22.216);
71
+ --border: oklch(1 0 0 / 10%);
72
+ --input: oklch(1 0 0 / 15%);
73
+ --ring: oklch(0.552 0.016 285.938);
74
+ --chart-1: oklch(0.488 0.243 264.376);
75
+ --chart-2: oklch(0.696 0.17 162.48);
76
+ --chart-3: oklch(0.769 0.188 70.08);
77
+ --chart-4: oklch(0.627 0.265 303.9);
78
+ --chart-5: oklch(0.645 0.246 16.439);
79
+ --sidebar: oklch(0.21 0.006 285.885);
80
+ --sidebar-foreground: oklch(0.985 0 0);
81
+ --sidebar-primary: oklch(0.488 0.243 264.376);
82
+ --sidebar-primary-foreground: oklch(0.985 0 0);
83
+ --sidebar-accent: oklch(0.274 0.006 286.033);
84
+ --sidebar-accent-foreground: oklch(0.985 0 0);
85
+ --sidebar-border: oklch(1 0 0 / 10%);
86
+ --sidebar-ring: oklch(0.552 0.016 285.938);
87
+ }
88
+
89
+ @theme inline {
90
+ --radius-sm: calc(var(--radius) - 4px);
91
+ --radius-md: calc(var(--radius) - 2px);
92
+ --radius-lg: var(--radius);
93
+ --radius-xl: calc(var(--radius) + 4px);
94
+ --color-background: var(--background);
95
+ --color-foreground: var(--foreground);
96
+ --color-card: var(--card);
97
+ --color-card-foreground: var(--card-foreground);
98
+ --color-popover: var(--popover);
99
+ --color-popover-foreground: var(--popover-foreground);
100
+ --color-primary: var(--primary);
101
+ --color-primary-foreground: var(--primary-foreground);
102
+ --color-secondary: var(--secondary);
103
+ --color-secondary-foreground: var(--secondary-foreground);
104
+ --color-muted: var(--muted);
105
+ --color-muted-foreground: var(--muted-foreground);
106
+ --color-accent: var(--accent);
107
+ --color-accent-foreground: var(--accent-foreground);
108
+ --color-destructive: var(--destructive);
109
+ --color-border: var(--border);
110
+ --color-input: var(--input);
111
+ --color-ring: var(--ring);
112
+ --color-brand: var(--brand);
113
+ --color-brand-soft: var(--brand-soft);
114
+ --color-paper: var(--paper);
115
+ --color-chart-1: var(--chart-1);
116
+ --color-chart-2: var(--chart-2);
117
+ --color-chart-3: var(--chart-3);
118
+ --color-chart-4: var(--chart-4);
119
+ --color-chart-5: var(--chart-5);
120
+ --color-sidebar: var(--sidebar);
121
+ --color-sidebar-foreground: var(--sidebar-foreground);
122
+ --color-sidebar-primary: var(--sidebar-primary);
123
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
124
+ --color-sidebar-accent: var(--sidebar-accent);
125
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
126
+ --color-sidebar-border: var(--sidebar-border);
127
+ --color-sidebar-ring: var(--sidebar-ring);
128
+ }
129
+
130
+ @layer base {
131
+ *,
132
+ ::after,
133
+ ::before,
134
+ ::backdrop,
135
+ ::file-selector-button {
136
+ border-color: var(--color-border, currentColor);
137
+ outline-color: var(--color-ring);
138
+ }
139
+ body {
140
+ background-color: var(--color-background);
141
+ color: var(--color-foreground);
142
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
143
+ -webkit-font-smoothing: antialiased;
144
+ }
145
+ button {
146
+ cursor: pointer;
147
+ }
148
+ }
@@ -0,0 +1,174 @@
1
+ import React from "react";
2
+ import { Link, type PageAuth } from "@pylonsync/react";
3
+ import { siteConfig } from "@/lib/site.config";
4
+ import { SectionScroller } from "@/components/section-scroller";
5
+
6
+ // A layout wraps every page. This marketing layout renders a slim nav up top
7
+ // and a footer below, both driven by lib/site.config.ts. `auth.user_id` is
8
+ // resolved server-side from the session cookie before any HTML is sent, so the
9
+ // nav shows "Dashboard" once the owner is signed in and "Sign in" otherwise —
10
+ // no flash, no client fetch.
11
+ interface LayoutProps {
12
+ children: React.ReactNode;
13
+ url: string;
14
+ auth: PageAuth;
15
+ }
16
+
17
+ export default function RootLayout({ children, url, auth }: LayoutProps) {
18
+ // A guest session (minted by <EnsureGuest> for the live counter) has a
19
+ // `guest_…` user id — that's an anonymous visitor, NOT the signed-in owner,
20
+ // so it shouldn't flip the nav to "Dashboard".
21
+ const signedIn = Boolean(auth?.user_id && !auth.user_id.startsWith("guest_"));
22
+ const { brand, colors } = siteConfig;
23
+
24
+ // The auth screens and the dashboard bring their own chrome, so they render
25
+ // bare (no marketing nav/footer). Match on the path PREFIX, not a substring.
26
+ const path = (url ?? "").split("?")[0];
27
+ const BARE_PREFIXES = ["/login", "/dashboard"];
28
+ const isBare = BARE_PREFIXES.some((p) => path === p || path.startsWith(p + "/"));
29
+
30
+ return (
31
+ <html
32
+ lang="en"
33
+ // Marketing theme colors come from the single site config. Set as inline
34
+ // CSS vars on <html> so they override globals.css and the whole page
35
+ // re-themes from one place — no CSS edit needed.
36
+ style={
37
+ {
38
+ "--brand": colors.brand,
39
+ "--brand-soft": colors.brandSoft,
40
+ "--paper": colors.paper,
41
+ } as React.CSSProperties
42
+ }
43
+ >
44
+ <head>
45
+ <meta charSet="utf-8" />
46
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
47
+ {/* No <title> here — each page's exported `metadata` sets it. */}
48
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
49
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
50
+ <link
51
+ rel="stylesheet"
52
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
53
+ />
54
+ {/* Tailwind is compiled by Pylon from app/globals.css and injected here. */}
55
+ </head>
56
+ <body className="flex min-h-screen flex-col bg-background text-foreground antialiased">
57
+ <SectionScroller />
58
+ {isBare ? (
59
+ children
60
+ ) : (
61
+ <>
62
+ <header className="sticky top-0 z-30 border-b border-zinc-200/70 bg-white/85 backdrop-blur">
63
+ <div className="mx-auto flex h-14 max-w-5xl items-center justify-between px-6">
64
+ <Link href="/" className="flex items-center gap-2">
65
+ <span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
66
+ {brand.letter}
67
+ </span>
68
+ <span className="text-[15px] font-semibold tracking-tight text-zinc-900">
69
+ {brand.name}
70
+ </span>
71
+ </Link>
72
+ <nav className="flex items-center gap-1 sm:gap-2">
73
+ <a href="/#work" className="hidden rounded-full px-3 py-1.5 text-[13px] font-medium text-zinc-600 transition-colors hover:text-zinc-900 sm:inline-flex">
74
+ Work
75
+ </a>
76
+ <a href="/#services" className="hidden rounded-full px-3 py-1.5 text-[13px] font-medium text-zinc-600 transition-colors hover:text-zinc-900 sm:inline-flex">
77
+ Services
78
+ </a>
79
+ {signedIn ? (
80
+ <Link
81
+ href="/dashboard"
82
+ className="inline-flex items-center rounded-full bg-zinc-900 px-3.5 py-1.5 text-[13px] font-medium text-white transition-colors hover:bg-zinc-700"
83
+ >
84
+ Dashboard
85
+ </Link>
86
+ ) : (
87
+ <>
88
+ <Link
89
+ href="/login"
90
+ className="hidden rounded-full px-3 py-1.5 text-[13px] font-medium text-zinc-600 transition-colors hover:text-zinc-900 sm:inline-flex"
91
+ >
92
+ Sign in
93
+ </Link>
94
+ <a
95
+ href="/#contact"
96
+ className="inline-flex items-center rounded-full bg-brand px-3.5 py-1.5 text-[13px] font-medium text-white transition-opacity hover:opacity-90"
97
+ >
98
+ {siteConfig.hero.ctaLabel}
99
+ </a>
100
+ </>
101
+ )}
102
+ </nav>
103
+ </div>
104
+ </header>
105
+
106
+ <main className="flex-1">{children}</main>
107
+
108
+ <SiteFooter />
109
+ </>
110
+ )}
111
+ </body>
112
+ </html>
113
+ );
114
+ }
115
+
116
+ function SiteFooter() {
117
+ const { brand } = siteConfig;
118
+ return (
119
+ <footer className="border-t border-zinc-200/70 bg-white">
120
+ <div className="mx-auto max-w-5xl px-6 py-12">
121
+ <div className="flex flex-col items-start justify-between gap-6 sm:flex-row">
122
+ <div className="max-w-sm">
123
+ <Link href="/" className="inline-flex items-center gap-2">
124
+ <span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
125
+ {brand.letter}
126
+ </span>
127
+ <span className="text-[15px] font-semibold tracking-tight text-zinc-900">
128
+ {brand.name}
129
+ </span>
130
+ </Link>
131
+ <p className="mt-3 text-[13px] leading-relaxed text-zinc-500">
132
+ {brand.footerBlurb}
133
+ </p>
134
+ </div>
135
+ <div className="flex items-center gap-4">
136
+ {brand.socials.map((s) => (
137
+ <a
138
+ key={s.label}
139
+ href={s.href}
140
+ aria-label={s.label}
141
+ className="text-zinc-400 transition-colors hover:text-zinc-900"
142
+ >
143
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
144
+ <path d={s.path} />
145
+ </svg>
146
+ </a>
147
+ ))}
148
+ <a
149
+ href={`mailto:${brand.email}`}
150
+ aria-label="Email"
151
+ className="text-zinc-400 transition-colors hover:text-zinc-900"
152
+ >
153
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
154
+ <rect x="3" y="5" width="18" height="14" rx="2" />
155
+ <path d="m3 7 9 6 9-6" />
156
+ </svg>
157
+ </a>
158
+ </div>
159
+ </div>
160
+ <div className="mt-10 flex flex-col items-start justify-between gap-3 border-t border-zinc-200/70 pt-6 text-[12px] text-zinc-400 sm:flex-row sm:items-center">
161
+ <span>
162
+ © {new Date().getFullYear()} {brand.copyrightName}
163
+ </span>
164
+ <span>
165
+ Built with{" "}
166
+ <a href="https://pylonsync.com" className="font-medium text-zinc-600 hover:text-zinc-900">
167
+ Pylon
168
+ </a>
169
+ </span>
170
+ </div>
171
+ </div>
172
+ </footer>
173
+ );
174
+ }
@@ -0,0 +1,39 @@
1
+ import React from "react";
2
+ import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { AuthForm } from "../auth-form";
4
+ import { siteConfig } from "@/lib/site.config";
5
+
6
+ export const metadata: Metadata = {
7
+ title: `Sign in — ${siteConfig.brand.name}`,
8
+ robots: "noindex",
9
+ };
10
+
11
+ // `app/login/page.tsx` → `/login`. The owner's sign-in. Rendered bare (the
12
+ // layout suppresses the marketing nav/footer for /login). Already signed in?
13
+ // Skip straight to the dashboard — `response.redirect` in the synchronous shell
14
+ // render is a real 307 before any HTML is sent.
15
+ export default function LoginPage({ auth, response }: PageProps) {
16
+ // Already signed in (a real account, not an anonymous guest)? Skip the form.
17
+ if (auth.user_id && !auth.user_id.startsWith("guest_")) response.redirect("/dashboard");
18
+ const { brand } = siteConfig;
19
+ return (
20
+ <div className="flex min-h-screen items-center justify-center bg-white px-6 py-12">
21
+ <div className="w-full max-w-[400px] rounded-2xl border border-zinc-200/70 p-8">
22
+ <Link href="/" className="inline-flex">
23
+ <span className="flex size-9 items-center justify-center rounded-xl bg-zinc-900 text-base font-bold text-white">
24
+ {brand.letter}
25
+ </span>
26
+ </Link>
27
+ <h1 className="mt-5 text-[22px] font-semibold tracking-tight text-zinc-900">
28
+ {brand.name} dashboard
29
+ </h1>
30
+ <p className="mt-1 text-[13px] text-zinc-500">
31
+ Sign in to manage your inquiries.
32
+ </p>
33
+ <div className="mt-6">
34
+ <AuthForm />
35
+ </div>
36
+ </div>
37
+ </div>
38
+ );
39
+ }
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { Link, type NotFoundProps } from "@pylonsync/react";
3
+
4
+ // `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL (and when a
5
+ // page calls `response.notFound()`). Hydrated, so the link is a client nav.
6
+ export default function NotFound(_props: NotFoundProps) {
7
+ return (
8
+ <div className="mx-auto flex min-h-[60vh] max-w-3xl flex-col items-center justify-center px-6 text-center">
9
+ <h1 className="text-3xl font-semibold tracking-tight">404</h1>
10
+ <p className="mt-2 text-zinc-500">We couldn&apos;t find that page.</p>
11
+ <Link
12
+ href="/"
13
+ className="mt-6 inline-flex h-10 items-center rounded-full bg-zinc-900 px-5 text-sm font-medium text-white transition-colors hover:bg-zinc-700"
14
+ >
15
+ Back home
16
+ </Link>
17
+ </div>
18
+ );
19
+ }