@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,398 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useState } from "react";
4
+ import { callFn, Link } from "@pylonsync/react";
5
+ import { Archive, ArchiveRestore, Check, FolderPlus, Pencil, Plus, Trash2 } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogFooter,
12
+ DialogHeader,
13
+ DialogTitle,
14
+ } from "@/components/ui/dialog";
15
+ import { Avatar, Progress, ProjectMark } from "@/components/task-ui";
16
+ import { FREE_PROJECT_LIMIT, TRIAL_DAYS, planById } from "@/lib/plans";
17
+ import { progressOf, type TaskRow } from "@/lib/tasks";
18
+ import { useLiveRows } from "@/lib/use-live";
19
+
20
+ export interface Project {
21
+ id: string;
22
+ orgId: string;
23
+ name: string;
24
+ description?: string | null;
25
+ status?: string;
26
+ createdAt: string;
27
+ }
28
+
29
+ export const inputCls =
30
+ "h-9 w-full rounded-lg border border-zinc-200 bg-white px-3 text-[13.5px] text-zinc-900 shadow-[0_1px_1px_rgba(0,0,0,0.03)] outline-none transition placeholder:text-zinc-400 focus:border-zinc-400 focus:ring-4 focus:ring-zinc-900/5";
31
+
32
+ /** The server's error message without its `CODE: ` prefix. */
33
+ export function errorText(err: unknown): string {
34
+ const msg = err instanceof Error ? err.message : String(err);
35
+ return msg.replace(/^[A-Z_]+:\s*/, "");
36
+ }
37
+
38
+ function isLimitError(err: unknown): boolean {
39
+ const msg = err instanceof Error ? err.message : String(err);
40
+ return /LIMIT_REACHED/.test(msg) || (err as { code?: string })?.code === "LIMIT_REACHED";
41
+ }
42
+
43
+ // The Projects page. Rows start from the server render and stay live. Every
44
+ // write goes through a server function: createProject and setProjectStatus
45
+ // enforce the free plan's cap, deleteProject removes the project's tasks.
46
+ // A LIMIT_REACHED answer opens the upgrade dialog.
47
+ export function ProjectsPage({
48
+ tenantId,
49
+ projects: initialProjects,
50
+ tasks: initialTasks,
51
+ }: {
52
+ tenantId: string;
53
+ projects: Project[];
54
+ tasks: TaskRow[];
55
+ }) {
56
+ const projects = useLiveRows<Project>("Project", initialProjects, tenantId);
57
+ const tasks = useLiveRows<TaskRow>("Task", initialTasks, tenantId);
58
+ const [view, setView] = useState<"active" | "archived">("active");
59
+ const [creating, setCreating] = useState(false);
60
+ const [editing, setEditing] = useState<Project | null>(null);
61
+ const [limitHit, setLimitHit] = useState(false);
62
+ const [error, setError] = useState<string | null>(null);
63
+
64
+ // `/dashboard/projects?new=1` (from the Overview's empty state) opens the
65
+ // create dialog.
66
+ useEffect(() => {
67
+ const params = new URLSearchParams(window.location.search);
68
+ if (params.get("new") === "1") {
69
+ setCreating(true);
70
+ window.history.replaceState(null, "", window.location.pathname);
71
+ }
72
+ }, []);
73
+
74
+ const archivedCount = projects.filter((p) => p.status === "archived").length;
75
+ const rows = projects
76
+ .filter((p) => ((p.status ?? "active") === "archived" ? "archived" : "active") === view)
77
+ .sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
78
+
79
+ async function setStatus(p: Project, status: "active" | "archived") {
80
+ setError(null);
81
+ try {
82
+ await callFn("setProjectStatus", { projectId: p.id, status });
83
+ } catch (err) {
84
+ if (isLimitError(err)) setLimitHit(true);
85
+ else setError(errorText(err));
86
+ }
87
+ }
88
+
89
+ async function remove(p: Project) {
90
+ const count = tasks.filter((t) => t.projectId === p.id).length;
91
+ const detail = count > 0 ? ` and its ${count} ${count === 1 ? "task" : "tasks"}` : "";
92
+ if (!window.confirm(`Delete ${p.name}${detail}? This can't be undone.`)) return;
93
+ setError(null);
94
+ try {
95
+ await callFn("deleteProject", { projectId: p.id });
96
+ } catch (err) {
97
+ setError(errorText(err));
98
+ }
99
+ }
100
+
101
+ return (
102
+ <div className="mx-auto max-w-[1080px]">
103
+ <div className="flex flex-wrap items-center justify-between gap-3">
104
+ <div className="inline-flex rounded-lg bg-zinc-100 p-0.5 text-[13px]">
105
+ {(["active", "archived"] as const).map((v) => (
106
+ <button
107
+ key={v}
108
+ type="button"
109
+ onClick={() => setView(v)}
110
+ className={
111
+ "rounded-md px-3 py-1 font-medium transition-colors " +
112
+ (view === v ? "bg-white text-zinc-900 shadow-[0_1px_2px_rgba(0,0,0,0.08)]" : "text-zinc-500 hover:text-zinc-800")
113
+ }
114
+ >
115
+ {v === "active" ? "Active" : "Archived"}
116
+ {v === "archived" && archivedCount > 0 ? (
117
+ <span className="ml-1.5 font-mono text-[11px] text-zinc-400">{archivedCount}</span>
118
+ ) : null}
119
+ </button>
120
+ ))}
121
+ </div>
122
+ <Button size="sm" onClick={() => setCreating(true)} className="h-8 rounded-lg px-3 text-[13px]">
123
+ <Plus className="size-4" /> New project
124
+ </Button>
125
+ </div>
126
+
127
+ {error ? <p className="mt-3 text-[12.5px] text-red-600">{error}</p> : null}
128
+
129
+ {rows.length === 0 ? (
130
+ <div className="mt-5 flex flex-col items-center gap-2 rounded-xl border border-dashed border-zinc-300 bg-zinc-50/60 py-14 text-center">
131
+ <span className="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)]">
132
+ <FolderPlus className="size-5" strokeWidth={1.75} />
133
+ </span>
134
+ <p className="mt-2 text-[14px] font-medium text-zinc-800">
135
+ {view === "active" ? "No active projects" : "No archived projects"}
136
+ </p>
137
+ <p className="text-[13px] text-zinc-500">
138
+ {view === "active" ? "Create a project to start adding tasks." : "Archived projects show up here."}
139
+ </p>
140
+ </div>
141
+ ) : (
142
+ <div className="mt-5 overflow-hidden rounded-xl border border-zinc-200/80 bg-white">
143
+ <div className="hidden grid-cols-[minmax(0,1fr)_200px_96px_80px_92px] items-center gap-6 border-b border-zinc-100 bg-zinc-50/60 px-4 py-2 text-[12px] font-medium text-zinc-500 md:grid">
144
+ <span>Name</span>
145
+ <span>Progress</span>
146
+ <span>Team</span>
147
+ <span className="text-right">Open</span>
148
+ <span />
149
+ </div>
150
+ <ul className="divide-y divide-zinc-100">
151
+ {rows.map((p) => {
152
+ const own = tasks.filter((t) => t.projectId === p.id);
153
+ const pr = progressOf(own);
154
+ const archived = p.status === "archived";
155
+ return (
156
+ <li
157
+ key={p.id}
158
+ className="group grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-4 gap-y-2 px-4 py-3 transition-colors hover:bg-zinc-50/70 md:grid-cols-[minmax(0,1fr)_200px_96px_80px_92px] md:gap-6"
159
+ >
160
+ <Link href={`/dashboard/projects/${p.id}`} className="flex min-w-0 items-center gap-3">
161
+ <ProjectMark id={p.id} name={p.name} className="size-8 text-[11px]" />
162
+ <span className="min-w-0">
163
+ <span className="block truncate text-[14px] font-medium text-zinc-900">{p.name}</span>
164
+ <span className="block truncate text-[12.5px] text-zinc-500">
165
+ {p.description || "No description"}
166
+ </span>
167
+ </span>
168
+ </Link>
169
+ <Progress percent={pr.percent} className="col-span-2 row-start-2 md:col-span-1 md:row-start-auto" />
170
+ <span className="hidden -space-x-1 md:flex">
171
+ {team(own).map((m) => (
172
+ <span key={m.id} className="rounded-full ring-2 ring-white">
173
+ <Avatar id={m.id} name={m.name} size="xs" />
174
+ </span>
175
+ ))}
176
+ </span>
177
+ <span className="hidden text-right font-mono text-[12.5px] tabular-nums text-zinc-600 md:block">
178
+ {pr.total - pr.done}
179
+ </span>
180
+ <div className="col-start-2 row-start-1 flex items-center justify-end gap-0.5 md:col-start-auto md:row-start-auto md:opacity-0 md:transition-opacity md:group-hover:opacity-100 md:group-focus-within:opacity-100">
181
+ <IconBtn label="Edit project" onClick={() => setEditing(p)}>
182
+ <Pencil className="size-3.5" />
183
+ </IconBtn>
184
+ <IconBtn
185
+ label={archived ? "Restore project" : "Archive project"}
186
+ onClick={() => void setStatus(p, archived ? "active" : "archived")}
187
+ >
188
+ {archived ? <ArchiveRestore className="size-3.5" /> : <Archive className="size-3.5" />}
189
+ </IconBtn>
190
+ <IconBtn label="Delete project" danger onClick={() => void remove(p)}>
191
+ <Trash2 className="size-3.5" />
192
+ </IconBtn>
193
+ </div>
194
+ </li>
195
+ );
196
+ })}
197
+ </ul>
198
+ </div>
199
+ )}
200
+
201
+ <ProjectDialog
202
+ open={creating}
203
+ onClose={() => setCreating(false)}
204
+ orgId={tenantId}
205
+ onLimit={() => {
206
+ setCreating(false);
207
+ setLimitHit(true);
208
+ }}
209
+ />
210
+ <ProjectDialog
211
+ open={editing !== null}
212
+ project={editing ?? undefined}
213
+ onClose={() => setEditing(null)}
214
+ orgId={tenantId}
215
+ onLimit={() => setLimitHit(true)}
216
+ />
217
+ <UpgradeDialog open={limitHit} onClose={() => setLimitHit(false)} />
218
+ </div>
219
+ );
220
+ }
221
+
222
+ /** Distinct assignees of a project's tasks, most tasks first, at most four. */
223
+ function team(tasks: TaskRow[]): { id: string; name: string }[] {
224
+ const counts = new Map<string, { id: string; name: string; n: number }>();
225
+ for (const t of tasks) {
226
+ if (!t.assigneeId) continue;
227
+ const cur = counts.get(t.assigneeId) ?? { id: t.assigneeId, name: t.assigneeName || "Member", n: 0 };
228
+ cur.n++;
229
+ counts.set(t.assigneeId, cur);
230
+ }
231
+ return [...counts.values()].sort((a, b) => b.n - a.n).slice(0, 4);
232
+ }
233
+
234
+ function IconBtn({
235
+ label,
236
+ danger,
237
+ onClick,
238
+ children,
239
+ }: {
240
+ label: string;
241
+ danger?: boolean;
242
+ onClick: () => void;
243
+ children: React.ReactNode;
244
+ }) {
245
+ return (
246
+ <button
247
+ type="button"
248
+ aria-label={label}
249
+ title={label}
250
+ onClick={onClick}
251
+ className={
252
+ "flex size-7 items-center justify-center rounded-md text-zinc-400 transition-colors hover:bg-zinc-100 " +
253
+ (danger ? "hover:text-red-600" : "hover:text-zinc-800")
254
+ }
255
+ >
256
+ {children}
257
+ </button>
258
+ );
259
+ }
260
+
261
+ // Create (no `project`) or edit a project's name and description. Creates go
262
+ // through createProject (cap-checked); edits through updateProject.
263
+ function ProjectDialog({
264
+ open,
265
+ project,
266
+ orgId,
267
+ onClose,
268
+ onLimit,
269
+ }: {
270
+ open: boolean;
271
+ project?: Project;
272
+ orgId: string;
273
+ onClose: () => void;
274
+ onLimit: () => void;
275
+ }) {
276
+ const [name, setName] = useState("");
277
+ const [desc, setDesc] = useState("");
278
+ const [busy, setBusy] = useState(false);
279
+ const [error, setError] = useState<string | null>(null);
280
+
281
+ useEffect(() => {
282
+ if (!open) return;
283
+ setName(project?.name ?? "");
284
+ setDesc(project?.description ?? "");
285
+ setError(null);
286
+ }, [open, project]);
287
+
288
+ async function submit(e: React.FormEvent) {
289
+ e.preventDefault();
290
+ if (!name.trim()) return;
291
+ setBusy(true);
292
+ setError(null);
293
+ try {
294
+ if (project) {
295
+ await callFn("updateProject", {
296
+ projectId: project.id,
297
+ name: name.trim(),
298
+ description: desc.trim() || undefined,
299
+ });
300
+ } else {
301
+ await callFn("createProject", {
302
+ orgId,
303
+ name: name.trim(),
304
+ ...(desc.trim() ? { description: desc.trim() } : {}),
305
+ });
306
+ }
307
+ onClose();
308
+ } catch (err) {
309
+ if (isLimitError(err)) onLimit();
310
+ else setError(errorText(err));
311
+ } finally {
312
+ setBusy(false);
313
+ }
314
+ }
315
+
316
+ return (
317
+ <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
318
+ <DialogContent className="max-w-md">
319
+ <form onSubmit={submit} className="space-y-4">
320
+ <DialogHeader>
321
+ <DialogTitle>{project ? "Edit project" : "New project"}</DialogTitle>
322
+ <DialogDescription>
323
+ {project ? "Change the name or description." : "Projects hold the tasks your team works on."}
324
+ </DialogDescription>
325
+ </DialogHeader>
326
+ <label className="block">
327
+ <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Name</span>
328
+ <input
329
+ autoFocus
330
+ value={name}
331
+ onChange={(e) => setName(e.target.value)}
332
+ placeholder="Website relaunch"
333
+ maxLength={80}
334
+ className={inputCls}
335
+ />
336
+ </label>
337
+ <label className="block">
338
+ <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Description</span>
339
+ <textarea
340
+ value={desc}
341
+ onChange={(e) => setDesc(e.target.value)}
342
+ rows={3}
343
+ placeholder="What the project delivers, and by when"
344
+ className={inputCls + " h-auto resize-none py-2"}
345
+ />
346
+ </label>
347
+ {error ? <p className="text-[12.5px] text-red-600">{error}</p> : null}
348
+ <DialogFooter>
349
+ <Button type="button" variant="outline" size="sm" onClick={onClose}>
350
+ Cancel
351
+ </Button>
352
+ <Button type="submit" size="sm" disabled={busy || !name.trim()}>
353
+ <Check className="size-3.5" />
354
+ {busy ? "Saving…" : project ? "Save changes" : "Create project"}
355
+ </Button>
356
+ </DialogFooter>
357
+ </form>
358
+ </DialogContent>
359
+ </Dialog>
360
+ );
361
+ }
362
+
363
+ // Shown when a server function answers LIMIT_REACHED. One action: go to
364
+ // Billing, where the trial starts.
365
+ export function UpgradeDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
366
+ const pro = planById("pro")!;
367
+ return (
368
+ <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
369
+ <DialogContent className="max-w-md">
370
+ <DialogHeader>
371
+ <DialogTitle>The free plan allows {FREE_PROJECT_LIMIT} active projects</DialogTitle>
372
+ <DialogDescription>
373
+ Archive a project, or upgrade to Pro for unlimited projects. Pro starts with a {TRIAL_DAYS}-day
374
+ free trial.
375
+ </DialogDescription>
376
+ </DialogHeader>
377
+ <ul className="space-y-1.5 text-[13.5px] text-zinc-700">
378
+ {pro.features.map((f) => (
379
+ <li key={f} className="flex items-center gap-2">
380
+ <Check className="size-3.5 text-brand" /> {f}
381
+ </li>
382
+ ))}
383
+ </ul>
384
+ <DialogFooter>
385
+ <Button type="button" variant="outline" size="sm" onClick={onClose}>
386
+ Not now
387
+ </Button>
388
+ <a
389
+ href="/dashboard/billing?upgrade=pro&interval=annual"
390
+ className="inline-flex h-8 items-center rounded-md bg-zinc-900 px-3 text-[13px] font-medium text-white transition-colors hover:bg-zinc-700"
391
+ >
392
+ {pro.cta}
393
+ </a>
394
+ </DialogFooter>
395
+ </DialogContent>
396
+ </Dialog>
397
+ );
398
+ }
@@ -19,10 +19,10 @@
19
19
  on <html>. */
20
20
  :root {
21
21
  --radius: 0.625rem;
22
- /* Brand accent (blue) + surfaces used by the marketing pages. Re-theme the
23
- whole landing page by editing these three. */
24
- --brand: #2563eb;
25
- --brand-soft: #e8f0fe;
22
+ /* Brand accent + surfaces. app/layout.tsx overrides these from
23
+ lib/site.config.ts, so re-theme there. */
24
+ --brand: #4353ff;
25
+ --brand-soft: #eef0ff;
26
26
  --paper: #fafafa;
27
27
  --background: oklch(1 0 0);
28
28
  --foreground: oklch(0.141 0.005 285.823);
@@ -92,6 +92,7 @@
92
92
  }
93
93
 
94
94
  @theme inline {
95
+ --font-mono: var(--font-geist-mono), ui-monospace, SFMono-Regular, Menlo, monospace;
95
96
  --radius-sm: calc(var(--radius) - 4px);
96
97
  --radius-md: calc(var(--radius) - 2px);
97
98
  --radius-lg: var(--radius);
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" rx="8" fill="#09090b"/><path d="M9.5 23 16 9l6.5 14M12 18h8" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/></svg>
@@ -3,21 +3,26 @@
3
3
  import React, { useState } from "react";
4
4
  import { callFn, db } from "@pylonsync/react";
5
5
  import { createInvite, createOrg, renameOrg } from "@pylonsync/client";
6
+ import { Check, Sparkles } from "lucide-react";
6
7
  import { Button } from "@/components/ui/button";
8
+ import { nameFromEmail } from "@/lib/names";
7
9
  import { TRIAL_DAYS, planById } from "@/lib/plans";
10
+ import { siteConfig } from "@/lib/site.config";
8
11
 
9
12
  type Step = "workspace" | "team" | "project" | "plan";
10
13
  const ORDER: Step[] = ["workspace", "team", "project", "plan"];
11
14
 
12
15
  const inputCls =
13
- "h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-zinc-900 focus:ring-2 focus:ring-zinc-900/10";
16
+ "h-10 w-full rounded-lg border border-zinc-200 bg-white px-3 text-sm text-zinc-900 shadow-[0_1px_1px_rgba(0,0,0,0.03)] outline-none transition placeholder:text-zinc-400 focus:border-zinc-400 focus:ring-4 focus:ring-zinc-900/5";
14
17
 
15
18
  /**
16
19
  * Four short steps, each one decision, each skippable after the first:
17
20
  *
18
- * 1. Workspace name → creates the org and makes it the active tenant
21
+ * 1. Workspace name → creates the org and makes it the active tenant; also
22
+ * asks for the person's name when the account has none
19
23
  * 2. Invite the team → up to three emails, or skip
20
- * 3. First project → so the dashboard is not empty on arrival
24
+ * 3. First project → a named project, or the sample projects from
25
+ * lib/sample-workspace.ts, so the dashboard is not empty
21
26
  * 4. Plan → start the Pro trial (Stripe Checkout) or stay free
22
27
  *
23
28
  * The workspace is created at step 1, so a closed tab still leaves the user
@@ -27,10 +32,14 @@ const inputCls =
27
32
  export function OnboardingWizard({
28
33
  org,
29
34
  email,
35
+ displayName,
30
36
  }: {
31
37
  org: { id: string; name: string } | null;
32
38
  email: string;
39
+ displayName: string;
33
40
  }) {
41
+ const askName = !displayName.trim();
42
+ const [person, setPerson] = useState(askName ? nameFromEmail(email) : displayName);
34
43
  const [orgId, setOrgId] = useState<string | null>(org?.id ?? null);
35
44
  const [step, setStep] = useState<Step>("workspace");
36
45
  const [name, setName] = useState(org?.name ?? suggestName(email));
@@ -55,8 +64,9 @@ export function OnboardingWizard({
55
64
  function saveWorkspace(e: React.FormEvent) {
56
65
  e.preventDefault();
57
66
  const value = name.trim();
58
- if (!value) return;
67
+ if (!value || (askName && !person.trim())) return;
59
68
  void run(async () => {
69
+ if (askName) await callFn("updateProfile", { displayName: person.trim() });
60
70
  if (orgId) {
61
71
  await renameOrg(orgId, value);
62
72
  } else {
@@ -88,6 +98,17 @@ export function OnboardingWizard({
88
98
  });
89
99
  }
90
100
 
101
+ function addSampleProjects() {
102
+ if (!orgId) return;
103
+ void run(async () => {
104
+ const res = await callFn<{ seeded: boolean }>("seedWorkspace", { orgId });
105
+ if (!res.seeded) {
106
+ throw new Error("This workspace already has projects, so the samples were not added.");
107
+ }
108
+ setStep("plan");
109
+ });
110
+ }
111
+
91
112
  async function finish(startTrial: boolean) {
92
113
  if (!orgId) return;
93
114
  await run(async () => {
@@ -113,6 +134,12 @@ export function OnboardingWizard({
113
134
  return (
114
135
  <div className="flex min-h-screen items-center justify-center bg-zinc-50 px-6 py-12">
115
136
  <div className="w-full max-w-md">
137
+ <div className="mb-8 flex items-center justify-center gap-2">
138
+ <span className="flex size-7 items-center justify-center rounded-lg bg-zinc-900 text-[13px] font-bold text-white">
139
+ {siteConfig.brand.letter}
140
+ </span>
141
+ <span className="text-[15px] font-semibold tracking-tight text-zinc-900">{siteConfig.brand.name}</span>
142
+ </div>
116
143
  <div className="mb-6 flex items-center gap-2">
117
144
  {ORDER.map((s, i) => (
118
145
  <span
@@ -121,22 +148,38 @@ export function OnboardingWizard({
121
148
  />
122
149
  ))}
123
150
  </div>
124
- <div className="rounded-2xl border border-zinc-200 bg-white p-8">
151
+ <div className="rounded-2xl bg-white p-8 shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_12px_40px_-12px_rgba(0,0,0,0.12)]">
125
152
  {step === "workspace" && (
126
153
  <form onSubmit={saveWorkspace} className="space-y-5">
127
154
  <div>
128
- <h1 className="text-xl font-semibold tracking-tight text-zinc-900">Name your workspace</h1>
155
+ <h1 className="text-xl font-semibold tracking-tight text-zinc-900">Set up your workspace</h1>
129
156
  <p className="mt-1 text-sm text-zinc-500">Usually your company or team name. You can change it later.</p>
130
157
  </div>
131
- <input
132
- autoFocus
133
- value={name}
134
- onChange={(e) => setName(e.target.value)}
135
- placeholder="Acme Inc"
136
- aria-label="Workspace name"
137
- className={inputCls}
138
- />
139
- <Button type="submit" className="w-full" disabled={busy || !name.trim()}>
158
+ {askName ? (
159
+ <label className="block">
160
+ <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Your name</span>
161
+ <input
162
+ autoFocus
163
+ value={person}
164
+ onChange={(e) => setPerson(e.target.value)}
165
+ placeholder="Dana Reyes"
166
+ autoComplete="name"
167
+ maxLength={60}
168
+ className={inputCls}
169
+ />
170
+ </label>
171
+ ) : null}
172
+ <label className="block">
173
+ <span className="mb-1.5 block text-[13px] font-medium text-zinc-700">Workspace name</span>
174
+ <input
175
+ autoFocus={!askName}
176
+ value={name}
177
+ onChange={(e) => setName(e.target.value)}
178
+ placeholder="Northwind"
179
+ className={inputCls}
180
+ />
181
+ </label>
182
+ <Button type="submit" className="w-full" disabled={busy || !name.trim() || (askName && !person.trim())}>
140
183
  {busy ? "…" : "Continue"}
141
184
  </Button>
142
185
  </form>
@@ -176,7 +219,7 @@ export function OnboardingWizard({
176
219
  <form onSubmit={createFirstProject} className="space-y-5">
177
220
  <div>
178
221
  <h1 className="text-xl font-semibold tracking-tight text-zinc-900">Create your first project</h1>
179
- <p className="mt-1 text-sm text-zinc-500">Something you are working on this week. It shows up on your dashboard.</p>
222
+ <p className="mt-1 text-sm text-zinc-500">Something your team is working on this week.</p>
180
223
  </div>
181
224
  <input
182
225
  autoFocus
@@ -186,14 +229,38 @@ export function OnboardingWizard({
186
229
  aria-label="Project name"
187
230
  className={inputCls}
188
231
  />
189
- <div className="flex gap-2">
190
- <Button type="button" variant="outline" className="flex-1" onClick={() => setStep("plan")} disabled={busy}>
191
- Skip
192
- </Button>
193
- <Button type="submit" className="flex-1" disabled={busy || !project.trim()}>
194
- {busy ? "…" : "Create project"}
195
- </Button>
232
+ <Button type="submit" className="w-full" disabled={busy || !project.trim()}>
233
+ {busy ? "…" : "Create project"}
234
+ </Button>
235
+ <div className="flex items-center gap-3 text-[12px] text-zinc-400">
236
+ <span className="h-px flex-1 bg-zinc-200" />
237
+ or
238
+ <span className="h-px flex-1 bg-zinc-200" />
196
239
  </div>
240
+ <button
241
+ type="button"
242
+ onClick={addSampleProjects}
243
+ disabled={busy}
244
+ className="flex w-full items-start gap-3 rounded-xl border border-zinc-200 p-3.5 text-left transition-colors hover:border-zinc-300 hover:bg-zinc-50 disabled:opacity-60"
245
+ >
246
+ <span className="mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-lg bg-brand-soft text-brand">
247
+ <Sparkles className="size-4" strokeWidth={1.75} />
248
+ </span>
249
+ <span>
250
+ <span className="block text-[14px] font-medium text-zinc-900">Start with sample projects</span>
251
+ <span className="mt-0.5 block text-[12.5px] leading-snug text-zinc-500">
252
+ Two projects with 16 tasks, and one archived project. Delete them any time.
253
+ </span>
254
+ </span>
255
+ </button>
256
+ <button
257
+ type="button"
258
+ onClick={() => setStep("plan")}
259
+ disabled={busy}
260
+ className="w-full text-center text-[13px] text-zinc-500 underline underline-offset-2 hover:text-zinc-900"
261
+ >
262
+ Skip for now
263
+ </button>
197
264
  </form>
198
265
  )}
199
266
 
@@ -207,8 +274,8 @@ export function OnboardingWizard({
207
274
  </div>
208
275
  <ul className="space-y-2 rounded-xl border border-zinc-200 bg-zinc-50 p-4 text-sm text-zinc-700">
209
276
  {pro.features.map((f) => (
210
- <li key={f} className="flex gap-2">
211
- <span className="text-zinc-900">✓</span> {f}
277
+ <li key={f} className="flex items-center gap-2">
278
+ <Check className="size-3.5 text-brand" /> {f}
212
279
  </li>
213
280
  ))}
214
281
  </ul>
@@ -243,7 +310,7 @@ export function OnboardingWizard({
243
310
  /** "jane@northwind.com" → "Northwind". A starting point the user can edit. */
244
311
  function suggestName(email: string): string {
245
312
  const domain = email.split("@")[1]?.split(".")[0] ?? "";
246
- if (!domain || ["gmail", "yahoo", "outlook", "hotmail", "icloud", "proton", "me"].includes(domain)) {
313
+ if (!domain || ["gmail", "yahoo", "outlook", "hotmail", "icloud", "proton", "me", "example"].includes(domain)) {
247
314
  return "";
248
315
  }
249
316
  return domain.charAt(0).toUpperCase() + domain.slice(1);
@@ -29,6 +29,10 @@ export default function OnboardingPage({ auth, response, serverData }: PageProps
29
29
  return null;
30
30
  }
31
31
  }
32
- const me = use(serverData.get<{ email?: string }>("User", auth.user_id));
33
- return <OnboardingWizard org={org} email={me?.email ?? ""} />;
32
+ const me = use(
33
+ serverData.get<{ email?: string; displayName?: string | null }>("User", auth.user_id),
34
+ );
35
+ return (
36
+ <OnboardingWizard org={org} email={me?.email ?? ""} displayName={me?.displayName ?? ""} />
37
+ );
34
38
  }