@pylonsync/create-pylon 0.19.1 → 0.21.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 (696) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/_root/AGENTS.md +1 -1
  4. package/templates/agency/.env.example +7 -0
  5. package/templates/agency/AGENTS.md +1 -1
  6. package/templates/agency/README.md +28 -3
  7. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  8. package/templates/agency/app/(marketing)/page.tsx +3 -3
  9. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  10. package/templates/agency/app/auth-form.tsx +4 -4
  11. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  12. package/templates/agency/app/dashboard/page.tsx +14 -16
  13. package/templates/agency/app/icon.svg +3 -0
  14. package/templates/agency/app/login/page.tsx +12 -14
  15. package/templates/agency/components/marketing.tsx +12 -5
  16. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  17. package/templates/agency/lib/agency.ts +22 -0
  18. package/templates/agency/lib/demo.ts +118 -0
  19. package/templates/agency/lib/site.config.ts +19 -13
  20. package/templates/agency/public/favicon.ico +0 -0
  21. package/templates/agency/tests/agency.test.ts +57 -0
  22. package/templates/ai-chat/.env.example +13 -27
  23. package/templates/ai-chat/AGENTS.md +1 -1
  24. package/templates/ai-chat/README.md +53 -60
  25. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  26. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  27. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  28. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  29. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  30. package/templates/ai-chat/app/auth-form.tsx +45 -38
  31. package/templates/ai-chat/app/error.tsx +9 -12
  32. package/templates/ai-chat/app/globals.css +464 -102
  33. package/templates/ai-chat/app/layout.tsx +11 -19
  34. package/templates/ai-chat/app/login/page.tsx +39 -24
  35. package/templates/ai-chat/app.ts +118 -43
  36. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  37. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  38. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  39. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  40. package/templates/ai-chat/components/chat/message.tsx +183 -0
  41. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  42. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  43. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  44. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  45. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  46. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  47. package/templates/ai-chat/functions/createConversation.ts +28 -0
  48. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  49. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  50. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  51. package/templates/ai-chat/functions/respond.ts +88 -0
  52. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  53. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  54. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  55. package/templates/ai-chat/lib/chat.ts +277 -0
  56. package/templates/ai-chat/lib/claim.ts +15 -0
  57. package/templates/ai-chat/lib/examples.ts +179 -0
  58. package/templates/ai-chat/lib/highlight.ts +107 -0
  59. package/templates/ai-chat/lib/markdown.ts +317 -0
  60. package/templates/ai-chat/lib/provider.ts +38 -0
  61. package/templates/ai-chat/lib/site.config.ts +55 -39
  62. package/templates/ai-chat/tests/chat.test.ts +174 -0
  63. package/templates/ai-chat/tests/components.test.tsx +57 -0
  64. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  65. package/templates/ai-chat/tests/provider.test.ts +32 -0
  66. package/templates/ai-studio/AGENTS.md +1 -1
  67. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  68. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  69. package/templates/backend/consumer/apps/api/app.ts +166 -0
  70. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  71. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  72. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  73. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  74. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  75. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  76. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  77. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  78. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  79. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  80. package/templates/backend/consumer/apps/api/package.json +8 -2
  81. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  85. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  86. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  87. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  88. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  89. package/templates/barebones/AGENTS.md +1 -1
  90. package/templates/chat/AGENTS.md +1 -1
  91. package/templates/chat/README.md +43 -21
  92. package/templates/chat/app/chat-client.tsx +495 -85
  93. package/templates/chat/app/error.tsx +1 -1
  94. package/templates/chat/app/globals.css +170 -54
  95. package/templates/chat/app/layout.tsx +12 -9
  96. package/templates/chat/app/not-found.tsx +4 -2
  97. package/templates/chat/app/page.tsx +9 -19
  98. package/templates/chat/app.ts +96 -17
  99. package/templates/chat/components/chat/avatar.tsx +45 -0
  100. package/templates/chat/components/chat/composer.tsx +110 -0
  101. package/templates/chat/components/chat/message-list.tsx +285 -0
  102. package/templates/chat/components/chat/sidebar.tsx +190 -0
  103. package/templates/chat/functions/joinChat.ts +83 -0
  104. package/templates/chat/functions/renameMember.ts +25 -0
  105. package/templates/chat/lib/chat.ts +188 -0
  106. package/templates/chat/lib/seed.ts +78 -0
  107. package/templates/chat/tests/chat.test.ts +159 -0
  108. package/templates/chat/tests/composer.test.tsx +54 -0
  109. package/templates/consumer/AGENTS.md +1 -1
  110. package/templates/consumer/README.md +3 -3
  111. package/templates/consumer/app/globals.css +10 -3
  112. package/templates/consumer/app/icon.svg +1 -0
  113. package/templates/consumer/components/social/avatar.tsx +25 -11
  114. package/templates/consumer/functions/seedFeed.ts +3 -3
  115. package/templates/consumer/lib/seed.ts +43 -85
  116. package/templates/consumer/public/favicon.ico +0 -0
  117. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  118. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  119. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  120. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  121. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  122. package/templates/consumer/public/images/posts/run.jpg +0 -0
  123. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  124. package/templates/consumer/tests/seed.test.ts +39 -0
  125. package/templates/creator/.env.example +7 -0
  126. package/templates/creator/AGENTS.md +1 -1
  127. package/templates/creator/README.md +24 -5
  128. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  129. package/templates/creator/app/auth-form.tsx +7 -7
  130. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  131. package/templates/creator/app/dashboard/page.tsx +18 -18
  132. package/templates/creator/app/icon.svg +2 -0
  133. package/templates/creator/app/login/page.tsx +14 -14
  134. package/templates/creator/app.ts +4 -4
  135. package/templates/creator/functions/seedDemo.ts +32 -0
  136. package/templates/creator/functions/subscribe.ts +4 -15
  137. package/templates/creator/lib/demo.ts +94 -0
  138. package/templates/creator/lib/site.config.ts +15 -10
  139. package/templates/creator/lib/stats.ts +35 -0
  140. package/templates/creator/public/favicon.ico +0 -0
  141. package/templates/creator/tests/counter.test.ts +92 -0
  142. package/templates/creator/tests/live-count.test.tsx +45 -0
  143. package/templates/crm/AGENTS.md +1 -1
  144. package/templates/crm/README.md +22 -1
  145. package/templates/crm/app/apple-icon.png +0 -0
  146. package/templates/crm/app/companies/companies-view.tsx +32 -14
  147. package/templates/crm/app/companies/page.tsx +2 -1
  148. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  149. package/templates/crm/app/contacts/page.tsx +2 -1
  150. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  151. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  152. package/templates/crm/app/globals.css +29 -14
  153. package/templates/crm/app/icon.svg +1 -0
  154. package/templates/crm/app/layout.tsx +7 -5
  155. package/templates/crm/app/page.tsx +2 -1
  156. package/templates/crm/app/pipeline-view.tsx +5 -5
  157. package/templates/crm/app/workspace.tsx +29 -17
  158. package/templates/crm/app.ts +5 -5
  159. package/templates/crm/components/activity-timeline.tsx +14 -4
  160. package/templates/crm/components/app-shell.tsx +94 -0
  161. package/templates/crm/components/auth-form.tsx +35 -5
  162. package/templates/crm/components/avatar.tsx +5 -1
  163. package/templates/crm/components/board-skeleton.tsx +5 -5
  164. package/templates/crm/components/brand-mark.tsx +25 -0
  165. package/templates/crm/components/command-palette.tsx +17 -17
  166. package/templates/crm/components/data-table.tsx +97 -36
  167. package/templates/crm/components/deal-card.tsx +23 -27
  168. package/templates/crm/components/filter-tabs.tsx +101 -0
  169. package/templates/crm/components/metrics-bar.tsx +6 -6
  170. package/templates/crm/components/nav.tsx +15 -0
  171. package/templates/crm/components/page-header.tsx +43 -7
  172. package/templates/crm/components/pipeline-board.tsx +16 -10
  173. package/templates/crm/components/require-auth.tsx +24 -26
  174. package/templates/crm/components/sidebar.tsx +54 -49
  175. package/templates/crm/components/stage-badge.tsx +4 -2
  176. package/templates/crm/components/ui/button.tsx +5 -4
  177. package/templates/crm/components/ui/dialog.tsx +1 -1
  178. package/templates/crm/components/ui/input.tsx +1 -1
  179. package/templates/crm/components/ui/select.tsx +1 -1
  180. package/templates/crm/components/ui/textarea.tsx +1 -1
  181. package/templates/crm/functions/seedWorkspace.ts +32 -17
  182. package/templates/crm/lib/brand.ts +9 -0
  183. package/templates/crm/lib/demo-team.ts +51 -0
  184. package/templates/crm/lib/pipeline.ts +37 -0
  185. package/templates/crm/lib/search.ts +4 -3
  186. package/templates/crm/lib/seed.ts +53 -8
  187. package/templates/crm/public/favicon.ico +0 -0
  188. package/templates/crm/tests/components.test.tsx +5 -5
  189. package/templates/crm/tests/demo-team.test.ts +18 -0
  190. package/templates/crm/tests/pipeline.test.ts +59 -3
  191. package/templates/directory/.env.example +7 -0
  192. package/templates/directory/AGENTS.md +1 -1
  193. package/templates/directory/README.md +22 -3
  194. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  195. package/templates/directory/app/(marketing)/page.tsx +6 -4
  196. package/templates/directory/app/auth-form.tsx +4 -4
  197. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  198. package/templates/directory/app/dashboard/page.tsx +16 -14
  199. package/templates/directory/app/icon.svg +4 -0
  200. package/templates/directory/app/login/page.tsx +16 -15
  201. package/templates/directory/functions/seedDemo.ts +43 -0
  202. package/templates/directory/lib/demo.ts +145 -0
  203. package/templates/directory/lib/directory.ts +27 -0
  204. package/templates/directory/lib/site.config.ts +21 -16
  205. package/templates/directory/public/favicon.ico +0 -0
  206. package/templates/directory/tests/directory.test.ts +46 -0
  207. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  208. package/templates/expo/consumer/apps/expo/app.json +9 -1
  209. package/templates/expo/consumer/apps/expo/package.json +2 -1
  210. package/templates/helpdesk/AGENTS.md +1 -1
  211. package/templates/helpdesk/README.md +21 -0
  212. package/templates/helpdesk/app/apple-icon.png +0 -0
  213. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  214. package/templates/helpdesk/app/customers/page.tsx +2 -1
  215. package/templates/helpdesk/app/globals.css +29 -14
  216. package/templates/helpdesk/app/icon.svg +1 -0
  217. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  218. package/templates/helpdesk/app/layout.tsx +7 -5
  219. package/templates/helpdesk/app/page.tsx +4 -7
  220. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  221. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  222. package/templates/helpdesk/app/workspace.tsx +33 -22
  223. package/templates/helpdesk/app.ts +2 -2
  224. package/templates/helpdesk/components/app-shell.tsx +94 -0
  225. package/templates/helpdesk/components/auth-form.tsx +35 -5
  226. package/templates/helpdesk/components/avatar.tsx +5 -1
  227. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  228. package/templates/helpdesk/components/command-palette.tsx +17 -17
  229. package/templates/helpdesk/components/data-table.tsx +97 -36
  230. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  231. package/templates/helpdesk/components/nav.tsx +14 -0
  232. package/templates/helpdesk/components/page-header.tsx +43 -7
  233. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  234. package/templates/helpdesk/components/require-auth.tsx +24 -26
  235. package/templates/helpdesk/components/sidebar.tsx +54 -48
  236. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  237. package/templates/helpdesk/components/thread.tsx +7 -7
  238. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  239. package/templates/helpdesk/components/ui/button.tsx +5 -4
  240. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  241. package/templates/helpdesk/components/ui/input.tsx +1 -1
  242. package/templates/helpdesk/components/ui/select.tsx +1 -1
  243. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  244. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  245. package/templates/helpdesk/lib/brand.ts +9 -0
  246. package/templates/helpdesk/lib/demo-team.ts +51 -0
  247. package/templates/helpdesk/lib/search.ts +4 -3
  248. package/templates/helpdesk/lib/seed.ts +43 -2
  249. package/templates/helpdesk/lib/tickets.ts +68 -20
  250. package/templates/helpdesk/public/favicon.ico +0 -0
  251. package/templates/helpdesk/tests/components.test.tsx +10 -0
  252. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  253. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  254. package/templates/imessage/.env.example +49 -0
  255. package/templates/imessage/AGENTS.md +153 -0
  256. package/templates/imessage/CLAUDE.md +7 -0
  257. package/templates/imessage/README.md +187 -0
  258. package/templates/imessage/app/console.tsx +382 -0
  259. package/templates/imessage/app/contacts/page.tsx +13 -0
  260. package/templates/imessage/app/error.tsx +16 -0
  261. package/templates/imessage/app/gate.tsx +31 -0
  262. package/templates/imessage/app/globals.css +183 -0
  263. package/templates/imessage/app/layout.tsx +17 -0
  264. package/templates/imessage/app/login/login-form.tsx +115 -0
  265. package/templates/imessage/app/login/page.tsx +30 -0
  266. package/templates/imessage/app/not-found.tsx +13 -0
  267. package/templates/imessage/app/page.tsx +14 -0
  268. package/templates/imessage/app/robots.ts +6 -0
  269. package/templates/imessage/app/setup/page.tsx +13 -0
  270. package/templates/imessage/app/sign-out.tsx +20 -0
  271. package/templates/imessage/app.ts +299 -0
  272. package/templates/imessage/bunfig +4 -0
  273. package/templates/imessage/components/avatar.tsx +46 -0
  274. package/templates/imessage/components/contact-panel.tsx +263 -0
  275. package/templates/imessage/components/contacts-view.tsx +172 -0
  276. package/templates/imessage/components/conversation-list.tsx +129 -0
  277. package/templates/imessage/components/nav-rail.tsx +67 -0
  278. package/templates/imessage/components/setup-view.tsx +408 -0
  279. package/templates/imessage/components/thread.tsx +229 -0
  280. package/templates/imessage/components/ui/badge.tsx +37 -0
  281. package/templates/imessage/components/ui/button.tsx +56 -0
  282. package/templates/imessage/components/ui/card.tsx +90 -0
  283. package/templates/imessage/components/ui/dialog.tsx +110 -0
  284. package/templates/imessage/components/ui/input.tsx +30 -0
  285. package/templates/imessage/components/ui/label.tsx +21 -0
  286. package/templates/imessage/components/ui/select.tsx +39 -0
  287. package/templates/imessage/components/ui/table.tsx +124 -0
  288. package/templates/imessage/components/ui/textarea.tsx +24 -0
  289. package/templates/imessage/components.json +20 -0
  290. package/templates/imessage/functions/answerNow.ts +34 -0
  291. package/templates/imessage/functions/assistant.ts +96 -0
  292. package/templates/imessage/functions/beginTurn.ts +101 -0
  293. package/templates/imessage/functions/cancelReminder.ts +13 -0
  294. package/templates/imessage/functions/claimOutbound.ts +44 -0
  295. package/templates/imessage/functions/clearDemo.ts +18 -0
  296. package/templates/imessage/functions/deleteNote.ts +11 -0
  297. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  298. package/templates/imessage/functions/finishTurn.ts +84 -0
  299. package/templates/imessage/functions/fireReminder.ts +42 -0
  300. package/templates/imessage/functions/ingestInbound.ts +22 -0
  301. package/templates/imessage/functions/ownerAccount.ts +13 -0
  302. package/templates/imessage/functions/processTurn.ts +74 -0
  303. package/templates/imessage/functions/relayApply.ts +102 -0
  304. package/templates/imessage/functions/relaySync.ts +63 -0
  305. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  306. package/templates/imessage/functions/resolveTurn.ts +25 -0
  307. package/templates/imessage/functions/saveContact.ts +27 -0
  308. package/templates/imessage/functions/seedDemo.ts +70 -0
  309. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  310. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  311. package/templates/imessage/functions/setContactStatus.ts +17 -0
  312. package/templates/imessage/functions/settleOutbound.ts +35 -0
  313. package/templates/imessage/functions/setupStatus.ts +33 -0
  314. package/templates/imessage/functions/sweep.ts +58 -0
  315. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  316. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  317. package/templates/imessage/functions/toolListNotes.ts +16 -0
  318. package/templates/imessage/functions/toolListReminders.ts +17 -0
  319. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  320. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  321. package/templates/imessage/functions/updateSettings.ts +48 -0
  322. package/templates/imessage/gitignore +11 -0
  323. package/templates/imessage/lib/agent-call.ts +251 -0
  324. package/templates/imessage/lib/assistant.ts +74 -0
  325. package/templates/imessage/lib/chunk.ts +135 -0
  326. package/templates/imessage/lib/demo.ts +68 -0
  327. package/templates/imessage/lib/format.ts +170 -0
  328. package/templates/imessage/lib/gate.ts +104 -0
  329. package/templates/imessage/lib/handles.ts +67 -0
  330. package/templates/imessage/lib/provider.ts +38 -0
  331. package/templates/imessage/lib/relay-protocol.ts +95 -0
  332. package/templates/imessage/lib/reminders.ts +41 -0
  333. package/templates/imessage/lib/secrets.ts +59 -0
  334. package/templates/imessage/lib/sendblue.ts +173 -0
  335. package/templates/imessage/lib/store.ts +407 -0
  336. package/templates/imessage/lib/transport.ts +135 -0
  337. package/templates/imessage/lib/utils.ts +10 -0
  338. package/templates/imessage/package.json +40 -0
  339. package/templates/imessage/relay/.env.example +14 -0
  340. package/templates/imessage/relay/applescript.ts +70 -0
  341. package/templates/imessage/relay/chatdb.ts +165 -0
  342. package/templates/imessage/relay/client.ts +41 -0
  343. package/templates/imessage/relay/main.ts +137 -0
  344. package/templates/imessage/relay/state.ts +35 -0
  345. package/templates/imessage/tests/agent-call.test.ts +112 -0
  346. package/templates/imessage/tests/applescript.test.ts +88 -0
  347. package/templates/imessage/tests/chatdb.test.ts +139 -0
  348. package/templates/imessage/tests/chunk.test.ts +61 -0
  349. package/templates/imessage/tests/fake-ctx.ts +88 -0
  350. package/templates/imessage/tests/ingest.test.ts +141 -0
  351. package/templates/imessage/tests/lib.test.ts +99 -0
  352. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  353. package/templates/imessage/tests/secrets.test.ts +36 -0
  354. package/templates/imessage/tests/sendblue.test.ts +143 -0
  355. package/templates/imessage/tests/setup.ts +5 -0
  356. package/templates/imessage/tsconfig.json +18 -0
  357. package/templates/inventory/AGENTS.md +1 -1
  358. package/templates/inventory/README.md +21 -0
  359. package/templates/inventory/app/apple-icon.png +0 -0
  360. package/templates/inventory/app/globals.css +29 -14
  361. package/templates/inventory/app/icon.svg +1 -0
  362. package/templates/inventory/app/layout.tsx +7 -5
  363. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  364. package/templates/inventory/app/movements/page.tsx +2 -1
  365. package/templates/inventory/app/page.tsx +4 -6
  366. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  367. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  368. package/templates/inventory/app/products-view.tsx +74 -33
  369. package/templates/inventory/app/workspace.tsx +34 -16
  370. package/templates/inventory/app.ts +10 -2
  371. package/templates/inventory/components/app-shell.tsx +94 -0
  372. package/templates/inventory/components/auth-form.tsx +35 -5
  373. package/templates/inventory/components/avatar.tsx +5 -1
  374. package/templates/inventory/components/brand-mark.tsx +25 -0
  375. package/templates/inventory/components/category-tile.tsx +49 -0
  376. package/templates/inventory/components/command-palette.tsx +17 -17
  377. package/templates/inventory/components/data-table.tsx +97 -36
  378. package/templates/inventory/components/filter-tabs.tsx +101 -0
  379. package/templates/inventory/components/nav.tsx +14 -0
  380. package/templates/inventory/components/page-header.tsx +43 -7
  381. package/templates/inventory/components/require-auth.tsx +24 -26
  382. package/templates/inventory/components/sidebar.tsx +54 -48
  383. package/templates/inventory/components/stock-level.tsx +30 -14
  384. package/templates/inventory/components/summary-bar.tsx +8 -15
  385. package/templates/inventory/components/ui/button.tsx +5 -4
  386. package/templates/inventory/components/ui/dialog.tsx +1 -1
  387. package/templates/inventory/components/ui/input.tsx +1 -1
  388. package/templates/inventory/components/ui/select.tsx +1 -1
  389. package/templates/inventory/components/ui/textarea.tsx +1 -1
  390. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  391. package/templates/inventory/lib/brand.ts +9 -0
  392. package/templates/inventory/lib/demo-team.ts +51 -0
  393. package/templates/inventory/lib/search.ts +4 -3
  394. package/templates/inventory/lib/seed.ts +29 -2
  395. package/templates/inventory/lib/stock.ts +32 -0
  396. package/templates/inventory/public/favicon.ico +0 -0
  397. package/templates/inventory/tests/demo-team.test.ts +18 -0
  398. package/templates/inventory/tests/stock.test.ts +29 -0
  399. package/templates/invoices/AGENTS.md +1 -1
  400. package/templates/invoices/README.md +16 -0
  401. package/templates/invoices/app/apple-icon.png +0 -0
  402. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  403. package/templates/invoices/app/clients/page.tsx +2 -1
  404. package/templates/invoices/app/globals.css +29 -14
  405. package/templates/invoices/app/icon.svg +1 -0
  406. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  407. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  408. package/templates/invoices/app/invoices-view.tsx +81 -32
  409. package/templates/invoices/app/layout.tsx +7 -5
  410. package/templates/invoices/app/page.tsx +4 -6
  411. package/templates/invoices/app/workspace.tsx +26 -16
  412. package/templates/invoices/app.ts +2 -2
  413. package/templates/invoices/components/app-shell.tsx +94 -0
  414. package/templates/invoices/components/auth-form.tsx +35 -5
  415. package/templates/invoices/components/avatar.tsx +5 -1
  416. package/templates/invoices/components/brand-mark.tsx +26 -0
  417. package/templates/invoices/components/command-palette.tsx +17 -17
  418. package/templates/invoices/components/data-table.tsx +97 -36
  419. package/templates/invoices/components/filter-tabs.tsx +101 -0
  420. package/templates/invoices/components/line-items.tsx +19 -14
  421. package/templates/invoices/components/nav.tsx +14 -0
  422. package/templates/invoices/components/page-header.tsx +43 -7
  423. package/templates/invoices/components/require-auth.tsx +24 -26
  424. package/templates/invoices/components/sidebar.tsx +54 -48
  425. package/templates/invoices/components/status-badge.tsx +7 -7
  426. package/templates/invoices/components/summary-bar.tsx +5 -5
  427. package/templates/invoices/components/ui/button.tsx +5 -4
  428. package/templates/invoices/components/ui/dialog.tsx +1 -1
  429. package/templates/invoices/components/ui/input.tsx +1 -1
  430. package/templates/invoices/components/ui/select.tsx +1 -1
  431. package/templates/invoices/components/ui/textarea.tsx +1 -1
  432. package/templates/invoices/functions/recordPayment.ts +3 -0
  433. package/templates/invoices/lib/billing.ts +40 -0
  434. package/templates/invoices/lib/brand.ts +9 -0
  435. package/templates/invoices/lib/search.ts +4 -3
  436. package/templates/invoices/public/favicon.ico +0 -0
  437. package/templates/invoices/tests/billing.test.ts +27 -0
  438. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  444. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  445. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  446. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  447. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  448. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  449. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  450. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  451. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  452. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  453. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  454. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  455. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  456. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  457. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  458. package/templates/local-service/.env.example +7 -0
  459. package/templates/local-service/AGENTS.md +1 -1
  460. package/templates/local-service/README.md +26 -2
  461. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  462. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  463. package/templates/local-service/app/auth-form.tsx +8 -8
  464. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  465. package/templates/local-service/app/dashboard/page.tsx +14 -14
  466. package/templates/local-service/app/icon.svg +2 -0
  467. package/templates/local-service/app/login/page.tsx +15 -15
  468. package/templates/local-service/functions/seedDemo.ts +49 -0
  469. package/templates/local-service/lib/demo.ts +135 -0
  470. package/templates/local-service/lib/hours.ts +44 -0
  471. package/templates/local-service/lib/site.config.ts +8 -3
  472. package/templates/local-service/lib/slots.ts +3 -0
  473. package/templates/local-service/public/favicon.ico +0 -0
  474. package/templates/local-service/tests/booking.test.ts +104 -0
  475. package/templates/marketplace/AGENTS.md +1 -1
  476. package/templates/marketplace/README.md +23 -15
  477. package/templates/marketplace/app/globals.css +172 -114
  478. package/templates/marketplace/app/layout.tsx +75 -30
  479. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  480. package/templates/marketplace/app/me/page.tsx +4 -4
  481. package/templates/marketplace/app/page.tsx +221 -298
  482. package/templates/marketplace/app/sell/page.tsx +8 -11
  483. package/templates/marketplace/app.ts +32 -7
  484. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  485. package/templates/marketplace/client/AuthNav.tsx +24 -16
  486. package/templates/marketplace/client/ListingCard.tsx +118 -0
  487. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  488. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  489. package/templates/marketplace/client/LoginCard.tsx +114 -136
  490. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  491. package/templates/marketplace/client/MyMarket.tsx +371 -185
  492. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  493. package/templates/marketplace/client/SellForm.tsx +271 -192
  494. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  495. package/templates/marketplace/client/WatchButton.tsx +16 -16
  496. package/templates/marketplace/client/market.ts +61 -14
  497. package/templates/marketplace/functions/seedMarket.ts +143 -36
  498. package/templates/marketplace/functions/seedOffers.ts +101 -0
  499. package/templates/marketplace/lib/catalog.ts +72 -1
  500. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  501. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  502. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  503. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  504. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  505. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  506. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  507. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  508. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  509. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  510. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  511. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  512. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  513. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  514. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  515. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  516. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  517. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  518. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  519. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  520. package/templates/marketplace/tests/example.test.ts +64 -2
  521. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  522. package/templates/projects/AGENTS.md +1 -1
  523. package/templates/projects/README.md +24 -3
  524. package/templates/projects/app/apple-icon.png +0 -0
  525. package/templates/projects/app/clients/clients-view.tsx +20 -6
  526. package/templates/projects/app/clients/page.tsx +2 -1
  527. package/templates/projects/app/globals.css +29 -14
  528. package/templates/projects/app/icon.svg +1 -0
  529. package/templates/projects/app/layout.tsx +7 -5
  530. package/templates/projects/app/page.tsx +3 -5
  531. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  532. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  533. package/templates/projects/app/projects-view.tsx +105 -52
  534. package/templates/projects/app/workspace.tsx +41 -20
  535. package/templates/projects/app.ts +2 -2
  536. package/templates/projects/components/app-shell.tsx +94 -0
  537. package/templates/projects/components/auth-form.tsx +35 -5
  538. package/templates/projects/components/avatar.tsx +5 -1
  539. package/templates/projects/components/board-skeleton.tsx +3 -3
  540. package/templates/projects/components/brand-mark.tsx +25 -0
  541. package/templates/projects/components/command-palette.tsx +17 -17
  542. package/templates/projects/components/data-table.tsx +97 -36
  543. package/templates/projects/components/filter-tabs.tsx +101 -0
  544. package/templates/projects/components/meter.tsx +48 -0
  545. package/templates/projects/components/nav.tsx +14 -0
  546. package/templates/projects/components/page-header.tsx +43 -7
  547. package/templates/projects/components/require-auth.tsx +24 -26
  548. package/templates/projects/components/sidebar.tsx +54 -48
  549. package/templates/projects/components/summary-bar.tsx +49 -0
  550. package/templates/projects/components/task-board.tsx +10 -9
  551. package/templates/projects/components/time-dialog.tsx +29 -5
  552. package/templates/projects/components/ui/button.tsx +5 -4
  553. package/templates/projects/components/ui/dialog.tsx +1 -1
  554. package/templates/projects/components/ui/input.tsx +1 -1
  555. package/templates/projects/components/ui/select.tsx +1 -1
  556. package/templates/projects/components/ui/textarea.tsx +1 -1
  557. package/templates/projects/functions/seedWorkspace.ts +26 -12
  558. package/templates/projects/lib/brand.ts +9 -0
  559. package/templates/projects/lib/demo-team.ts +51 -0
  560. package/templates/projects/lib/search.ts +4 -3
  561. package/templates/projects/lib/seed.ts +35 -1
  562. package/templates/projects/lib/work.ts +63 -0
  563. package/templates/projects/public/favicon.ico +0 -0
  564. package/templates/projects/tests/demo-team.test.ts +18 -0
  565. package/templates/projects/tests/work.test.ts +65 -0
  566. package/templates/restaurant/.env.example +7 -0
  567. package/templates/restaurant/AGENTS.md +1 -1
  568. package/templates/restaurant/README.md +29 -3
  569. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  570. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  571. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  572. package/templates/restaurant/app/auth-form.tsx +8 -8
  573. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  574. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  575. package/templates/restaurant/app/icon.svg +4 -0
  576. package/templates/restaurant/app/login/page.tsx +15 -15
  577. package/templates/restaurant/functions/seedDemo.ts +44 -0
  578. package/templates/restaurant/lib/demo.ts +144 -0
  579. package/templates/restaurant/lib/hours.ts +45 -0
  580. package/templates/restaurant/lib/site.config.ts +3 -3
  581. package/templates/restaurant/public/favicon.ico +0 -0
  582. package/templates/restaurant/tests/reservations.test.ts +67 -0
  583. package/templates/saas/AGENTS.md +1 -1
  584. package/templates/saas/README.md +54 -35
  585. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  586. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  587. package/templates/saas/app/(auth)/layout.tsx +27 -33
  588. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  589. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  590. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  591. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  592. package/templates/saas/app/(marketing)/page.tsx +133 -372
  593. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  594. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  595. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  596. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  597. package/templates/saas/app/dashboard/layout.tsx +27 -13
  598. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  599. package/templates/saas/app/dashboard/page.tsx +29 -35
  600. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  601. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  602. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  603. package/templates/saas/app/globals.css +5 -4
  604. package/templates/saas/app/icon.svg +1 -0
  605. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  606. package/templates/saas/app/onboarding/page.tsx +6 -2
  607. package/templates/saas/app.ts +68 -13
  608. package/templates/saas/components/dashboard-shell.tsx +212 -132
  609. package/templates/saas/components/marketing.tsx +137 -274
  610. package/templates/saas/components/pricing-table.tsx +12 -11
  611. package/templates/saas/components/setup-checklist.tsx +31 -29
  612. package/templates/saas/components/task-ui.tsx +199 -0
  613. package/templates/saas/functions/createProject.ts +7 -13
  614. package/templates/saas/functions/createTask.ts +67 -0
  615. package/templates/saas/functions/deleteProject.ts +17 -0
  616. package/templates/saas/functions/deleteTask.ts +14 -0
  617. package/templates/saas/functions/seedWorkspace.ts +46 -0
  618. package/templates/saas/functions/setProjectStatus.ts +41 -0
  619. package/templates/saas/functions/updateProfile.ts +13 -0
  620. package/templates/saas/functions/updateProject.ts +4 -5
  621. package/templates/saas/functions/updateTask.ts +88 -0
  622. package/templates/saas/lib/members.ts +53 -0
  623. package/templates/saas/lib/names.ts +53 -0
  624. package/templates/saas/lib/plans.ts +11 -0
  625. package/templates/saas/lib/sample-workspace.ts +117 -0
  626. package/templates/saas/lib/site.config.ts +200 -463
  627. package/templates/saas/lib/tasks.ts +101 -0
  628. package/templates/saas/lib/use-live.ts +25 -0
  629. package/templates/saas/public/favicon.ico +0 -0
  630. package/templates/saas/public/screenshots/board.png +0 -0
  631. package/templates/saas/public/screenshots/members.png +0 -0
  632. package/templates/saas/public/screenshots/overview.png +0 -0
  633. package/templates/saas/public/screenshots/projects.png +0 -0
  634. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  635. package/templates/saas/tests/names.test.ts +35 -0
  636. package/templates/saas/tests/plans.test.ts +12 -0
  637. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  638. package/templates/saas/tests/tasks.test.ts +81 -0
  639. package/templates/shop/AGENTS.md +1 -1
  640. package/templates/shop/README.md +25 -13
  641. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  642. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  643. package/templates/shop/app/(marketing)/page.tsx +120 -59
  644. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  645. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  646. package/templates/shop/app/globals.css +94 -1
  647. package/templates/shop/app/layout.tsx +4 -4
  648. package/templates/shop/app/login/page.tsx +4 -4
  649. package/templates/shop/app.ts +18 -6
  650. package/templates/shop/components/cart-button.tsx +33 -0
  651. package/templates/shop/components/marketing.tsx +17 -87
  652. package/templates/shop/components/shelf-count.tsx +36 -0
  653. package/templates/shop/functions/checkout.ts +17 -1
  654. package/templates/shop/lib/cart-store.ts +57 -0
  655. package/templates/shop/lib/shelf-store.ts +35 -0
  656. package/templates/shop/lib/shop.ts +27 -0
  657. package/templates/shop/lib/site.config.ts +108 -98
  658. package/templates/shop/public/images/hero.jpg +0 -0
  659. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  660. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  661. package/templates/shop/public/images/products/linen.jpg +0 -0
  662. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  663. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  664. package/templates/shop/tests/shop.test.ts +64 -0
  665. package/templates/todo/AGENTS.md +1 -1
  666. package/templates/waitlist/.env.example +7 -0
  667. package/templates/waitlist/AGENTS.md +1 -1
  668. package/templates/waitlist/README.md +29 -13
  669. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  670. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  671. package/templates/waitlist/app/auth-form.tsx +8 -8
  672. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  673. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  674. package/templates/waitlist/app/icon.svg +2 -0
  675. package/templates/waitlist/app/login/page.tsx +14 -14
  676. package/templates/waitlist/app.ts +6 -6
  677. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  678. package/templates/waitlist/functions/seedDemo.ts +32 -0
  679. package/templates/waitlist/lib/demo.ts +94 -0
  680. package/templates/waitlist/lib/launch.ts +19 -0
  681. package/templates/waitlist/lib/site.config.ts +14 -9
  682. package/templates/waitlist/lib/stats.ts +35 -0
  683. package/templates/waitlist/public/favicon.ico +0 -0
  684. package/templates/waitlist/tests/counter.test.ts +107 -0
  685. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  686. package/templates/ai-chat/tests/example.test.ts +0 -12
  687. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  688. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  689. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  690. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  691. package/templates/chat/functions/_keep.ts +0 -13
  692. package/templates/chat/tests/example.test.ts +0 -12
  693. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  694. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  695. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  696. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -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
  }
@@ -51,7 +51,7 @@ export function AuthForm() {
51
51
  <div className="space-y-5">
52
52
  <form onSubmit={onSubmit} className="space-y-4">
53
53
  <label className="block">
54
- <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Email</span>
54
+ <span className="mb-1.5 block text-[13px] font-medium text-chalk-2">Email</span>
55
55
  <input
56
56
  type="email"
57
57
  value={email}
@@ -59,11 +59,11 @@ export function AuthForm() {
59
59
  required
60
60
  autoComplete="email"
61
61
  placeholder="you@yourbusiness.com"
62
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
62
+ className="h-11 w-full rounded-md border border-line bg-ink px-3.5 text-[15px] text-chalk outline-none transition placeholder:text-chalk-2 focus:border-brand"
63
63
  />
64
64
  </label>
65
65
  <label className="block">
66
- <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Password</span>
66
+ <span className="mb-1.5 block text-[13px] font-medium text-chalk-2">Password</span>
67
67
  <input
68
68
  type="password"
69
69
  value={password}
@@ -71,24 +71,24 @@ export function AuthForm() {
71
71
  required
72
72
  autoComplete={mode === "login" ? "current-password" : "new-password"}
73
73
  placeholder={mode === "login" ? "Your password" : "At least 10 characters"}
74
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
74
+ className="h-11 w-full rounded-md border border-line bg-ink px-3.5 text-[15px] text-chalk outline-none transition placeholder:text-chalk-2 focus:border-brand"
75
75
  />
76
76
  </label>
77
77
  {error ? (
78
- <p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-[13px] leading-snug text-red-700">
78
+ <p className="rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-[13px] leading-snug text-red-300">
79
79
  {error}
80
80
  </p>
81
81
  ) : null}
82
82
  <button
83
83
  type="submit"
84
84
  disabled={pending}
85
- className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-zinc-900 text-sm font-medium text-white transition-colors hover:bg-zinc-700 disabled:opacity-60"
85
+ className="inline-flex h-11 w-full items-center justify-center rounded-md bg-brand text-[15px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60"
86
86
  >
87
87
  {pending ? "…" : mode === "login" ? "Sign in" : "Create account"}
88
88
  </button>
89
89
  </form>
90
90
 
91
- <p className="text-center text-[13px] text-zinc-500">
91
+ <p className="text-center text-[13px] text-chalk-2">
92
92
  {mode === "login" ? "First time here?" : "Already have an account?"}{" "}
93
93
  <button
94
94
  type="button"
@@ -96,7 +96,7 @@ export function AuthForm() {
96
96
  setMode(mode === "login" ? "signup" : "login");
97
97
  setError(null);
98
98
  }}
99
- className="font-medium text-zinc-900 underline underline-offset-2"
99
+ className="font-medium text-chalk underline underline-offset-2"
100
100
  >
101
101
  {mode === "login" ? "Create the owner account" : "Sign in"}
102
102
  </button>
@@ -3,14 +3,14 @@
3
3
  import React, { useEffect, useMemo, useState } from "react";
4
4
  import { db, callFn } from "@pylonsync/react";
5
5
  import { useAuth } from "@pylonsync/client";
6
+ import { Download, LogOut, Search } from "lucide-react";
6
7
  import type { WaitlistStatsData, WaitlistStatsResult, SignupRow } from "@/lib/stats";
7
8
 
8
- // The owner's live dashboard. Liveness rides the SAME public aggregate the
9
+ // The owner's live dashboard. Liveness rides the same public aggregate the
9
10
  // landing page uses: `db.useQuery("WaitlistStat")` re-renders the instant the
10
- // count changes (cross-tab, via the replica). The emails themselves never sync
11
- // — they come from the owner-gated `waitlistStats` function, (re)fetched on
12
- // mount and whenever the live count ticks. So the total, chart, and list all
13
- // stay live, but PII only ever travels through the gated call.
11
+ // count changes (cross-tab, via the replica). The emails themselves never sync.
12
+ // They come from the owner-gated `waitlistStats` function, fetched on mount and
13
+ // again whenever the live count changes, so PII only travels through that call.
14
14
  export function WaitlistDashboard({ userEmail }: { userEmail: string }) {
15
15
  const { data: statRows } = db.useQuery<{ id: string; count: number }>("WaitlistStat");
16
16
  const liveCount = statRows.length > 0 ? statRows[0].count : 0;
@@ -19,6 +19,12 @@ export function WaitlistDashboard({ userEmail }: { userEmail: string }) {
19
19
  const [denied, setDenied] = useState(false);
20
20
  const [error, setError] = useState<string | null>(null);
21
21
 
22
+ // In `pylon dev` an empty waitlist gets demo signups (functions/seedDemo.ts).
23
+ // On a deploy the call returns without writing.
24
+ useEffect(() => {
25
+ void callFn("seedDemo", {}).catch(() => {});
26
+ }, []);
27
+
22
28
  useEffect(() => {
23
29
  let cancelled = false;
24
30
  callFn<WaitlistStatsResult>("waitlistStats", {})
@@ -46,7 +52,7 @@ export function WaitlistDashboard({ userEmail }: { userEmail: string }) {
46
52
  if (denied) return <OwnerOnly email={userEmail} />;
47
53
  if (error) {
48
54
  return (
49
- <div className="rounded-xl border border-red-200 bg-red-50 px-5 py-4 text-sm text-red-700">
55
+ <div className="rounded-lg border border-red-500/30 bg-red-500/10 px-5 py-4 text-sm text-red-300">
50
56
  {error}
51
57
  </div>
52
58
  );
@@ -54,15 +60,26 @@ export function WaitlistDashboard({ userEmail }: { userEmail: string }) {
54
60
  if (!data) return <Skeleton />;
55
61
 
56
62
  return (
57
- <div className="space-y-8">
58
- <div>
59
- <h1 className="text-xl font-semibold tracking-tight">Waitlist</h1>
60
- <p className="mt-1 text-sm text-zinc-500">
61
- Live — new signups appear here the moment they happen.
63
+ <div className="space-y-10">
64
+ <div className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
65
+ <div>
66
+ <h1 className="font-display text-[2rem] font-bold leading-none tracking-[-0.02em] text-chalk">
67
+ Waitlist
68
+ </h1>
69
+ <p className="mt-3 text-[14px] text-chalk-2">
70
+ Signups appear here as people join. The landing page shows the same total.
71
+ </p>
72
+ </div>
73
+ <p className="flex items-center gap-2 font-mono-ui text-[12px] text-chalk-2">
74
+ <span className="relative flex size-2">
75
+ <span className="absolute inline-flex size-2 animate-ping rounded-full bg-brand/60" />
76
+ <span className="relative inline-flex size-2 rounded-full bg-brand" />
77
+ </span>
78
+ live
62
79
  </p>
63
80
  </div>
64
81
 
65
- <div className="grid gap-4 sm:grid-cols-3">
82
+ <div className="grid gap-px overflow-hidden rounded-lg border border-line bg-line grid-cols-3">
66
83
  <Stat label="Total signups" value={data.total} />
67
84
  <Stat label="Last 7 days" value={data.last7} />
68
85
  <Stat label="Today" value={data.today} />
@@ -77,12 +94,10 @@ export function WaitlistDashboard({ userEmail }: { userEmail: string }) {
77
94
 
78
95
  function Stat({ label, value }: { label: string; value: number }) {
79
96
  return (
80
- <div className="rounded-xl border border-zinc-200 bg-white p-4">
81
- <div className="text-[11px] font-medium uppercase tracking-wide text-zinc-400">
82
- {label}
83
- </div>
84
- <div className="mt-1 text-2xl font-semibold tabular-nums text-zinc-900">
85
- {value.toLocaleString()}
97
+ <div className="bg-paper px-3 py-4 sm:px-5 sm:py-5">
98
+ <div className="font-mono-ui text-[12px] text-chalk-2">{label}</div>
99
+ <div className="font-display mt-2 text-[1.75rem] sm:text-[2.25rem] font-bold leading-none tabular-nums tracking-[-0.02em] text-chalk">
100
+ {value.toLocaleString("en-US")}
86
101
  </div>
87
102
  </div>
88
103
  );
@@ -92,121 +107,145 @@ function Stat({ label, value }: { label: string; value: number }) {
92
107
 
93
108
  function TrendChart({ daily }: { daily: { date: string; count: number }[] }) {
94
109
  const max = Math.max(1, ...daily.map((d) => d.count));
110
+ const total = daily.reduce((n, d) => n + d.count, 0);
95
111
  return (
96
- <div className="rounded-xl border border-zinc-200 bg-white p-5">
97
- <div className="flex items-center justify-between">
98
- <h2 className="text-sm font-semibold text-zinc-900">Last 30 days</h2>
99
- <span className="text-[12px] text-zinc-400">
100
- peak {max.toLocaleString()}/day
112
+ <section className="rounded-lg border border-line bg-paper p-5 sm:p-6">
113
+ <div className="flex items-baseline justify-between gap-4">
114
+ <h2 className="font-display text-[17px] font-medium text-chalk">Last 30 days</h2>
115
+ <span className="font-mono-ui text-[12px] text-chalk-2">
116
+ {total.toLocaleString("en-US")} signups · peak {max}/day
101
117
  </span>
102
118
  </div>
103
- {/* Each column is a FULL-HEIGHT flex cell (h-full) that bottom-aligns its
104
- bar — so the bar's percentage height resolves against the 7rem track,
105
- not an auto-height parent (which would collapse it to nothing). */}
106
- <div className="mt-4 flex h-28 items-end gap-1">
107
- {daily.map((d) => (
108
- <div
109
- key={d.date}
110
- className="group relative flex h-full flex-1 flex-col justify-end"
111
- >
119
+ {/* Each column is a full-height flex cell that bottom-aligns its bar, so
120
+ the bar's percentage height resolves against the track. */}
121
+ <div className="mt-6 flex h-36 items-end gap-[3px]">
122
+ {daily.map((d, i) => (
123
+ <div key={d.date} className="group relative flex h-full flex-1 flex-col justify-end">
112
124
  <div
113
- className="w-full rounded-t bg-brand/80 transition-colors group-hover:bg-brand"
125
+ className={
126
+ "w-full rounded-t-[2px] transition-colors " +
127
+ (i === daily.length - 1 ? "bg-brand" : "bg-brand/45 group-hover:bg-brand/80")
128
+ }
114
129
  style={{ height: `${Math.max(2, (d.count / max) * 100)}%` }}
115
130
  />
116
- {/* Tooltip on hover — date + count. */}
117
- <div className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 hidden -translate-x-1/2 whitespace-nowrap rounded bg-zinc-900 px-2 py-1 text-[11px] text-white group-hover:block">
131
+ <div className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1.5 hidden -translate-x-1/2 whitespace-nowrap rounded border border-line bg-ink px-2 py-1 font-mono-ui text-[11px] text-chalk group-hover:block">
118
132
  {fmtDay(d.date)} · {d.count}
119
133
  </div>
120
134
  </div>
121
135
  ))}
122
136
  </div>
123
- <div className="mt-2 flex justify-between text-[11px] text-zinc-400">
137
+ <div className="mt-3 flex justify-between border-t border-line pt-2 font-mono-ui text-[11px] text-chalk-2">
124
138
  <span>{fmtDay(daily[0]?.date)}</span>
125
- <span>{fmtDay(daily[daily.length - 1]?.date)}</span>
139
+ <span>Today</span>
126
140
  </div>
127
- </div>
141
+ </section>
128
142
  );
129
143
  }
130
144
 
131
145
  /* ----------------------------- signup list ---------------------------- */
132
146
 
147
+ const PAGE = 50;
148
+
133
149
  function SignupTable({ signups }: { signups: SignupRow[] }) {
134
150
  const [q, setQ] = useState("");
151
+ const [shown, setShown] = useState(PAGE);
135
152
  const filtered = useMemo(() => {
136
153
  const needle = q.trim().toLowerCase();
137
154
  if (!needle) return signups;
138
155
  return signups.filter((s) => s.email.toLowerCase().includes(needle));
139
156
  }, [q, signups]);
157
+ // Join order: the oldest signup is No. 1.
158
+ const position = useMemo(() => {
159
+ const m = new Map<string, number>();
160
+ signups.forEach((s, i) => m.set(s.id, signups.length - i));
161
+ return m;
162
+ }, [signups]);
140
163
 
141
164
  return (
142
- <div className="rounded-xl border border-zinc-200 bg-white">
143
- <div className="flex flex-col gap-3 border-b border-zinc-100 p-4 sm:flex-row sm:items-center sm:justify-between">
144
- <h2 className="text-sm font-semibold text-zinc-900">
165
+ <section className="overflow-hidden rounded-lg border border-line bg-paper">
166
+ <div className="flex flex-col gap-3 border-b border-line p-4 sm:flex-row sm:items-center sm:justify-between sm:px-5">
167
+ <h2 className="font-display text-[17px] font-medium text-chalk">
145
168
  Signups{" "}
146
- <span className="font-normal text-zinc-400">
147
- ({filtered.length.toLocaleString()})
169
+ <span className="font-mono-ui text-[13px] font-normal text-chalk-2">
170
+ {filtered.length.toLocaleString("en-US")}
148
171
  </span>
149
172
  </h2>
150
173
  <div className="flex items-center gap-2">
151
- <input
152
- value={q}
153
- onChange={(e) => setQ(e.target.value)}
154
- placeholder="Search email…"
155
- aria-label="Search signups"
156
- className="h-9 w-44 rounded-md border border-zinc-300 bg-white px-3 text-sm outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
157
- />
174
+ <label className="relative flex-1 sm:flex-none">
175
+ <Search aria-hidden className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-chalk-2" />
176
+ <input
177
+ value={q}
178
+ onChange={(e) => setQ(e.target.value)}
179
+ placeholder="Search email"
180
+ aria-label="Search signups"
181
+ className="h-9 w-full rounded-md border border-line bg-ink pl-8 pr-3 text-[13px] text-chalk outline-none transition placeholder:text-chalk-2 focus:border-brand sm:w-52"
182
+ />
183
+ </label>
158
184
  <button
159
185
  type="button"
160
186
  onClick={() => exportCsv(signups)}
161
187
  disabled={signups.length === 0}
162
- className="inline-flex h-9 items-center rounded-md border border-zinc-300 px-3 text-[13px] font-medium text-zinc-700 transition-colors hover:bg-zinc-50 disabled:opacity-40"
188
+ className="inline-flex h-9 shrink-0 items-center gap-1.5 rounded-md border border-line px-3 text-[13px] font-medium text-chalk transition-colors hover:border-chalk-2 disabled:opacity-40"
163
189
  >
190
+ <Download aria-hidden className="size-3.5" />
164
191
  Export CSV
165
192
  </button>
166
193
  </div>
167
194
  </div>
168
195
 
169
196
  {filtered.length === 0 ? (
170
- <p className="p-8 text-center text-sm text-zinc-500">
197
+ <p className="px-5 py-12 text-center text-[14px] text-chalk-2">
171
198
  {signups.length === 0
172
- ? "No signups yet. Share your landing page and watch them roll in — live."
199
+ ? "No signups yet. They appear here as soon as someone joins."
173
200
  : "No emails match your search."}
174
201
  </p>
175
202
  ) : (
176
- <ul className="divide-y divide-zinc-100">
177
- {filtered.map((s) => (
178
- <li key={s.id} className="flex items-center justify-between gap-3 px-4 py-2.5">
179
- <span className="truncate text-sm text-zinc-800">{s.email}</span>
180
- <span className="shrink-0 text-[12px] text-zinc-400">
181
- {fmtDateTime(s.createdAt)}
182
- </span>
183
- </li>
184
- ))}
185
- </ul>
203
+ <>
204
+ <ul className="divide-y divide-line">
205
+ {filtered.slice(0, shown).map((s) => (
206
+ <li key={s.id} className="flex items-center gap-4 px-4 py-3 sm:px-5">
207
+ <span className="hidden w-12 shrink-0 sm:block font-mono-ui text-[12px] tabular-nums text-chalk-2">
208
+ {position.get(s.id)}
209
+ </span>
210
+ <span className="min-w-0 flex-1 truncate text-[14px] text-chalk">{s.email}</span>
211
+ <span className="shrink-0 font-mono-ui text-[12px] text-chalk-2">
212
+ {fmtDateTime(s.createdAt)}
213
+ </span>
214
+ </li>
215
+ ))}
216
+ </ul>
217
+ {filtered.length > shown ? (
218
+ <button
219
+ type="button"
220
+ onClick={() => setShown((n) => n + PAGE)}
221
+ className="w-full border-t border-line py-3 text-[13px] text-chalk-2 transition-colors hover:text-chalk"
222
+ >
223
+ Show {Math.min(PAGE, filtered.length - shown)} more
224
+ </button>
225
+ ) : null}
226
+ </>
186
227
  )}
187
- </div>
228
+ </section>
188
229
  );
189
230
  }
190
231
 
191
232
  /* --------------------------- owner-only gate -------------------------- */
192
233
 
193
234
  // Shown to a signed-in user the `waitlistStats` function refused (not the
194
- // configured owner, or no PYLON_OWNER_EMAIL set). Fails closed — no signup data
235
+ // configured owner, or no PYLON_OWNER_EMAIL set). Fails closed: no signup data
195
236
  // is fetched or shown.
196
237
  function OwnerOnly({ email }: { email: string }) {
197
238
  return (
198
- <div className="rounded-xl border border-dashed border-zinc-300 px-6 py-12 text-center">
199
- <h1 className="text-lg font-semibold">This dashboard is owner-only</h1>
200
- <p className="mx-auto mt-2 max-w-md text-sm text-zinc-500">
201
- You&apos;re signed in as{" "}
202
- <span className="font-medium text-zinc-700">{email || "this account"}</span>.
203
- A waitlist is single-tenant — only the owner can see the signups. Set{" "}
204
- <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-[12px]">
239
+ <div className="rounded-lg border border-dashed border-line px-6 py-14 text-center">
240
+ <h1 className="font-display text-[1.5rem] font-bold text-chalk">This dashboard is owner-only</h1>
241
+ <p className="mx-auto mt-3 max-w-md text-[14px] leading-relaxed text-chalk-2">
242
+ You are signed in as <span className="text-chalk">{email || "this account"}</span>. Only
243
+ the owner can see signups. Set{" "}
244
+ <code className="rounded bg-ink-2 px-1.5 py-0.5 font-mono-ui text-[12px] text-chalk">
205
245
  PYLON_OWNER_EMAIL={email || "you@yourbusiness.com"}
206
246
  </code>{" "}
207
- in your{" "}
208
- <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-[12px]">.env</code>,
209
- restart, and reload — or sign in with the owner account.
247
+ in <code className="rounded bg-ink-2 px-1.5 py-0.5 font-mono-ui text-[12px] text-chalk">.env</code>,
248
+ restart, and reload. Or sign in with the owner account.
210
249
  </p>
211
250
  </div>
212
251
  );
@@ -223,20 +262,22 @@ export function UserMenu({ email }: { email: string }) {
223
262
  }
224
263
  return (
225
264
  <details className="group relative">
226
- <summary className="flex size-8 cursor-pointer select-none list-none items-center justify-center rounded-full bg-zinc-900 text-[12px] font-semibold text-white marker:hidden [&::-webkit-details-marker]:hidden">
265
+ <summary
266
+ aria-label="Account"
267
+ className="flex size-8 cursor-pointer select-none list-none items-center justify-center rounded-full bg-brand text-[12px] font-semibold text-white marker:hidden [&::-webkit-details-marker]:hidden"
268
+ >
227
269
  {initial}
228
270
  </summary>
229
- <div className="absolute right-0 top-full z-40 mt-2 w-56 overflow-hidden rounded-xl border border-zinc-200 bg-white py-1 shadow-[0_16px_48px_-16px_rgba(0,0,0,0.25)]">
230
- <div className="border-b border-zinc-100 px-3 py-2">
231
- <div className="truncate text-[13px] font-medium text-zinc-900">
232
- {email || "Signed in"}
233
- </div>
271
+ <div className="absolute right-0 top-full z-40 mt-2 w-60 overflow-hidden rounded-lg border border-line bg-paper py-1 shadow-[0_24px_48px_-24px_rgba(0,0,0,0.8)]">
272
+ <div className="truncate border-b border-line px-3 py-2.5 text-[13px] text-chalk">
273
+ {email || "Signed in"}
234
274
  </div>
235
275
  <button
236
276
  type="button"
237
277
  onClick={onSignOut}
238
- className="flex w-full items-center px-3 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
278
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-[13px] text-chalk-2 transition-colors hover:bg-ink-2 hover:text-chalk"
239
279
  >
280
+ <LogOut aria-hidden className="size-3.5" />
240
281
  Sign out
241
282
  </button>
242
283
  </div>
@@ -248,26 +289,22 @@ export function UserMenu({ email }: { email: string }) {
248
289
 
249
290
  function Skeleton() {
250
291
  return (
251
- <div className="space-y-8">
252
- <div className="h-6 w-32 animate-pulse rounded bg-zinc-100" />
292
+ <div className="space-y-10">
293
+ <div className="h-8 w-40 animate-pulse rounded bg-ink-2" />
253
294
  <div className="grid gap-4 sm:grid-cols-3">
254
295
  {[0, 1, 2].map((i) => (
255
- <div key={i} className="h-20 animate-pulse rounded-xl bg-zinc-100" />
296
+ <div key={i} className="h-24 animate-pulse rounded-lg bg-ink-2" />
256
297
  ))}
257
298
  </div>
258
- <div className="h-44 animate-pulse rounded-xl bg-zinc-100" />
259
- <div className="h-64 animate-pulse rounded-xl bg-zinc-100" />
299
+ <div className="h-56 animate-pulse rounded-lg bg-ink-2" />
300
+ <div className="h-72 animate-pulse rounded-lg bg-ink-2" />
260
301
  </div>
261
302
  );
262
303
  }
263
304
 
264
305
  function exportCsv(signups: SignupRow[]) {
265
- const cell = (v: string) =>
266
- /[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v;
267
- const rows = [
268
- "email,joined_at",
269
- ...signups.map((s) => `${cell(s.email)},${cell(s.createdAt)}`),
270
- ];
306
+ const cell = (v: string) => (/[",\n]/.test(v) ? `"${v.replace(/"/g, '""')}"` : v);
307
+ const rows = ["email,joined_at", ...signups.map((s) => `${cell(s.email)},${cell(s.createdAt)}`)];
271
308
  const blob = new Blob([rows.join("\n")], { type: "text/csv;charset=utf-8" });
272
309
  const url = URL.createObjectURL(blob);
273
310
  const a = document.createElement("a");
@@ -282,13 +319,13 @@ function exportCsv(signups: SignupRow[]) {
282
319
  function fmtDay(iso?: string) {
283
320
  if (!iso) return "";
284
321
  const d = new Date(iso + "T00:00:00Z");
285
- return d.toLocaleDateString(undefined, { month: "short", day: "numeric" });
322
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" });
286
323
  }
287
324
 
288
325
  function fmtDateTime(iso: string) {
289
326
  const t = Date.parse(iso);
290
327
  if (Number.isNaN(t)) return "";
291
- return new Date(t).toLocaleString(undefined, {
328
+ return new Date(t).toLocaleString("en-US", {
292
329
  month: "short",
293
330
  day: "numeric",
294
331
  hour: "numeric",
@@ -1,5 +1,6 @@
1
1
  import React, { use } from "react";
2
2
  import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { ArrowUpRight } from "lucide-react";
3
4
  import { siteConfig } from "@/lib/site.config";
4
5
  import { UserMenu, WaitlistDashboard } from "./dashboard-client";
5
6
 
@@ -35,35 +36,35 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
35
36
  );
36
37
  }
37
38
 
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).
39
+ // Dashboard chrome in the site's own look: the same near-black page, the brand
40
+ // name in the display face, a link back to the public site, and the account
41
+ // menu (a client island for sign-out).
40
42
  function Shell({ email, children }: { email: string; children: React.ReactNode }) {
41
43
  const { brand } = siteConfig;
42
44
  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">/ waitlist</span>
52
- </span>
45
+ <div className="flex min-h-screen flex-col bg-ink text-chalk">
46
+ <header className="border-b border-line">
47
+ <div className="mx-auto flex h-16 w-full max-w-5xl items-center justify-between px-6">
48
+ <div className="flex items-baseline gap-2.5">
49
+ <Link href="/" className="font-display text-[17px] font-bold tracking-tight text-chalk">
50
+ {brand.name}
51
+ </Link>
52
+ <span className="font-mono-ui text-[12px] text-chalk-2">owner</span>
53
53
  </div>
54
- <div className="flex items-center gap-4">
54
+ <div className="flex items-center gap-5">
55
55
  <Link
56
56
  href="/"
57
- className="text-[13px] text-zinc-500 transition-colors hover:text-zinc-900"
57
+ className="inline-flex items-center gap-1 text-[13px] text-chalk-2 transition-colors hover:text-chalk"
58
58
  >
59
- View site ↗
59
+ View site
60
+ <ArrowUpRight aria-hidden className="size-3.5" />
60
61
  </Link>
61
62
  <UserMenu email={email} />
62
63
  </div>
63
64
  </div>
64
65
  </header>
65
66
  <main className="flex-1">
66
- <div className="mx-auto max-w-4xl px-6 py-8">{children}</div>
67
+ <div className="mx-auto w-full max-w-5xl px-6 py-10 sm:py-12">{children}</div>
67
68
  </main>
68
69
  </div>
69
70
  );