@pylonsync/create-pylon 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -1,21 +1,26 @@
1
- // Business-specific copy and settings live here. The landing page and layout
2
- // read this file. The product list, including starting stock, seeds the Product table on
3
- // first visit; after that, stock lives in the database and updates live.
1
+ // Business-specific copy and settings live here. The storefront pages and
2
+ // layout read this file. The product list, including starting stock, seeds the
3
+ // Product table on first visit; after that, stock lives in the database and
4
+ // updates live.
4
5
  //
5
6
  // Colors live here (applied as CSS variables on <html> in app/layout.tsx).
6
7
  // Fictional demo copy. Replace the values and keep the shape.
7
8
 
8
9
  /* ----------------------------- types ----------------------------- */
9
10
 
11
+ import type { ShippingTerms } from "./shop";
12
+
10
13
  export type Social = { label: string; href: string; path: string };
11
14
 
12
15
  export type BaseConfig = {
13
16
  brand: {
14
17
  name: string;
15
- letter: string;
18
+ letter: string; // the monogram on the sign-in and dashboard screens
16
19
  domain: string;
17
20
  email: string;
21
+ announcement: string;
18
22
  footerBlurb: string;
23
+ address: string[];
19
24
  copyrightName: string;
20
25
  socials: Social[];
21
26
  };
@@ -28,21 +33,29 @@ export type ProductItem = {
28
33
  name: string;
29
34
  priceCents: number;
30
35
  description?: string;
31
- image: string; // a product photo URL/path → shown as the photo; or an emoji stand-in (tagged "sample image")
32
- stock: number; // STARTING stock — seeded once, then live in the DB
36
+ size?: string; // one short line under the name, e.g. "8 oz, 60-hour burn"
37
+ image: string; // a product photo URL or /public path (4:5 portrait works best)
38
+ stock: number; // STARTING stock, seeded once, then live in the DB
33
39
  };
34
40
 
35
- export type ValueProp = { title: string; body: string; icon?: string };
36
- export type Review = { quote: string; name: string; rating?: number };
41
+ export type Fact = { value: string; label: string };
42
+ export type Review = { quote: string; name: string; product?: string };
37
43
  export type Policy = { title: string; body: string };
38
44
 
39
45
  export type ShopConfig = BaseConfig & {
40
- hero: { tagline: string; headline: string; subcopy: string; ctaLabel: string };
41
- products: { eyebrow: string; headline: string; items: ProductItem[] };
42
- checkout: { confirmationMessage: string };
43
- valueProps: { eyebrow: string; headline: string; items: ValueProp[] };
44
- reviews?: { eyebrow: string; headline: string; items: Review[] };
45
- policies: { eyebrow: string; headline: string; items: Policy[] };
46
+ hero: {
47
+ headline: string;
48
+ subcopy: string;
49
+ ctaLabel: string;
50
+ secondaryLabel: string;
51
+ image: string;
52
+ imageAlt: string;
53
+ };
54
+ products: { headline: string; subcopy: string; items: ProductItem[] };
55
+ studio: { headline: string; body: string[]; facts: Fact[] };
56
+ checkout: { shipping: ShippingTerms; reservedMessage: string; paidMessage: string; image: string };
57
+ reviews?: { headline: string; items: Review[] };
58
+ policies: { headline: string; items: Policy[] };
46
59
  };
47
60
 
48
61
  /* ----------------------------- config ---------------------------- */
@@ -53,145 +66,142 @@ export const siteConfig: ShopConfig = {
53
66
  letter: "E",
54
67
  domain: "embergoods.com",
55
68
  email: "hello@embergoods.example",
56
- footerBlurb:
57
- "Hand-poured candles and small-batch home goods, made in Dallas. Slow, simple, and built to last. Free shipping over $50.",
69
+ announcement: "Free shipping on orders over $50. Ships from Dallas in two business days.",
70
+ footerBlurb: "Candles poured by hand in small batches in Dallas, Texas.",
71
+ address: ["2809 Elm Street", "Dallas, TX 75226", "Studio open Saturdays, 10 to 4"],
58
72
  copyrightName: "Ember Goods",
59
73
  socials: [
60
74
  {
61
75
  label: "Instagram",
62
76
  href: "https://instagram.com",
63
- path: "M12 2.2c3.2 0 3.6 0 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s0 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58 0-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.42-.36-1.06-.41-2.23C2.2 15.58 2.2 15.2 2.2 12s0-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.2 8.8 2.2 12 2.2zm0 1.8c-3.15 0-3.5 0-4.74.07-.9.04-1.38.19-1.7.32-.43.16-.74.36-1.06.68-.32.32-.52.63-.68 1.06-.13.32-.28.8-.32 1.7C3.8 8.5 3.8 8.85 3.8 12s0 3.5.07 4.74c.04.9.19 1.38.32 1.7.16.43.36.74.68 1.06.32.32.63.52 1.06.68.32.13.8.28 1.7.32 1.24.07 1.59.07 4.74.07s3.5 0 4.74-.07c.9-.04 1.38-.19 1.7-.32.43-.16.74-.36 1.06-.68.32-.32.52-.63.68-1.06.13-.32.28-.8.32-1.7.07-1.24.07-1.59.07-4.74s0-3.5-.07-4.74c-.04-.9-.19-1.38-.32-1.7a2.85 2.85 0 0 0-.68-1.06 2.85 2.85 0 0 0-1.06-.68c-.32-.13-.8-.28-1.7-.32C15.5 4 15.15 4 12 4zm0 3.06A4.94 4.94 0 1 0 12 16.94 4.94 4.94 0 0 0 12 7.06zm0 8.15A3.21 3.21 0 1 1 12 8.8a3.21 3.21 0 0 1 0 6.4zm6.3-8.35a1.15 1.15 0 1 1-2.3 0 1.15 1.15 0 0 1 2.3 0z",
64
- },
77
+ path: "M12 2.2c3.2 0 3.6 0 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s0 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58 0-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.42-.36-1.06-.41-2.23C2.2 15.58 2.2 15.2 2.2 12s0-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.2 8.8 2.2 12 2.2zm0 1.8c-3.15 0-3.5 0-4.74.07-.9.04-1.38.19-1.7.32-.43.16-.74.36-1.06.68-.32.32-.52.63-.68 1.06-.13.32-.28.8-.32 1.7C3.8 8.5 3.8 8.85 3.8 12s0 3.5.07 4.74c.04.9.19 1.38.32 1.7.16.43.36.74.68 1.06.32.32.63.52 1.06.68.32.13.8.28 1.7.32 1.24.07 1.59.07 4.74.07s3.5 0 4.74-.07c.9-.04 1.38-.19 1.7-.32.43-.16.74-.36 1.06-.68.32-.32.52-.63.68-1.06.13-.32.28-.8.32-1.7.07-1.24.07-1.59.07-4.74s0-3.5-.07-4.74c-.04-.9-.19-1.38-.32-1.7a2.85 2.85 0 0 0-.68-1.06 2.85 2.85 0 0 0-1.06-.68c-.32-.13-.8-.28-1.7-.32C15.5 4 15.15 4 12 4zm0 3.06A4.94 4.94 0 1 0 12 16.94 4.94 4.94 0 0 0 12 7.06zm0 8.15A3.21 3.21 0 1 1 12 8.8a3.21 3.21 0 0 1 0 6.4zm6.3-8.35a1.15 1.15 0 1 1-2.3 0 1.15 1.15 0 0 1 2.3 0z", },
65
78
  ],
66
79
  },
67
80
 
68
- colors: { brand: "#9a3412", brandSoft: "#ffedd5", paper: "#faf8f5" },
81
+ colors: { brand: "#8b4a2b", brandSoft: "#efe3d6", paper: "#e9e1d5" },
69
82
 
70
83
  seo: {
71
- title: "Ember Goods — hand-poured candles, made in Dallas.",
84
+ title: "Ember Goods: candles poured by hand in Dallas",
72
85
  description:
73
- "Small-batch candles and home goods, hand-poured in Dallas. Stock counts match the studio shelf. Free shipping over $50.",
86
+ "Small-batch candles poured in Dallas. Soy and coconut wax, cotton wicks, 60-hour burn. Stock counts match the studio shelf. Free shipping over $50.",
74
87
  },
75
88
 
76
89
  hero: {
77
- tagline: "Small batch · Made in Dallas",
78
- headline: "Hand-poured candles, made to last.",
90
+ headline: "Candles poured by hand in Dallas.",
79
91
  subcopy:
80
- "We pour everything by hand in small batches, so quantities are real and limited. The stock you see below is exactly what's on the studio shelf right now.",
81
- ctaLabel: "Shop the shelf",
92
+ "Soy and coconut wax, cotton wicks, and scents we blend ourselves. We pour forty at a time, so the stock counts are the real shelf.",
93
+ ctaLabel: "Shop candles",
94
+ secondaryLabel: "See the studio",
95
+ image: "/images/hero.jpg",
96
+ imageAlt: "Three amber glass candles on a travertine shelf, the middle one lit, in late afternoon light.",
82
97
  },
83
98
 
84
99
  products: {
85
- eyebrow: "The shelf",
86
- headline: "What's in the studio right now.",
100
+ headline: "The shelf",
101
+ subcopy: "Stock updates live. When a batch sells out, it is gone until the next pour.",
87
102
  items: [
103
+ {
104
+ slug: "tobacco-oak",
105
+ name: "Tobacco & Oak",
106
+ priceCents: 4200,
107
+ description: "Cured tobacco leaf, oak barrel, and amber. Our darkest scent, poured once a season.",
108
+ size: "8 oz, 60-hour burn",
109
+ image: "/images/products/tobacco-oak.jpg",
110
+ stock: 2,
111
+ },
88
112
  {
89
113
  slug: "cedar-smoke",
90
114
  name: "Cedar & Smoke",
91
- priceCents: 3200,
92
- description: "Cedarwood, smoked vanilla, a little leather. 60-hour burn.",
93
- image: "🕯️",
115
+ priceCents: 3800,
116
+ description: "Cedarwood, smoked vanilla, and a little leather.",
117
+ size: "8 oz, 60-hour burn",
118
+ image: "/images/products/cedar-smoke.jpg",
94
119
  stock: 8,
95
120
  },
96
121
  {
97
122
  slug: "linen",
98
- name: "Fresh Linen",
99
- priceCents: 3200,
100
- description: "Clean cotton and white musk. The everyday one.",
101
- image: "🤍",
123
+ name: "Linen",
124
+ priceCents: 3800,
125
+ description: "Clean cotton, white musk, and iris.",
126
+ size: "8 oz, 60-hour burn",
127
+ image: "/images/products/linen.jpg",
102
128
  stock: 3,
103
129
  },
104
130
  {
105
- slug: "brass-holder",
106
- name: "Brass matchstick holder",
107
- priceCents: 2400,
108
- description: "Solid brass, striker on the base. Ages beautifully.",
109
- image: "🟫",
110
- stock: 12,
131
+ slug: "fig-leaf",
132
+ name: "Fig Leaf",
133
+ priceCents: 3800,
134
+ description: "Green fig, cedar, and coconut.",
135
+ size: "8 oz, 60-hour burn",
136
+ image: "/images/products/fig-leaf.jpg",
137
+ stock: 11,
111
138
  },
112
139
  {
113
- slug: "ceramic-vessel",
114
- name: "Ceramic vessel candle",
115
- priceCents: 4800,
116
- description: "Hand-thrown stoneware you'll keep long after the wax.",
117
- image: "🏺",
118
- stock: 5,
119
- },
120
- {
121
- slug: "wick-trimmer",
122
- name: "Wick trimmer",
123
- priceCents: 1800,
124
- description: "The one tool that makes a candle last. Matte black.",
125
- image: "✂️",
126
- stock: 0,
127
- },
128
- {
129
- slug: "gift-set",
130
- name: "The trio gift set",
131
- priceCents: 8400,
132
- description: "Three minis, boxed and ready to give.",
133
- image: "🎁",
134
- stock: 6,
140
+ slug: "sea-salt",
141
+ name: "Sea Salt & Sage",
142
+ priceCents: 3800,
143
+ description: "Salt air, sage, and driftwood.",
144
+ size: "8 oz, 60-hour burn",
145
+ image: "/images/products/sea-salt.jpg",
146
+ stock: 9,
135
147
  },
136
148
  ],
137
149
  },
138
150
 
139
- checkout: {
140
- confirmationMessage:
141
- "Order placed! We'll email you a payment link and ship within two business days.",
151
+ studio: {
152
+ headline: "Poured in small batches, cured for two weeks.",
153
+ body: [
154
+ "Every candle is poured, cured, and trimmed by hand in our studio in Deep Ellum. We use a soy and coconut wax blend, cotton wicks, and phthalate-free fragrance oils.",
155
+ "Each batch cures for fourteen days before it goes on the shelf, so the scent carries across the room from the first burn.",
156
+ ],
157
+ facts: [
158
+ { value: "40", label: "candles per batch" },
159
+ { value: "14 days", label: "cure before sale" },
160
+ { value: "60 hr", label: "burn time, 8 oz" },
161
+ ],
142
162
  },
143
163
 
144
- valueProps: {
145
- eyebrow: "Why Ember",
146
- headline: "Made slow, on purpose.",
147
- items: [
148
- {
149
- icon: "◍",
150
- title: "Hand-poured",
151
- body: "Every candle is poured, wicked, and labelled by hand in our Dallas studio.",
152
- },
153
- {
154
- icon: "◇",
155
- title: "Real small batch",
156
- body: "We make what we can do well. When the shelf says 3 left, there are 3 left.",
157
- },
158
- {
159
- icon: "✦",
160
- title: "Free shipping over $50",
161
- body: "Carbon-neutral shipping, plastic-free packaging, and easy returns.",
162
- },
163
- ],
164
+ checkout: {
165
+ shipping: { flatCents: 600, freeOverCents: 5000 },
166
+ reservedMessage:
167
+ "We are holding your items and will email a payment link within one business day. Orders ship from Dallas within two business days of payment.",
168
+ paidMessage:
169
+ "Your payment went through and the receipt is on its way to your inbox. Orders ship from Dallas within two business days.",
170
+ image: "/images/products/tobacco-oak.jpg", // shown on /success
164
171
  },
165
172
 
166
173
  reviews: {
167
- eyebrow: "Reviews",
168
- headline: "What people say.",
174
+ headline: "Reviews",
169
175
  items: [
170
176
  {
171
- quote: "Cedar & Smoke is the best candle I've ever bought, full stop. The brass holder is gorgeous too.",
177
+ quote: "Cedar & Smoke fills the whole living room without being loud about it. On my third one.",
172
178
  name: "Maya C.",
173
- rating: 5,
179
+ product: "Cedar & Smoke",
174
180
  },
175
181
  {
176
- quote: "Ordered the gift set for my mom. Beautiful packaging and it arrived in two days. Will reorder.",
182
+ quote: "Bought Linen for my mom. It arrived in two days, packed in paper, no plastic anywhere.",
177
183
  name: "Daniel R.",
178
- rating: 5,
184
+ product: "Linen",
179
185
  },
180
186
  {
181
- quote: "You can tell these are made by hand. The ceramic vessel is on my shelf forever now.",
187
+ quote: "Fig Leaf burned evenly all the way to the bottom of the jar. I kept the jar for pens.",
182
188
  name: "Hannah K.",
183
- rating: 5,
189
+ product: "Fig Leaf",
184
190
  },
185
191
  ],
186
192
  },
187
193
 
188
194
  policies: {
189
- eyebrow: "Good to know",
190
- headline: "Shipping & returns.",
195
+ headline: "Shipping and returns",
191
196
  items: [
192
- { title: "Shipping", body: "Ships in 2 business days from Dallas. Free over $50, $6 flat otherwise." },
193
- { title: "Returns", body: "Unused and unburned? Return within 30 days for a full refund." },
194
- { title: "Wholesale", body: "Run a shop? Email us for the line sheet and wholesale pricing." },
197
+ { title: "Shipping", body: "Ships from Dallas in two business days. Free over $50, $6 flat otherwise." },
198
+ { title: "Returns", body: "Return unused and unburned items within 30 days for a full refund." },
199
+ { title: "Local pickup", body: "Pick up at the studio on Saturdays. Reply to your order email to switch to pickup." },
200
+ { title: "Wholesale", body: "Email us for the line sheet and wholesale pricing." },
195
201
  ],
196
202
  },
197
203
  };
204
+
205
+ export function productBySlug(slug: string): ProductItem | undefined {
206
+ return siteConfig.products.items.find((p) => p.slug === slug);
207
+ }
@@ -0,0 +1,64 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { formatPrice, freeShippingRemaining, shippingCents, stockStatus } from "../lib/shop";
5
+ import { productBySlug, siteConfig } from "../lib/site.config";
6
+
7
+ const terms = { flatCents: 600, freeOverCents: 5000 };
8
+
9
+ describe("stockStatus", () => {
10
+ test("zero or less is sold out", () => {
11
+ expect(stockStatus(0)).toEqual({ kind: "sold_out", label: "Sold out" });
12
+ expect(stockStatus(-2).kind).toBe("sold_out");
13
+ });
14
+
15
+ test("three or fewer is low", () => {
16
+ expect(stockStatus(1)).toEqual({ kind: "low", label: "Last one" });
17
+ expect(stockStatus(3)).toEqual({ kind: "low", label: "Only 3 left" });
18
+ });
19
+
20
+ test("more than three shows the count", () => {
21
+ expect(stockStatus(4)).toEqual({ kind: "in_stock", label: "4 in stock" });
22
+ });
23
+ });
24
+
25
+ describe("shipping", () => {
26
+ test("flat rate below the threshold", () => {
27
+ expect(shippingCents(4999, terms)).toBe(600);
28
+ expect(freeShippingRemaining(3800, terms)).toBe(1200);
29
+ });
30
+
31
+ test("free at and above the threshold", () => {
32
+ expect(shippingCents(5000, terms)).toBe(0);
33
+ expect(freeShippingRemaining(8400, terms)).toBe(0);
34
+ });
35
+ });
36
+
37
+ test("formatPrice drops zero cents", () => {
38
+ expect(formatPrice(3800)).toBe("$38");
39
+ expect(formatPrice(1850)).toBe("$18.50");
40
+ });
41
+
42
+ describe("catalog config", () => {
43
+ const items = siteConfig.products.items;
44
+
45
+ test("slugs are unique", () => {
46
+ expect(new Set(items.map((p) => p.slug)).size).toBe(items.length);
47
+ });
48
+
49
+ test("productBySlug finds a product and misses an unknown slug", () => {
50
+ expect(productBySlug(items[0].slug)?.name).toBe(items[0].name);
51
+ expect(productBySlug("nope")).toBeUndefined();
52
+ });
53
+
54
+ // Every local image the storefront references must ship in public/.
55
+ test("every local image exists in public/", () => {
56
+ const paths = [
57
+ siteConfig.hero.image,
58
+ siteConfig.checkout.image,
59
+ ...items.map((p) => p.image),
60
+ ].filter((src) => src.startsWith("/"));
61
+ const missing = paths.filter((src) => !existsSync(join(import.meta.dir, "..", "public", src)));
62
+ expect(missing).toEqual([]);
63
+ });
64
+ });
@@ -10,3 +10,10 @@ PYLON_OWNER_EMAIL=you@yourbusiness.com
10
10
  # ── Site URL (optional) ──────────────────────────────────────────────────────
11
11
  # Used by robots.txt + sitemap.xml. Point it at your real domain in production.
12
12
  # SITE_URL=https://yourbusiness.com
13
+
14
+ # ── Demo data ────────────────────────────────────────────────────────────────
15
+ # `pylon dev` fills an empty waitlist with fictional signups. Seeding runs only
16
+ # under `pylon dev` (it checks PYLON_DEV_MODE and PYLON_DEV_WATCH_DIR), so
17
+ # `pylon start`, Docker, and Pylon Cloud deploys never seed. Set 0 to start
18
+ # empty in development. Never set 1 on a deploy.
19
+ # PYLON_DEMO_DATA=0
@@ -18,14 +18,24 @@ increment in another without refreshing.
18
18
  ## How the realtime works
19
19
 
20
20
  - `functions/joinWaitlist.ts`: a public **mutation** that validates, lowercases,
21
- and dedupes the email, then inserts one `Signup` row. The insert fires a
22
- change event.
23
- - `functions/waitlistCount.ts`: a public **query** the landing page subscribes
24
- to with `db.useReactiveQuery`. The server records that it read the `Signup`
25
- table, so every new signup re-runs it and pushes the fresh count to every open
26
- tab. No polling.
27
- - The counter island (`app/waitlist-hero.tsx`) is wrapped in `<EnsureGuest>`,
28
- which mints an anonymous session so the live WebSocket can connect.
21
+ and dedupes the email, inserts one `Signup` row, and rewrites the PII-free
22
+ `WaitlistStat` row with the new count.
23
+ - The landing page reads `WaitlistStat` with `db.useQuery`. The row syncs to
24
+ every open tab, so the counter moves as soon as anyone joins. No polling.
25
+ - The counter island (`app/(marketing)/waitlist-hero.tsx`) is wrapped in
26
+ `<EnsureGuest>`, which mints an anonymous session so the live connection can
27
+ open.
28
+
29
+ The counter is the `Signup` table's row count. `counter.importedCount` in
30
+ `lib/site.config.ts` adds signups you collected somewhere else before this site
31
+ (an old form, a spreadsheet). It ships as 0.
32
+
33
+ ## Demo data
34
+
35
+ In `pylon dev`, an empty waitlist gets about 180 fictional signups from the last
36
+ 30 days, so the counter, chart, and list have data (`functions/seedDemo.ts`,
37
+ `lib/demo.ts`). Seeding runs only when `PYLON_DEMO_DATA` is on, or when it is unset and the app runs under `pylon dev`, and only while the `Signup` table is empty. `pylon start`, Docker, and Pylon Cloud deploys do not seed, because only `pylon dev` sets `PYLON_DEV_WATCH_DIR`. Set `PYLON_DEMO_DATA=0` to start
38
+ empty in development. Delete both files once you have real signups.
29
39
 
30
40
  ## Privacy
31
41
 
@@ -47,22 +57,28 @@ account can see signups; anyone else gets a locked screen.
47
57
 
48
58
  ## Rebrand it
49
59
 
50
- Brand, colors, hero copy, value props, social proof, and FAQ content live in
60
+ Brand, colors, hero copy, the launch date, value props, and FAQ content live in
51
61
  **`lib/site.config.ts`**. Edit or generate that file to update the page without
52
62
  changing JSX or CSS.
53
63
 
64
+ The tab icon is `app/icon.svg` (with `public/favicon.ico` for browsers that
65
+ request `/favicon.ico`). Both carry this brand's letter and colors; replace
66
+ them when you rename the brand.
67
+
54
68
  ## Layout
55
69
 
56
70
  ```
57
71
  app.ts data model + manifest (Signup, User, policies, auth)
58
72
  lib/site.config.ts ALL business copy + brand + colors (edit this)
59
73
  lib/owner.ts owner-email gate (PYLON_OWNER_EMAIL)
60
- lib/stats.ts shared dashboard-stats types
74
+ lib/stats.ts dashboard types + the public count
75
+ lib/launch.ts the launch line from hero.launchDate
76
+ lib/demo.ts demo signups + the dev-only gate
61
77
  functions/joinWaitlist.ts public mutation: validate + dedupe + insert
62
- functions/waitlistCount.ts public reactive query: the live counter
78
+ functions/seedDemo.ts dev-only demo signups (see Demo data)
63
79
  functions/waitlistStats.ts owner-only reactive query: total + chart + list
64
- app/page.tsx the landing page (server-rendered)
65
- app/waitlist-hero.tsx client island: signup form + live counter
80
+ app/(marketing)/page.tsx the landing page (server-rendered)
81
+ app/(marketing)/waitlist-hero.tsx client island: signup form + live counter
66
82
  app/login/page.tsx owner sign-in
67
83
  app/dashboard/ owner dashboard (auth-gated, live)
68
84
  app/globals.css Tailwind entrypoint (compiled by Pylon)
@@ -3,6 +3,7 @@ import { type Metadata } from "@pylonsync/react";
3
3
  import { WRAP, FactList, ProductMock } from "@/components/marketing";
4
4
  import { SignupForm, LiveCount } from "./waitlist-hero";
5
5
  import { siteConfig } from "@/lib/site.config";
6
+ import { launchLabel } from "@/lib/launch";
6
7
 
7
8
  // SEO metadata: server-rendered into <head>, so the page is fully indexable.
8
9
  // All copy lives in lib/site.config.ts; edit it there to rebrand.
@@ -25,6 +26,7 @@ export const metadata: Metadata = {
25
26
  // Signed-in state (the nav's "Dashboard" link) is resolved in the layout.
26
27
  export default function LandingPage() {
27
28
  const { hero, counter, mock, facts, faq } = siteConfig;
29
+ const launch = launchLabel(hero.launchDate, Date.now());
28
30
 
29
31
  return (
30
32
  <div>
@@ -36,9 +38,14 @@ export default function LandingPage() {
36
38
  </h1>
37
39
  <p className="mt-6 max-w-md text-[17px] leading-relaxed text-chalk-2">{hero.subcopy}</p>
38
40
  <div className="mt-8 max-w-md">
39
- <SignupForm hero={hero} />
41
+ <SignupForm hero={hero} showCountNote={counter.enabled} />
42
+ </div>
43
+ <div className="mt-8 flex flex-col gap-2 border-t border-line pt-5">
44
+ {counter.enabled ? (
45
+ <LiveCount importedCount={counter.importedCount} label={counter.label} labelOne={counter.labelOne} />
46
+ ) : null}
47
+ {launch ? <p className="font-mono-ui text-[13px] text-brand">{launch}</p> : null}
40
48
  </div>
41
- <p className="font-mono-ui mt-6 text-[13px] text-brand">{hero.launchNote}</p>
42
49
  </div>
43
50
  <ProductMock
44
51
  windowTitle={mock.windowTitle}
@@ -56,11 +63,6 @@ export default function LandingPage() {
56
63
  <div className="mt-8">
57
64
  <FactList items={facts.items} />
58
65
  </div>
59
- {counter.enabled ? (
60
- <div className="mt-12 border-t border-line pt-5">
61
- <LiveCount seed={counter.seedCount ?? 0} label={counter.label} />
62
- </div>
63
- ) : null}
64
66
  </section>
65
67
 
66
68
  {/* ============================== FAQ ============================= */}
@@ -4,9 +4,10 @@ import React, { useEffect, useRef, useState } from "react";
4
4
  import { db, callFn } from "@pylonsync/react";
5
5
  import { EnsureGuest } from "@pylonsync/client";
6
6
  import type { WaitlistConfig } from "@/lib/site.config";
7
+ import { publicCount } from "@/lib/stats";
7
8
 
8
9
  // The two client islands on the landing page: the email-capture form (in the
9
- // hero) and the LIVE signup count (under the facts). The count is a live
10
+ // hero) and the LIVE signup count (in the hero, under the form). The count is a live
10
11
  // `db.useQuery("WaitlistStat")` over the public, PII-free aggregate row, so
11
12
  // the moment anyone (this tab or another) submits an email, joinWaitlist
12
13
  // updates that row and the new count syncs to every open tab through the
@@ -20,7 +21,13 @@ import type { WaitlistConfig } from "@/lib/site.config";
20
21
 
21
22
  /* ----------------------------- signup form ---------------------------- */
22
23
 
23
- export function SignupForm({ hero }: { hero: WaitlistConfig["hero"] }) {
24
+ export function SignupForm({
25
+ hero,
26
+ showCountNote,
27
+ }: {
28
+ hero: WaitlistConfig["hero"];
29
+ showCountNote: boolean;
30
+ }) {
24
31
  const [email, setEmail] = useState("");
25
32
  const [status, setStatus] = useState<"idle" | "sending" | "done">("idle");
26
33
  const [alreadyJoined, setAlreadyJoined] = useState(false);
@@ -57,9 +64,11 @@ export function SignupForm({ hero }: { hero: WaitlistConfig["hero"] }) {
57
64
  <p className="text-[15px] font-medium text-chalk">
58
65
  {alreadyJoined ? "You are already on the list." : hero.successMessage}
59
66
  </p>
60
- <p className="mt-1 font-mono-ui text-[12px] text-chalk-2">
61
- The count below went up for everyone.
62
- </p>
67
+ {!alreadyJoined && showCountNote ? (
68
+ <p className="mt-1 font-mono-ui text-[12px] text-chalk-2">
69
+ The count below now includes you.
70
+ </p>
71
+ ) : null}
63
72
  </div>
64
73
  );
65
74
  }
@@ -76,7 +85,7 @@ export function SignupForm({ hero }: { hero: WaitlistConfig["hero"] }) {
76
85
  placeholder={hero.emailPlaceholder}
77
86
  aria-label="Email address"
78
87
  required
79
- className="h-11 flex-1 rounded-md border border-line bg-paper px-3.5 text-[15px] text-chalk outline-none transition placeholder:text-chalk-2 focus:border-brand"
88
+ className="h-11 w-full min-w-0 rounded-md border border-line bg-paper sm:flex-1 px-3.5 text-[15px] text-chalk outline-none transition placeholder:text-chalk-2 focus:border-brand"
80
89
  />
81
90
  <button
82
91
  type="submit"
@@ -101,44 +110,68 @@ interface WaitlistStatRow {
101
110
  updatedAt: string;
102
111
  }
103
112
 
104
- export function LiveCount({ seed, label }: { seed: number; label: string }) {
113
+ export function LiveCount({
114
+ importedCount,
115
+ label,
116
+ labelOne,
117
+ }: {
118
+ importedCount: number;
119
+ label: string;
120
+ labelOne: string;
121
+ }) {
105
122
  return (
106
- <EnsureGuest fallback={<CountView value={seed} label={label} />}>
107
- <LiveCountInner seed={seed} label={label} />
123
+ <EnsureGuest fallback={null}>
124
+ <LiveCountInner importedCount={importedCount} label={label} labelOne={labelOne} />
108
125
  </EnsureGuest>
109
126
  );
110
127
  }
111
128
 
112
- function LiveCountInner({ seed, label }: { seed: number; label: string }) {
113
- // Live entity query over the public WaitlistStat row. `db.useQuery` re-renders
114
- // the instant the row changes, in THIS tab and every other open tab, because
115
- // the change syncs through the shared replica. `loading` is true until the
116
- // first sync settles; until then we show the seed (never a flash of 0).
117
- const { data, loading } = db.useQuery<WaitlistStatRow>("WaitlistStat");
118
- const real = data.length > 0 ? data[0].count : 0;
119
- const value = seed + real;
120
- return <CountView value={value} label={label} live={!loading} />;
121
- }
122
-
123
- function CountView({
124
- value,
129
+ function LiveCountInner({
130
+ importedCount,
125
131
  label,
126
- live,
132
+ labelOne,
127
133
  }: {
128
- value: number;
134
+ importedCount: number;
129
135
  label: string;
130
- live?: boolean;
136
+ labelOne: string;
131
137
  }) {
138
+ // Live entity query over the public WaitlistStat row. `db.useQuery` re-renders
139
+ // the instant the row changes, in this tab and every other open tab, because
140
+ // the change syncs through the shared replica.
141
+ const { data, loading } = db.useQuery<WaitlistStatRow>("WaitlistStat");
142
+
143
+ // In `pylon dev` an empty waitlist gets demo signups (functions/seedDemo.ts).
144
+ // The call goes out only when the synced list is empty, so a live site with
145
+ // signups never makes it.
146
+ useSeedWhenEmpty(!loading && data.length === 0);
147
+
148
+ // Nothing renders until the first sync settles or while the list is empty,
149
+ // so the page never shows a placeholder number or "0 people on the list".
150
+ if (loading) return null;
151
+ const value = publicCount(data, importedCount);
152
+ if (value === 0) return null;
153
+ return <CountView value={value} label={value === 1 ? labelOne : label} />;
154
+ }
155
+
156
+ function useSeedWhenEmpty(empty: boolean) {
157
+ const sent = useRef(false);
158
+ useEffect(() => {
159
+ if (!empty || sent.current) return;
160
+ sent.current = true;
161
+ void callFn("seedDemo", {}).catch(() => {});
162
+ }, [empty]);
163
+ }
164
+
165
+ function CountView({ value, label }: { value: number; label: string }) {
132
166
  const shown = useCountUp(value);
133
167
  return (
134
168
  <p className="flex items-center gap-3 font-mono-ui text-[14px] text-chalk-2">
135
- <span
136
- aria-hidden
137
- className={live ? "size-2 rounded-full bg-brand" : "size-2 rounded-full bg-line"}
138
- />
139
- <span className="tabular-nums text-chalk">{shown.toLocaleString()}</span>
169
+ <span aria-hidden className="size-2 rounded-full bg-brand" />
170
+ <span className="tabular-nums text-chalk" data-testid="live-count">
171
+ {shown.toLocaleString("en-US")}
172
+ </span>
140
173
  <span>{label}</span>
141
- <span className="text-[12px]">{live ? "live" : ""}</span>
174
+ <span className="text-[12px]">live</span>
142
175
  </p>
143
176
  );
144
177
  }