@pylonsync/create-pylon 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -1,13 +1,13 @@
1
1
  import React, { use } from "react";
2
2
  import type { PageAuth, ServerData, SsrResponse } from "@pylonsync/react";
3
- import { DashboardShell, NAV } from "@/components/dashboard-shell";
3
+ import { DashboardShell, NAV, type ShellProject } from "@/components/dashboard-shell";
4
+ import { personName } from "@/lib/names";
4
5
 
5
- // `app/dashboard/layout.tsx` wraps every /dashboard page in the sidebar +
6
- // top-bar shell, so no page repeats the chrome. This is the thin-server-
7
- // wrapper pattern: the layout gates auth, resolves the chrome's data
8
- // server-side (user email, org name), derives the active nav item + title
9
- // from the request URL, and hands the interactive shell (a `"use client"`
10
- // component) its props.
6
+ // `app/dashboard/layout.tsx` wraps every /dashboard page in the sidebar shell,
7
+ // so no page repeats the chrome. The layout gates auth, resolves the chrome's
8
+ // data on the server (the person's name, the workspace name, the project
9
+ // list), derives the active nav item and title from the request URL, and hands
10
+ // the interactive shell (a `"use client"` component) its props.
11
11
  interface LayoutProps {
12
12
  children: React.ReactNode;
13
13
  url: string;
@@ -30,34 +30,48 @@ export default function DashboardLayout({
30
30
  response.redirect("/login");
31
31
  return null;
32
32
  }
33
- // No active workspace: render the page bare — /dashboard shows the
34
- // full-screen ProvisionWorkspace flow, and the subpages redirect back to
35
- // /dashboard themselves. The shell's org switcher has nothing to show yet.
33
+ // No active workspace: render the page bare. /dashboard shows the
34
+ // ProvisionWorkspace flow, and the subpages redirect back to /dashboard.
36
35
  if (!auth.tenant_id) {
37
36
  return <>{children}</>;
38
37
  }
38
+ const path = (url ?? "").split("?")[0];
39
+ // `/dashboard/projects/<id>` shows that project's board; its name becomes
40
+ // the title with Projects as the breadcrumb parent.
41
+ const projectId = path.match(/^\/dashboard\/projects\/([^/]+)$/)?.[1];
42
+
39
43
  // Every read is started before the first use(): calling them one at a time
40
44
  // makes each wait for the last, since use() suspends on the first pending
41
45
  // thenable. Never Promise.all them — a new pending promise each render means
42
46
  // the page never returns (React error #482).
43
- const mePromise = serverData.get<{ email?: string }>("User", auth.user_id);
47
+ const mePromise = serverData.get<{ email?: string; displayName?: string | null }>("User", auth.user_id);
44
48
  const orgPromise = serverData.get<{ name?: string }>("Org", auth.tenant_id);
49
+ const projectsPromise = serverData.list<ShellProject>("Project");
45
50
 
46
51
  const me = use(mePromise);
47
52
  const org = use(orgPromise);
53
+ const projects = use(projectsPromise).filter((p) => p.orgId === auth.tenant_id);
54
+
48
55
  // Longest matching NAV href wins so /dashboard/projects highlights
49
56
  // Projects, not Overview.
50
- const path = (url ?? "").split("?")[0];
51
57
  const nav =
52
58
  NAV.filter((n) => path === n.href || path.startsWith(n.href + "/")).sort(
53
59
  (a, b) => b.href.length - a.href.length,
54
60
  )[0] ?? NAV[0];
61
+ const project = projectId ? projects.find((p) => p.id === projectId) : undefined;
62
+
55
63
  return (
56
64
  <DashboardShell
57
65
  active={nav.key}
58
- title={nav.label}
66
+ title={project ? project.name : nav.label}
67
+ parent={project ? { label: "Projects", href: "/dashboard/projects" } : undefined}
68
+ activeProjectId={project?.id}
69
+ tenantId={auth.tenant_id}
70
+ userId={auth.user_id}
71
+ userName={personName({ displayName: me?.displayName, email: me?.email })}
59
72
  userEmail={me?.email ?? ""}
60
73
  orgName={org?.name}
74
+ projects={projects.map((p) => ({ id: p.id, orgId: p.orgId, name: p.name, status: p.status }))}
61
75
  >
62
76
  {children}
63
77
  </DashboardShell>
@@ -0,0 +1,332 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import { callFn, Link } from "@pylonsync/react";
5
+ import { ArrowRight, FolderPlus, Sparkles } from "lucide-react";
6
+ import { SetupChecklist, type SetupState } from "@/components/setup-checklist";
7
+ import {
8
+ Avatar,
9
+ DueLabel,
10
+ EmptyAvatar,
11
+ PriorityIcon,
12
+ Progress,
13
+ ProjectMark,
14
+ StatusIcon,
15
+ shortDay,
16
+ } from "@/components/task-ui";
17
+ import { FREE_PROJECT_LIMIT } from "@/lib/plans";
18
+ import { compareTasks, daysUntil, progressOf, type TaskRow } from "@/lib/tasks";
19
+ import { useLiveRows } from "@/lib/use-live";
20
+ import type { Project } from "./projects-client";
21
+
22
+ // The Overview. Projects and tasks start from the server-rendered rows and
23
+ // stay live through sync, so a task finished in another tab or by a teammate
24
+ // updates the counts here without a reload.
25
+ export function Overview({
26
+ tenantId,
27
+ userId,
28
+ firstName,
29
+ projects: initialProjects,
30
+ tasks: initialTasks,
31
+ memberCount,
32
+ plan,
33
+ today,
34
+ canSeed,
35
+ setup,
36
+ }: {
37
+ tenantId: string;
38
+ userId: string;
39
+ firstName: string;
40
+ projects: Project[];
41
+ tasks: TaskRow[];
42
+ memberCount: number;
43
+ plan: string;
44
+ /** `YYYY-MM-DD` from the server render. */
45
+ today: string;
46
+ /** Owners and admins may add the sample projects to an empty workspace. */
47
+ canSeed: boolean;
48
+ /** Getting-started state; null once the checklist is dismissed. */
49
+ setup?: SetupState | null;
50
+ }) {
51
+ const projects = useLiveRows<Project>("Project", initialProjects, tenantId);
52
+ const tasks = useLiveRows<TaskRow>("Task", initialTasks, tenantId);
53
+
54
+ const active = projects.filter((p) => (p.status ?? "active") !== "archived");
55
+ const activeIds = new Set(active.map((p) => p.id));
56
+ const liveTasks = tasks.filter((t) => activeIds.has(t.projectId));
57
+ const open = liveTasks.filter((t) => t.status !== "done");
58
+ const dueThisWeek = open.filter((t) => {
59
+ if (!t.dueDate) return false;
60
+ const d = daysUntil(t.dueDate, today);
61
+ return d >= 0 && d <= 7;
62
+ }).length;
63
+ const overdue = open.filter((t) => t.dueDate && daysUntil(t.dueDate, today) < 0).length;
64
+ const doneThisWeek = liveTasks.filter(
65
+ (t) => t.status === "done" && t.completedAt && daysUntil(t.completedAt.slice(0, 10), today) >= -7,
66
+ ).length;
67
+ const [scope, setScope] = useState<"everyone" | "mine">("everyone");
68
+ const mineCount = open.filter((t) => t.assigneeId === userId).length;
69
+ // Open tasks by due date, earliest first; undated tasks last.
70
+ const upNext = open
71
+ .filter((t) => scope === "everyone" || t.assigneeId === userId)
72
+ .sort((a, b) => (a.dueDate ?? "9999") < (b.dueDate ?? "9999") ? -1 : (a.dueDate ?? "9999") > (b.dueDate ?? "9999") ? 1 : compareTasks(a, b))
73
+ .slice(0, 8);
74
+ const recent = liveTasks
75
+ .filter((t) => t.status === "done" && t.completedAt)
76
+ .sort((a, b) => (a.completedAt! < b.completedAt! ? 1 : -1))
77
+ .slice(0, 4);
78
+ const projectName = new Map(projects.map((p) => [p.id, p.name]));
79
+
80
+ const greeting = firstName ? `Welcome back, ${firstName}` : "Welcome back";
81
+ const summary =
82
+ active.length === 0
83
+ ? "Create a project to start tracking work."
84
+ : `${open.length} open ${open.length === 1 ? "task" : "tasks"} across ${active.length} active ${
85
+ active.length === 1 ? "project" : "projects"
86
+ }.`;
87
+
88
+ return (
89
+ <div className="mx-auto max-w-[1080px] space-y-8">
90
+ <div className="flex flex-wrap items-end justify-between gap-4">
91
+ <div>
92
+ <h1 className="text-[22px] font-semibold tracking-[-0.015em] text-zinc-900">{greeting}</h1>
93
+ <p className="mt-1 text-[14px] text-zinc-500">{summary}</p>
94
+ </div>
95
+ {active.length > 0 ? (
96
+ <Link
97
+ href="/dashboard/projects"
98
+ className="inline-flex h-8 items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 text-[13px] font-medium text-zinc-700 shadow-[0_1px_1px_rgba(0,0,0,0.03)] transition-colors hover:bg-zinc-50"
99
+ >
100
+ All projects <ArrowRight className="size-3.5" />
101
+ </Link>
102
+ ) : null}
103
+ </div>
104
+
105
+ {setup ? <SetupChecklist state={setup} /> : null}
106
+
107
+ {projects.length === 0 ? (
108
+ <EmptyWorkspace orgId={tenantId} canSeed={canSeed} />
109
+ ) : (
110
+ <>
111
+ <div className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-zinc-200/80 bg-zinc-200/60 lg:grid-cols-4">
112
+ <Stat label="Open tasks" value={open.length} note={`${liveTasks.length - open.length} done`} />
113
+ <Stat
114
+ label="Due in 7 days"
115
+ value={dueThisWeek}
116
+ note={overdue > 0 ? `${overdue} overdue` : "none overdue"}
117
+ alert={overdue > 0}
118
+ />
119
+ <Stat label="Completed this week" value={doneThisWeek} note="last 7 days" />
120
+ <Stat
121
+ label="Plan"
122
+ value={<span className="capitalize">{plan}</span>}
123
+ note={
124
+ plan === "free" ? (
125
+ <Link href="/dashboard/billing" className="text-brand hover:underline">
126
+ {active.length} of {FREE_PROJECT_LIMIT} projects used
127
+ </Link>
128
+ ) : (
129
+ `${memberCount} ${memberCount === 1 ? "member" : "members"}`
130
+ )
131
+ }
132
+ />
133
+ </div>
134
+
135
+ <div className="grid gap-6 lg:grid-cols-[minmax(0,1.55fr)_minmax(0,1fr)]">
136
+ <section className="min-w-0 self-start rounded-xl border border-zinc-200/80 bg-white">
137
+ <header className="flex items-center justify-between border-b border-zinc-100 px-4 py-3">
138
+ <h2 className="text-[13px] font-semibold text-zinc-900">Up next</h2>
139
+ <div className="inline-flex rounded-md bg-zinc-100 p-0.5 text-[12px]">
140
+ {(
141
+ [
142
+ ["everyone", "Everyone"],
143
+ ["mine", `Assigned to me · ${mineCount}`],
144
+ ] as const
145
+ ).map(([key, label]) => (
146
+ <button
147
+ key={key}
148
+ type="button"
149
+ onClick={() => setScope(key)}
150
+ className={
151
+ "rounded px-2 py-0.5 font-medium transition-colors " +
152
+ (scope === key ? "bg-white text-zinc-900 shadow-[0_1px_2px_rgba(0,0,0,0.08)]" : "text-zinc-500 hover:text-zinc-800")
153
+ }
154
+ >
155
+ {label}
156
+ </button>
157
+ ))}
158
+ </div>
159
+ </header>
160
+ {upNext.length === 0 ? (
161
+ <p className="px-4 py-10 text-center text-[13px] text-zinc-500">
162
+ {scope === "mine" ? "Nothing assigned to you." : "No open tasks."}
163
+ </p>
164
+ ) : (
165
+ <ul className="divide-y divide-zinc-100">
166
+ {upNext.map((t) => (
167
+ <li key={t.id}>
168
+ <Link
169
+ href={`/dashboard/projects/${t.projectId}`}
170
+ className="flex items-center gap-3 px-4 py-2.5 transition-colors hover:bg-zinc-50"
171
+ >
172
+ <PriorityIcon priority={t.priority} />
173
+ <StatusIcon status={t.status} />
174
+ <span className="min-w-0 flex-1 truncate text-[13.5px] text-zinc-800">{t.title}</span>
175
+ <span className="hidden max-w-[10rem] items-center gap-1.5 truncate text-[12px] text-zinc-500 sm:inline-flex">
176
+ <ProjectMark
177
+ id={t.projectId}
178
+ name={projectName.get(t.projectId) ?? "?"}
179
+ className="size-4 text-[7.5px]"
180
+ />
181
+ <span className="truncate">{projectName.get(t.projectId)}</span>
182
+ </span>
183
+ <span className="w-16 text-right">
184
+ <DueLabel due={t.dueDate} today={today} />
185
+ </span>
186
+ {t.assigneeId ? (
187
+ <Avatar id={t.assigneeId} name={t.assigneeName || "Member"} size="xs" />
188
+ ) : (
189
+ <EmptyAvatar size="xs" />
190
+ )}
191
+ </Link>
192
+ </li>
193
+ ))}
194
+ </ul>
195
+ )}
196
+ </section>
197
+
198
+ <div className="min-w-0 space-y-6">
199
+ <section className="rounded-xl border border-zinc-200/80 bg-white">
200
+ <header className="flex items-center justify-between border-b border-zinc-100 px-4 py-3">
201
+ <h2 className="text-[13px] font-semibold text-zinc-900">Projects</h2>
202
+ <Link href="/dashboard/projects" className="text-[12px] font-medium text-zinc-500 hover:text-zinc-900">
203
+ View all
204
+ </Link>
205
+ </header>
206
+ {active.length === 0 ? (
207
+ <p className="px-4 py-10 text-center text-[13px] text-zinc-500">No active projects.</p>
208
+ ) : (
209
+ <ul className="divide-y divide-zinc-100">
210
+ {active.map((p) => {
211
+ const pr = progressOf(tasks.filter((t) => t.projectId === p.id));
212
+ return (
213
+ <li key={p.id}>
214
+ <Link
215
+ href={`/dashboard/projects/${p.id}`}
216
+ className="block px-4 py-3 transition-colors hover:bg-zinc-50"
217
+ >
218
+ <div className="flex items-center gap-2.5">
219
+ <ProjectMark id={p.id} name={p.name} className="size-6 text-[9.5px]" />
220
+ <span className="min-w-0 flex-1 truncate text-[13.5px] font-medium text-zinc-900">
221
+ {p.name}
222
+ </span>
223
+ <span className="font-mono text-[11.5px] tabular-nums text-zinc-400">
224
+ {pr.done}/{pr.total}
225
+ </span>
226
+ </div>
227
+ <Progress percent={pr.percent} className="mt-2.5" />
228
+ </Link>
229
+ </li>
230
+ );
231
+ })}
232
+ </ul>
233
+ )}
234
+ </section>
235
+
236
+ {recent.length > 0 ? (
237
+ <section className="rounded-xl border border-zinc-200/80 bg-white">
238
+ <header className="border-b border-zinc-100 px-4 py-3">
239
+ <h2 className="text-[13px] font-semibold text-zinc-900">Recently completed</h2>
240
+ </header>
241
+ <ul className="divide-y divide-zinc-100">
242
+ {recent.map((t) => (
243
+ <li key={t.id} className="flex items-center gap-2.5 px-4 py-2.5">
244
+ <StatusIcon status="done" />
245
+ <span className="min-w-0 flex-1 truncate text-[13px] text-zinc-600">{t.title}</span>
246
+ {t.assigneeId ? (
247
+ <Avatar id={t.assigneeId} name={t.assigneeName || "Member"} size="xs" />
248
+ ) : null}
249
+ <span className="w-12 text-right text-[12px] tabular-nums text-zinc-400">
250
+ {shortDay(t.completedAt!.slice(0, 10))}
251
+ </span>
252
+ </li>
253
+ ))}
254
+ </ul>
255
+ </section>
256
+ ) : null}
257
+ </div>
258
+ </div>
259
+ </>
260
+ )}
261
+ </div>
262
+ );
263
+ }
264
+
265
+ function Stat({
266
+ label,
267
+ value,
268
+ note,
269
+ alert,
270
+ }: {
271
+ label: string;
272
+ value: React.ReactNode;
273
+ note: React.ReactNode;
274
+ alert?: boolean;
275
+ }) {
276
+ return (
277
+ <div className="bg-white p-4">
278
+ <div className="text-[12.5px] text-zinc-500">{label}</div>
279
+ <div className="mt-1.5 text-[26px] font-semibold leading-none tracking-[-0.02em] tabular-nums text-zinc-900">
280
+ {value}
281
+ </div>
282
+ <div className={"mt-2 text-[12px] " + (alert ? "text-red-600" : "text-zinc-400")}>{note}</div>
283
+ </div>
284
+ );
285
+ }
286
+
287
+ function EmptyWorkspace({ orgId, canSeed }: { orgId: string; canSeed: boolean }) {
288
+ const [busy, setBusy] = useState(false);
289
+ const [error, setError] = useState<string | null>(null);
290
+ async function seed() {
291
+ setBusy(true);
292
+ setError(null);
293
+ try {
294
+ await callFn("seedWorkspace", { orgId });
295
+ } catch (e) {
296
+ setError(e instanceof Error ? e.message.replace(/^[A-Z_]+:\s*/, "") : String(e));
297
+ } finally {
298
+ setBusy(false);
299
+ }
300
+ }
301
+ return (
302
+ <div className="rounded-xl border border-dashed border-zinc-300 bg-zinc-50/60 px-6 py-14 text-center">
303
+ <span className="mx-auto flex size-10 items-center justify-center rounded-xl bg-white text-zinc-500 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]">
304
+ <FolderPlus className="size-5" strokeWidth={1.75} />
305
+ </span>
306
+ <h2 className="mt-4 text-[15px] font-semibold text-zinc-900">This workspace has no projects</h2>
307
+ <p className="mx-auto mt-1 max-w-sm text-[13.5px] text-zinc-500">
308
+ Create your first project, or add two sample projects with tasks to try the board.
309
+ </p>
310
+ <div className="mt-5 flex flex-wrap items-center justify-center gap-2">
311
+ <Link
312
+ href="/dashboard/projects?new=1"
313
+ className="inline-flex h-9 items-center rounded-lg bg-zinc-900 px-4 text-[13px] font-medium text-white transition-colors hover:bg-zinc-700"
314
+ >
315
+ Create a project
316
+ </Link>
317
+ {canSeed ? (
318
+ <button
319
+ type="button"
320
+ onClick={() => void seed()}
321
+ disabled={busy}
322
+ className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-4 text-[13px] font-medium text-zinc-800 transition-colors hover:bg-zinc-50 disabled:opacity-60"
323
+ >
324
+ <Sparkles className="size-3.5 text-zinc-500" strokeWidth={1.75} />
325
+ {busy ? "Adding…" : "Add sample projects"}
326
+ </button>
327
+ ) : null}
328
+ </div>
329
+ {error ? <p className="mt-3 text-[12.5px] text-red-600">{error}</p> : null}
330
+ </div>
331
+ );
332
+ }
@@ -1,54 +1,46 @@
1
1
  import React, { use } from "react";
2
2
  import { type Metadata, type PageProps } from "@pylonsync/react";
3
3
  import { ProvisionWorkspace } from "./provision-workspace";
4
- import {
5
- Overview,
6
- type Project,
7
- type OrgMemberRow,
8
- type Subscription,
9
- } from "./dashboard-client";
4
+ import { Overview } from "./overview-client";
5
+ import type { Project } from "./projects-client";
6
+ import type { OrgMemberRow, Subscription } from "./dashboard-client";
10
7
  import type { SetupState } from "@/components/setup-checklist";
8
+ import { firstName } from "@/lib/names";
9
+ import { isoDay, type TaskRow } from "@/lib/tasks";
11
10
 
12
11
  export const metadata: Metadata = {
13
- title: "Dashboard — Acme",
12
+ title: "Overview — Acme",
14
13
  robots: "noindex",
15
14
  };
16
15
 
17
16
  // `app/dashboard/page.tsx` → `/dashboard`. The dashboard layout owns the auth
18
- // gate and the shell chrome; this page reads the active org's rows during the
19
- // render via `serverData` + React 19 `use()` — resolved server-side and
20
- // replayed on hydration, so the dashboard paints with real data on the first
21
- // byte (no client fetch, no empty-state flash).
17
+ // gate and the shell chrome; this page reads the active workspace's rows
18
+ // during the render via `serverData` + React 19 `use()`, so the Overview
19
+ // paints with real data on the first byte. The client then keeps projects and
20
+ // tasks live.
22
21
  export default function DashboardPage({ auth, response, serverData }: PageProps) {
23
- // No active workspace (signup's auto-provision failed, or the user left/
24
- // deleted their last org). Every read below is tenant-scoped, so instead of
25
- // an empty shell, provision one client-side and reload into a ready dashboard.
26
- // The layout renders this bare (no shell) when there's no tenant.
22
+ // No active workspace (the user left or deleted their last org, or quit the
23
+ // wizard before step 1). Every read below is tenant-scoped, so select an
24
+ // existing workspace or go to onboarding instead of rendering an empty shell.
27
25
  if (!auth.tenant_id) {
28
26
  return <ProvisionWorkspace />;
29
27
  }
30
28
  // Every read is STARTED here, before the first use(). Each serverData call
31
29
  // returns a thenable the handle caches by key, and use() suspends on the
32
- // first one still pending — so reading them one at a time makes each wait
33
- // for the last. Six reads that way is six round trips; issued together it
34
- // is one, and the replayed render finds each already cached.
30
+ // first one still pending, so reading them one at a time makes each wait
31
+ // for the last. Issued together they overlap.
35
32
  //
36
33
  // Do NOT reach for Promise.all. It builds a new, pending, uncached promise
37
34
  // on every render, so use() suspends, re-renders, builds another, and the
38
- // page never returns — React reports it as an async Client Component
39
- // (minified error #482).
40
- //
41
- // The onboarding redirect below only needs `org`, so on that path the other
42
- // reads are issued and thrown away. A redirect is the rare case; paying one
43
- // round trip on every normal load to save four on a rare one is the wrong
44
- // way round.
45
- const mePromise = serverData.get<{ email?: string }>("User", auth.user_id!);
35
+ // page never returns (React error #482).
36
+ const mePromise = serverData.get<{ email?: string; displayName?: string | null }>("User", auth.user_id!);
46
37
  const orgPromise = serverData.get<{
47
38
  name?: string;
48
39
  onboardedAt?: string | null;
49
40
  setupDismissedAt?: string | null;
50
41
  }>("Org", auth.tenant_id);
51
42
  const projectsPromise = serverData.list<Project>("Project");
43
+ const tasksPromise = serverData.list<TaskRow>("Task");
52
44
  const membersPromise = serverData.list<OrgMemberRow>("OrgMember");
53
45
  const subsPromise = serverData.list<Subscription>("StripeSubscription");
54
46
  const invitesPromise =
@@ -65,14 +57,13 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
65
57
  response.redirect("/onboarding");
66
58
  return null;
67
59
  }
68
- const projects = use(projectsPromise);
60
+ const projects = use(projectsPromise).filter((p) => p.orgId === auth.tenant_id);
61
+ const tasks = use(tasksPromise).filter((t) => t.orgId === auth.tenant_id);
69
62
  // The OrgMember read policy returns this user's memberships across every org,
70
63
  // so scope the count to the active workspace.
71
- const memberCount = use(membersPromise).filter(
72
- (m) => m.orgId === auth.tenant_id,
73
- ).length;
74
- // Active-plan badge from the workspace's Stripe subscription (Free until one
75
- // exists). Scoped to the active tenant by the plugin's read policy.
64
+ const memberCount = use(membersPromise).filter((m) => m.orgId === auth.tenant_id).length;
65
+ // Plan from the workspace's Stripe subscription (Free until one exists).
66
+ // Scoped to the active tenant by the plugin's read policy.
76
67
  const active = use(subsPromise).find((s) =>
77
68
  ["active", "trialing", "past_due"].includes(s.status),
78
69
  );
@@ -92,13 +83,16 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
92
83
  };
93
84
  return (
94
85
  <Overview
95
- setup={setup}
96
86
  tenantId={auth.tenant_id}
97
- orgName={org?.name}
98
- userEmail={me?.email}
87
+ userId={auth.user_id!}
88
+ firstName={firstName({ displayName: me?.displayName, email: me?.email })}
99
89
  projects={projects}
90
+ tasks={tasks}
100
91
  memberCount={memberCount}
101
92
  plan={plan}
93
+ today={isoDay(new Date())}
94
+ canSeed={manages}
95
+ setup={setup}
102
96
  />
103
97
  );
104
98
  }
@@ -0,0 +1,29 @@
1
+ import React, { use } from "react";
2
+ import { type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { Board } from "../../board-client";
4
+ import type { Project } from "../../projects-client";
5
+ import { isoDay, type TaskRow } from "@/lib/tasks";
6
+
7
+ export const metadata: Metadata = {
8
+ title: "Project — Acme",
9
+ robots: "noindex",
10
+ };
11
+
12
+ // `/dashboard/projects/:id` — one project's task board. The Project read
13
+ // policy only returns rows from the active workspace, so an id from another
14
+ // workspace resolves to null and becomes a real 404.
15
+ export default function ProjectBoardPage({ auth, params, response, serverData }: PageProps<{ id: string }>) {
16
+ if (!auth.tenant_id) {
17
+ response.redirect("/dashboard");
18
+ return null;
19
+ }
20
+ const projectPromise = serverData.get<Project>("Project", params.id);
21
+ const tasksPromise = serverData.query<TaskRow>("Task", { projectId: params.id });
22
+ const project = use(projectPromise);
23
+ if (!project || project.orgId !== auth.tenant_id) {
24
+ response.notFound();
25
+ return null;
26
+ }
27
+ const tasks = use(tasksPromise).filter((t) => t.orgId === auth.tenant_id);
28
+ return <Board tenantId={auth.tenant_id} project={project} tasks={tasks} today={isoDay(new Date())} />;
29
+ }
@@ -1,20 +1,24 @@
1
1
  import React, { use } from "react";
2
2
  import { type Metadata, type PageProps } from "@pylonsync/react";
3
- import { Projects, type Project } from "../dashboard-client";
3
+ import { ProjectsPage, type Project } from "../projects-client";
4
+ import type { TaskRow } from "@/lib/tasks";
4
5
 
5
6
  export const metadata: Metadata = {
6
7
  title: "Projects — Acme",
7
8
  robots: "noindex",
8
9
  };
9
10
 
10
- // `/dashboard/projects` — this org's projects, server-resolved via `use()` then
11
- // kept live + optimistic by `db` on the client. Auth gate + shell chrome come
12
- // from the dashboard layout.
13
- export default function ProjectsPage({ auth, response, serverData }: PageProps) {
11
+ // `/dashboard/projects` — the workspace's projects with task progress,
12
+ // resolved on the server and kept live on the client. Auth gate and shell
13
+ // chrome come from the dashboard layout.
14
+ export default function Page({ auth, response, serverData }: PageProps) {
14
15
  if (!auth.tenant_id) {
15
16
  response.redirect("/dashboard");
16
17
  return null;
17
18
  }
18
- const projects = use(serverData.list<Project>("Project"));
19
- return <Projects tenantId={auth.tenant_id} initial={projects} />;
19
+ const projectsPromise = serverData.list<Project>("Project");
20
+ const tasksPromise = serverData.list<TaskRow>("Task");
21
+ const projects = use(projectsPromise).filter((p) => p.orgId === auth.tenant_id);
22
+ const tasks = use(tasksPromise).filter((t) => t.orgId === auth.tenant_id);
23
+ return <ProjectsPage tenantId={auth.tenant_id} projects={projects} tasks={tasks} />;
20
24
  }