@pylonsync/create-pylon 0.19.1 → 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
@@ -0,0 +1,94 @@
1
+ // Demo subscribers for local development.
2
+ //
3
+ // `pylon dev` fills an empty list with about 180 fictional subscribers spread
4
+ // over the last 30 days, so the counter, chart, and list have data. A deploy
5
+ // does not seed: `demoDataEnabled` is true only when PYLON_DEMO_DATA is on, or
6
+ // when it is unset and the process runs under `pylon dev`. Set
7
+ // PYLON_DEMO_DATA=0 to turn it off in development too.
8
+ //
9
+ // Pure data + pure shaping, so functions/seedDemo.ts stays a thin wrapper and
10
+ // this file is testable without a server.
11
+
12
+ const ON = new Set(["1", "true", "yes", "on"]);
13
+ const OFF = new Set(["0", "false", "no", "off"]);
14
+
15
+ /**
16
+ * True when demo rows may be written. An explicit PYLON_DEMO_DATA wins.
17
+ * Otherwise it needs both PYLON_DEV_MODE on and PYLON_DEV_WATCH_DIR set. Only
18
+ * `pylon dev` sets PYLON_DEV_WATCH_DIR; `pylon start` and the Docker image do
19
+ * not, and the image turns PYLON_DEV_MODE on by default, so dev mode alone is
20
+ * not proof of a local machine.
21
+ */
22
+ export function demoDataEnabled(env: Record<string, string | undefined>): boolean {
23
+ const explicit = env.PYLON_DEMO_DATA?.trim().toLowerCase();
24
+ if (explicit && ON.has(explicit)) return true;
25
+ if (explicit && OFF.has(explicit)) return false;
26
+ const dev = env.PYLON_DEV_MODE?.trim().toLowerCase();
27
+ const devMode = dev === "1" || dev === "true";
28
+ return devMode && Boolean(env.PYLON_DEV_WATCH_DIR?.trim());
29
+ }
30
+
31
+ const FIRST = [
32
+ "priya", "marcus", "lena", "tomas", "aisha", "ben", "hana", "diego", "sofia", "kwame",
33
+ "ingrid", "raj", "noor", "felix", "maya", "owen", "yuki", "carmen", "theo", "amara",
34
+ "jonas", "leila", "sam", "ines", "dev", "clara", "malik", "rosa", "ethan", "zara",
35
+ ];
36
+ const LAST = [
37
+ "nair", "bell", "fischer", "alvarez", "khan", "osei", "sato", "ruiz", "marin", "mensah",
38
+ "berg", "patel", "haddad", "wagner", "chen", "reilly", "tanaka", "vega", "park", "okafor",
39
+ ];
40
+ // Fictional company domains on the reserved .example TLD, so no demo address
41
+ // can reach a real inbox.
42
+ const DOMAINS = [
43
+ "fieldnote.example", "northwind.example", "quarry.example", "bluefin.example", "wren.example",
44
+ "copperline.example", "tidewater.example", "larkspur.example", "kestrel.example", "orchardlabs.example",
45
+ "sable.example", "ironwood.example", "halcyon.example", "pinecrest.example", "vantage.example",
46
+ ];
47
+
48
+ /** Small deterministic PRNG (mulberry32) so the demo set is stable per seed. */
49
+ function rng(seed: number): () => number {
50
+ let a = seed >>> 0;
51
+ return () => {
52
+ a = (a + 0x6d2b79f5) >>> 0;
53
+ let t = a;
54
+ t = Math.imul(t ^ (t >>> 15), t | 1);
55
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
56
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
57
+ };
58
+ }
59
+
60
+ export interface DemoSubscriber {
61
+ email: string;
62
+ createdAt: string;
63
+ }
64
+
65
+ /**
66
+ * About 180 subscribers over the 30 days before `nowMs`, rising toward today.
67
+ * Emails are unique. Every timestamp is in the past.
68
+ */
69
+ export function demoSubscribers(nowMs: number, seed = 23): DemoSubscriber[] {
70
+ const rand = rng(seed);
71
+ const out: DemoSubscriber[] = [];
72
+ const seen = new Set<string>();
73
+ const DAY = 86_400_000;
74
+ for (let daysAgo = 29; daysAgo >= 0; daysAgo--) {
75
+ // Growth curve: ~2/day a month ago, ~10/day this week, plus noise.
76
+ const base = 2 + Math.round((29 - daysAgo) * 0.28);
77
+ const count = Math.max(0, base + Math.round((rand() - 0.5) * 4));
78
+ for (let i = 0; i < count; i++) {
79
+ const first = FIRST[Math.floor(rand() * FIRST.length)];
80
+ const last = LAST[Math.floor(rand() * LAST.length)];
81
+ const domain = DOMAINS[Math.floor(rand() * DOMAINS.length)];
82
+ const shape = rand();
83
+ const local = shape < 0.5 ? `${first}.${last}` : shape < 0.8 ? `${first}` : `${first[0]}${last}`;
84
+ const email = `${local}@${domain}`;
85
+ if (seen.has(email)) continue;
86
+ seen.add(email);
87
+ // A random minute inside that day, never later than `nowMs`.
88
+ const offset = Math.floor(rand() * DAY);
89
+ const ts = Math.min(nowMs - 60_000, nowMs - daysAgo * DAY - offset);
90
+ out.push({ email, createdAt: new Date(ts).toISOString() });
91
+ }
92
+ }
93
+ return out.sort((a, b) => (a.createdAt < b.createdAt ? -1 : 1));
94
+ }
@@ -48,7 +48,11 @@ export type CreatorConfig = BaseConfig & {
48
48
  ctaLabel: string;
49
49
  successMessage: string;
50
50
  counterLabel: string; // e.g. "readers subscribed"
51
- seedCount?: number; // vanity baseline added to the real live count
51
+ counterLabelOne: string; // the same label for a count of 1
52
+ // Subscribers you are moving over from another tool, added to the
53
+ // Subscriber table's count. Keep 0 unless those people are real; the page
54
+ // shows the sum as one number.
55
+ importedCount: number;
52
56
  };
53
57
  links?: { headline: string; body: string; items: LinkItem[] };
54
58
  };
@@ -62,7 +66,7 @@ export const siteConfig: CreatorConfig = {
62
66
  domain: "mayarivera.co",
63
67
  email: "hello@mayarivera.example",
64
68
  footerBlurb:
65
- "Product design coach and writer. I help designers do braver work — through 1:1 coaching, portfolio reviews, and a weekly newsletter.",
69
+ "Product design coach and writer. 1:1 coaching, portfolio reviews, and a weekly newsletter.",
66
70
  copyrightName: "Maya Rivera",
67
71
  socials: [
68
72
  {
@@ -83,14 +87,14 @@ export const siteConfig: CreatorConfig = {
83
87
  seo: {
84
88
  title: "Maya Rivera — product design coach & writer",
85
89
  description:
86
- "Product design coaching, portfolio reviews, and a weekly newsletter for designers who want to do braver work.",
90
+ "Product design coaching, portfolio reviews, and a weekly newsletter for product designers.",
87
91
  },
88
92
 
89
93
  hero: {
90
94
  name: "Maya Rivera",
91
95
  tagline: "Product design coach & writer.",
92
96
  intro:
93
- "I help product designers get unstuck, sharpen their portfolios, and do the bravest work of their careers. I spent fifteen years leading design teams, and now I spend that time with you.",
97
+ "I coach product designers on portfolios, critique, and the move to senior roles. I led design teams for fifteen years. Now I work with designers one at a time.",
94
98
  },
95
99
 
96
100
  about: {
@@ -106,7 +110,7 @@ export const siteConfig: CreatorConfig = {
106
110
  items: [
107
111
  {
108
112
  title: "1:1 coaching",
109
- body: "Monthly sessions on whatever's in your way — craft, career, confidence. We build a plan and I hold you to it.",
113
+ body: "Two sessions a month on your work and your next role. We agree on a plan and review it each session.",
110
114
  price: "from $400/mo",
111
115
  },
112
116
  {
@@ -151,7 +155,7 @@ export const siteConfig: CreatorConfig = {
151
155
  items: [
152
156
  {
153
157
  quote:
154
- "Maya helped me land a senior role in three months. The portfolio review alone was worth ten times the price.",
158
+ "Maya helped me land a senior role in three months. The portfolio review changed which projects I led with.",
155
159
  name: "Daniel Reyes",
156
160
  role: "Senior Product Designer",
157
161
  },
@@ -163,7 +167,7 @@ export const siteConfig: CreatorConfig = {
163
167
  },
164
168
  {
165
169
  quote:
166
- "I read the newsletter every week. It feels like a coaching session in my inbox.",
170
+ "I read the newsletter every week. Each issue has one thing I can use that week.",
167
171
  name: "Marcus Bell",
168
172
  role: "Product Designer",
169
173
  },
@@ -172,12 +176,13 @@ export const siteConfig: CreatorConfig = {
172
176
 
173
177
  newsletter: {
174
178
  name: "The Studio Notes",
175
- subcopy: "One short email every Sunday on design, taste, and doing brave work.",
179
+ subcopy: "One short email every Sunday on critique, portfolios, and design careers.",
176
180
  emailPlaceholder: "you@email.com",
177
181
  ctaLabel: "Subscribe",
178
182
  successMessage: "You are in. The next issue arrives on Sunday.",
179
- counterLabel: "designers reading",
180
- seedCount: 2400,
183
+ counterLabel: "designers subscribed",
184
+ counterLabelOne: "designer subscribed",
185
+ importedCount: 0,
181
186
  },
182
187
 
183
188
  links: {
@@ -28,3 +28,38 @@ export interface SubscriberStatsData {
28
28
  export type SubscriberStatsResult =
29
29
  | ({ authorized: true } & SubscriberStatsData)
30
30
  | { authorized: false };
31
+
32
+ /* ------------------------- public live counter ------------------------- */
33
+
34
+ // The public counter shows the Subscriber table's row count, read from the
35
+ // PII-free SubscriberCount row. `importedCount` is
36
+ // `siteConfig.newsletter.importedCount`: subscribers you are moving over from
37
+ // another tool. It is 0 by default, so the page shows only rows in the table.
38
+ export function publicCount(
39
+ countRows: ReadonlyArray<{ count: number }>,
40
+ importedCount = 0,
41
+ ): number {
42
+ const real = countRows.length > 0 ? Math.max(0, Math.floor(countRows[0].count)) : 0;
43
+ const imported = Number.isFinite(importedCount) ? Math.max(0, Math.floor(importedCount)) : 0;
44
+ return real + imported;
45
+ }
46
+
47
+ // The subset of `ctx.db.unsafe` the recount needs. Typed structurally so the
48
+ // helper runs against the real handle and a test double.
49
+ export interface CountDb {
50
+ list(entity: string): Promise<unknown[]>;
51
+ insert(entity: string, data: Record<string, unknown>): Promise<unknown>;
52
+ update(entity: string, id: string, data: Record<string, unknown>): Promise<unknown>;
53
+ }
54
+
55
+ // Write the Subscriber row count into the single SubscriberCount row, creating
56
+ // it if missing. Every writer of Subscriber calls this in the same transaction,
57
+ // so the public number is always the table's count.
58
+ export async function syncSubscriberCount(db: CountDb): Promise<number> {
59
+ const total = (await db.list("Subscriber")).length;
60
+ const row = (await db.list("SubscriberCount"))[0] as { id: string } | undefined;
61
+ const now = new Date().toISOString();
62
+ if (row) await db.update("SubscriberCount", row.id, { count: total, updatedAt: now });
63
+ else await db.insert("SubscriberCount", { count: total, updatedAt: now });
64
+ return total;
65
+ }
@@ -0,0 +1,92 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { publicCount, syncSubscriberCount, type CountDb } from "../lib/stats";
3
+ import { demoDataEnabled, demoSubscribers } from "../lib/demo";
4
+ import { siteConfig } from "../lib/site.config";
5
+
6
+ describe("publicCount", () => {
7
+ test("is the table's count when nothing is imported", () => {
8
+ expect(publicCount([{ count: 3 }])).toBe(3);
9
+ expect(publicCount([])).toBe(0);
10
+ });
11
+ test("adds only an explicit, non-negative imported count", () => {
12
+ expect(publicCount([{ count: 3 }], 40)).toBe(43);
13
+ expect(publicCount([{ count: 3 }], -5)).toBe(3);
14
+ expect(publicCount([{ count: 3 }], Number.NaN)).toBe(3);
15
+ });
16
+ test("the shipped config adds nothing to the real count", () => {
17
+ expect(siteConfig.newsletter.importedCount).toBe(0);
18
+ });
19
+ });
20
+
21
+ // An in-memory stand-in for ctx.db.unsafe.
22
+ function fakeDb(signups: number): CountDb & { rows: Record<string, Record<string, unknown>[]> } {
23
+ const rows: Record<string, Record<string, unknown>[]> = {
24
+ Subscriber: Array.from({ length: signups }, (_, i) => ({ id: `s${i}` })),
25
+ SubscriberCount: [],
26
+ };
27
+ return {
28
+ rows,
29
+ async list(entity) {
30
+ return rows[entity] ?? [];
31
+ },
32
+ async insert(entity, data) {
33
+ const id = `${entity}-${rows[entity].length}`;
34
+ rows[entity].push({ id, ...data });
35
+ return id;
36
+ },
37
+ async update(entity, id, data) {
38
+ const row = rows[entity].find((r) => r.id === id);
39
+ if (row) Object.assign(row, data);
40
+ return id;
41
+ },
42
+ };
43
+ }
44
+
45
+ describe("syncSubscriberCount", () => {
46
+ test("creates the stat row with the Subscriber count", async () => {
47
+ const db = fakeDb(5);
48
+ expect(await syncSubscriberCount(db)).toBe(5);
49
+ expect(db.rows.SubscriberCount).toHaveLength(1);
50
+ expect(db.rows.SubscriberCount[0].count).toBe(5);
51
+ });
52
+ test("recounts into the existing row instead of adding one", async () => {
53
+ const db = fakeDb(5);
54
+ await syncSubscriberCount(db);
55
+ db.rows.Subscriber.push({ id: "late" });
56
+ await syncSubscriberCount(db);
57
+ expect(db.rows.SubscriberCount).toHaveLength(1);
58
+ expect(db.rows.SubscriberCount[0].count).toBe(6);
59
+ });
60
+ });
61
+
62
+ describe("demoDataEnabled", () => {
63
+ test("needs a `pylon dev` process when PYLON_DEMO_DATA is unset", () => {
64
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
65
+ // The Docker base image turns dev mode on by default; without `pylon dev` it must not seed.
66
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "true" })).toBe(false);
67
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "true", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
68
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "false" })).toBe(false);
69
+ expect(demoDataEnabled({})).toBe(false);
70
+ });
71
+ test("an explicit PYLON_DEMO_DATA wins", () => {
72
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app", PYLON_DEMO_DATA: "0" })).toBe(false);
73
+ expect(demoDataEnabled({ PYLON_DEV_MODE: "false", PYLON_DEMO_DATA: "1" })).toBe(true);
74
+ });
75
+ });
76
+
77
+ describe("demoSubscribers", () => {
78
+ const now = Date.parse("2026-09-26T15:00:00Z");
79
+ const rows = demoSubscribers(now);
80
+ test("unique emails, all in the past 30 days", () => {
81
+ expect(new Set(rows.map((r) => r.email)).size).toBe(rows.length);
82
+ for (const r of rows) {
83
+ const t = Date.parse(r.createdAt);
84
+ expect(t).toBeLessThan(now);
85
+ expect(t).toBeGreaterThan(now - 31 * 86_400_000);
86
+ }
87
+ });
88
+ test("enough rows to fill the chart, stable for a seed", () => {
89
+ expect(rows.length).toBeGreaterThan(120);
90
+ expect(demoSubscribers(now)).toEqual(rows);
91
+ });
92
+ });
@@ -0,0 +1,45 @@
1
+ import { afterEach, expect, mock, test } from "bun:test";
2
+ import React from "react";
3
+ import { cleanup, render, screen } from "@testing-library/react";
4
+
5
+ // The live count reads the SubscriberCount row. Mock the data boundary so the
6
+ // test controls what the "server" says.
7
+ let rows: { id: string; count: number }[] = [];
8
+ let loading = false;
9
+ mock.module("@pylonsync/react", () => ({
10
+ db: { useQuery: () => ({ data: rows, loading }) },
11
+ callFn: async () => ({ seeded: false }),
12
+ }));
13
+ mock.module("@pylonsync/client", () => ({
14
+ EnsureGuest: ({ children }: { children: React.ReactNode }) => <>{children}</>,
15
+ }));
16
+ const { LiveCounter } = await import("../app/(marketing)/newsletter-signup");
17
+
18
+ afterEach(() => {
19
+ cleanup();
20
+ rows = [];
21
+ loading = false;
22
+ });
23
+
24
+ test("shows the table's count, with no vanity baseline", () => {
25
+ rows = [{ id: "c", count: 42 }];
26
+ render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
27
+ expect(screen.getByTestId("live-count").textContent).toBe("42 designers subscribed");
28
+ });
29
+
30
+ test("shows nothing before the first sync or while the list is empty", () => {
31
+ loading = true;
32
+ const { unmount } = render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
33
+ expect(screen.queryByTestId("live-count")).toBeNull();
34
+ unmount();
35
+ loading = false;
36
+ rows = [{ id: "c", count: 0 }];
37
+ render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
38
+ expect(screen.queryByTestId("live-count")).toBeNull();
39
+ });
40
+
41
+ test("uses the singular label for one subscriber", () => {
42
+ rows = [{ id: "c", count: 1 }];
43
+ render(<LiveCounter importedCount={0} label="designers subscribed" labelOne="designer subscribed" />);
44
+ expect(screen.getByTestId("live-count").textContent).toBe("1 designer subscribed");
45
+ });
@@ -14,6 +14,11 @@ Open http://localhost:4321 and create an account. The first sign-in seeds a demo
14
14
  pipeline so the board isn't empty; delete `functions/seedWorkspace.ts` and
15
15
  `lib/seed.ts` once you have real customers.
16
16
 
17
+ The deals are spread across you and four demo teammates. Each is a User
18
+ row with a `@demo.invalid` address and no password, so outside dev mode nobody
19
+ can sign in as one (see `lib/demo-team.ts`). Delete those users along with the
20
+ seed.
21
+
17
22
  ## What's interesting here
18
23
 
19
24
  **The board is live.** Drag a deal to another column and it moves on every
@@ -31,6 +36,22 @@ win rate are computed from the deals themselves in `lib/pipeline.ts` — pure
31
36
  functions with no React and no `db`, so the arithmetic is unit-tested directly
32
37
  (`tests/pipeline.test.ts`).
33
38
 
39
+ ## Brand and layout
40
+
41
+ The product ships as **Dealbook**. The name lives in `lib/brand.ts`, and the
42
+ sidebar, sign-in screen, page titles, and phone drawer all read it. The mark is
43
+ `components/brand-mark.tsx`; `app/icon.svg`, `app/apple-icon.png`, and
44
+ `public/favicon.ico` draw the same shape for browsers and home screens. The
45
+ accent colour is `--brand` at the top of `app/globals.css`, and the typeface
46
+ (Instrument Sans) is set in `fonts` in `app.ts`.
47
+
48
+ The frame is `components/app-shell.tsx`. From 768px up the sidebar is a fixed
49
+ rail; below that it opens as a drawer from the menu button in each page header,
50
+ tables drop their lower-priority columns (`hideBelow` in
51
+ `components/data-table.tsx`), and boards scroll one column at a time. The nav
52
+ links are in `components/nav.tsx`. Every nav count comes from `navCounts` in
53
+ `app/workspace.tsx`, and page headers and tabs read the same numbers.
54
+
34
55
  ## Layout
35
56
 
36
57
  ```
@@ -84,7 +105,7 @@ already themed.
84
105
  - **Add a stage:** `PIPELINE` in `lib/pipeline.ts`. The board, the forecast, and
85
106
  the stage picker all read from it.
86
107
  - **Add a view:** drop `app/reports/page.tsx` and add it to `NAV` in
87
- `components/sidebar.tsx`.
108
+ `components/nav.tsx`.
88
109
 
89
110
  ## Deploy
90
111
 
Binary file
@@ -9,7 +9,7 @@ import { DataTable, type ColumnDef } from "@/components/data-table";
9
9
  import { EmptyState } from "@/components/empty-state";
10
10
  import { RecordDialog } from "@/components/record-dialog";
11
11
  import { Avatar } from "@/components/avatar";
12
- import { money, relativeTime } from "@/lib/pipeline";
12
+ import { isOpen, money, relativeTime } from "@/lib/pipeline";
13
13
  import { RequireAuth } from "@/components/require-auth";
14
14
  import { Workspace, type CompanyRow, type DealRow } from "../workspace";
15
15
 
@@ -17,30 +17,37 @@ export function CompaniesView() {
17
17
  const [open, setOpen] = useState(false);
18
18
 
19
19
  return (
20
- <RequireAuth title="CRM" description="Your team shares one pipeline. Anyone with an account sees it.">
20
+ <RequireAuth>
21
21
  <Workspace pathname="/companies">
22
22
  {(data) => {
23
- // Pipeline per company, computed here rather than stored — one less
24
- // thing to keep in sync when a deal moves.
23
+ // Open pipeline per company, computed from the deals rather than
24
+ // stored, so a moved deal can't leave it stale.
25
25
  const openValue = (companyId: string) =>
26
26
  data.deals
27
- .filter((deal: DealRow) => deal.companyId === companyId)
27
+ .filter((deal: DealRow) => deal.companyId === companyId && isOpen(deal))
28
28
  .reduce((sum, deal) => sum + (Number(deal.value) || 0), 0);
29
29
 
30
30
  const columns: ColumnDef<CompanyRow>[] = [
31
31
  {
32
32
  key: "name",
33
33
  header: "Company",
34
+ className: "w-[46%] md:w-[26%]",
34
35
  cell: (row) => (
35
- <span className="flex items-center gap-2">
36
- <Avatar name={row.name} size="sm" />
37
- <span className="truncate font-medium">{row.name}</span>
36
+ <span className="flex min-w-0 items-center gap-2.5">
37
+ <Avatar name={row.name} shape="square" />
38
+ <span className="min-w-0">
39
+ <span className="block truncate font-medium">{row.name}</span>
40
+ <span className="block truncate text-[12px] text-muted-foreground md:hidden">
41
+ {row.industry ?? row.domain ?? ""}
42
+ </span>
43
+ </span>
38
44
  </span>
39
45
  ),
40
46
  },
41
47
  {
42
48
  key: "domain",
43
49
  header: "Domain",
50
+ hideBelow: "md",
44
51
  cell: (row) => (
45
52
  <span className="text-muted-foreground">{row.domain ?? "—"}</span>
46
53
  ),
@@ -48,6 +55,7 @@ export function CompaniesView() {
48
55
  {
49
56
  key: "industry",
50
57
  header: "Industry",
58
+ hideBelow: "md",
51
59
  cell: (row) => (
52
60
  <span className="text-muted-foreground">{row.industry ?? "—"}</span>
53
61
  ),
@@ -55,19 +63,27 @@ export function CompaniesView() {
55
63
  {
56
64
  key: "size",
57
65
  header: "Size",
66
+ hideBelow: "lg",
67
+ className: "w-[110px]",
58
68
  cell: (row) => (
59
69
  <span className="text-muted-foreground">{row.size ?? "—"}</span>
60
70
  ),
61
71
  },
62
72
  {
63
73
  key: "pipeline",
64
- header: "Pipeline",
74
+ header: "Open pipeline",
65
75
  numeric: true,
66
- cell: (row) => money(openValue(row.id)),
76
+ className: "w-[130px]",
77
+ cell: (row) => {
78
+ const value = openValue(row.id);
79
+ return value > 0 ? money(value) : <span className="text-muted-foreground">—</span>;
80
+ },
67
81
  },
68
82
  {
69
83
  key: "created",
70
84
  header: "Added",
85
+ hideBelow: "sm",
86
+ className: "w-[110px]",
71
87
  cell: (row) => (
72
88
  <span className="text-muted-foreground">
73
89
  {relativeTime(row.createdAt)}
@@ -80,16 +96,18 @@ export function CompaniesView() {
80
96
 
81
97
  return (
82
98
  <>
83
- <PageHeader title="Companies" count={rows.length}>
99
+ <PageHeader title="Companies" count={data.navCounts["/companies"]}>
84
100
  <Button size="sm" onClick={() => setOpen(true)}>
85
101
  <Plus />
86
- New company
102
+ <span className="max-sm:sr-only">New company</span>
87
103
  </Button>
88
104
  </PageHeader>
89
105
 
90
106
  <DataTable
91
107
  rows={rows}
92
108
  columns={columns}
109
+ loading={data.loading}
110
+ label="Companies"
93
111
  empty={
94
112
  <EmptyState
95
113
  icon={<Building2 />}
@@ -111,8 +129,8 @@ export function CompaniesView() {
111
129
  submitLabel="Create company"
112
130
  onOpenChange={setOpen}
113
131
  fields={[
114
- { name: "name", label: "Name", required: true, placeholder: "Acme Inc" },
115
- { name: "domain", label: "Domain", placeholder: "acme.com" },
132
+ { name: "name", label: "Name", required: true, placeholder: "Northwind Logistics" },
133
+ { name: "domain", label: "Domain", placeholder: "northwind.co" },
116
134
  { name: "industry", label: "Industry", placeholder: "Logistics" },
117
135
  { name: "size", label: "Size", placeholder: "50-200" },
118
136
  ]}
@@ -1,9 +1,10 @@
1
1
  import React from "react";
2
2
  import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { BRAND } from "@/lib/brand";
3
4
  import { CompaniesView } from "./companies-view";
4
5
 
5
6
  export const metadata: Metadata = {
6
- title: "Companies",
7
+ title: `Companies · ${BRAND.name}`,
7
8
  robots: "noindex",
8
9
  };
9
10
 
@@ -17,23 +17,30 @@ export function ContactsView() {
17
17
  const [open, setOpen] = useState(false);
18
18
 
19
19
  return (
20
- <RequireAuth title="CRM" description="Your team shares one pipeline. Anyone with an account sees it.">
20
+ <RequireAuth>
21
21
  <Workspace pathname="/contacts">
22
22
  {(data) => {
23
23
  const columns: ColumnDef<ContactRow>[] = [
24
24
  {
25
25
  key: "name",
26
26
  header: "Name",
27
+ className: "w-[55%] md:w-[22%]",
27
28
  cell: (row) => (
28
- <span className="flex items-center gap-2">
29
- <Avatar name={row.name} size="sm" />
30
- <span className="truncate font-medium">{row.name}</span>
29
+ <span className="flex min-w-0 items-center gap-2.5">
30
+ <Avatar name={row.name} />
31
+ <span className="min-w-0">
32
+ <span className="block truncate font-medium">{row.name}</span>
33
+ <span className="block truncate text-[12px] text-muted-foreground md:hidden">
34
+ {[row.title, data.companyName(row.companyId)].filter(Boolean).join(", ")}
35
+ </span>
36
+ </span>
31
37
  </span>
32
38
  ),
33
39
  },
34
40
  {
35
41
  key: "title",
36
42
  header: "Title",
43
+ hideBelow: "md",
37
44
  cell: (row) => (
38
45
  <span className="text-muted-foreground">{row.title ?? "—"}</span>
39
46
  ),
@@ -41,6 +48,7 @@ export function ContactsView() {
41
48
  {
42
49
  key: "company",
43
50
  header: "Company",
51
+ hideBelow: "md",
44
52
  cell: (row) => (
45
53
  <span className="text-muted-foreground">
46
54
  {data.companyName(row.companyId) ?? "—"}
@@ -50,6 +58,8 @@ export function ContactsView() {
50
58
  {
51
59
  key: "email",
52
60
  header: "Email",
61
+ hideBelow: "lg",
62
+ className: "w-[24%]",
53
63
  cell: (row) =>
54
64
  row.email ? (
55
65
  <a
@@ -66,13 +76,25 @@ export function ContactsView() {
66
76
  {
67
77
  key: "phone",
68
78
  header: "Phone",
69
- cell: (row) => (
70
- <span className="text-muted-foreground">{row.phone ?? "—"}</span>
71
- ),
79
+ className: "w-[140px]",
80
+ cell: (row) =>
81
+ row.phone ? (
82
+ <a
83
+ href={`tel:${row.phone.replace(/[^+\d]/g, "")}`}
84
+ onClick={(event) => event.stopPropagation()}
85
+ className="tabular text-muted-foreground hover:text-foreground"
86
+ >
87
+ {row.phone}
88
+ </a>
89
+ ) : (
90
+ <span className="text-muted-foreground">—</span>
91
+ ),
72
92
  },
73
93
  {
74
94
  key: "created",
75
95
  header: "Added",
96
+ hideBelow: "xl",
97
+ className: "w-[100px]",
76
98
  cell: (row) => (
77
99
  <span className="text-muted-foreground">
78
100
  {relativeTime(row.createdAt)}
@@ -85,16 +107,18 @@ export function ContactsView() {
85
107
 
86
108
  return (
87
109
  <>
88
- <PageHeader title="Contacts" count={rows.length}>
110
+ <PageHeader title="Contacts" count={data.navCounts["/contacts"]}>
89
111
  <Button size="sm" onClick={() => setOpen(true)}>
90
112
  <Plus />
91
- New contact
113
+ <span className="max-sm:sr-only">New contact</span>
92
114
  </Button>
93
115
  </PageHeader>
94
116
 
95
117
  <DataTable
96
118
  rows={rows}
97
119
  columns={columns}
120
+ loading={data.loading}
121
+ label="Contacts"
98
122
  empty={
99
123
  <EmptyState
100
124
  icon={<Users />}
@@ -126,7 +150,7 @@ export function ContactsView() {
126
150
  .sort((a, b) => a.name.localeCompare(b.name))
127
151
  .map((company) => ({ value: company.id, label: company.name })),
128
152
  },
129
- { name: "email", label: "Email", type: "email", placeholder: "dana@acme.com" },
153
+ { name: "email", label: "Email", type: "email", placeholder: "dana@northwind.co" },
130
154
  { name: "phone", label: "Phone", type: "tel", placeholder: "+1 555 0100" },
131
155
  ]}
132
156
  onCreate={async (values) => {
@@ -1,9 +1,10 @@
1
1
  import React from "react";
2
2
  import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { BRAND } from "@/lib/brand";
3
4
  import { ContactsView } from "./contacts-view";
4
5
 
5
6
  export const metadata: Metadata = {
6
- title: "Contacts",
7
+ title: `Contacts · ${BRAND.name}`,
7
8
  robots: "noindex",
8
9
  };
9
10