@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,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import React, { useEffect, useState } from "react";
4
- import { db, callFn } from "@pylonsync/react";
4
+ import { callFn } from "@pylonsync/react";
5
5
  import {
6
6
  createInvite,
7
7
  deleteOrg,
@@ -15,16 +15,10 @@ import {
15
15
  type OrgMember,
16
16
  type PendingInvite,
17
17
  } from "@pylonsync/client";
18
+ import { Mail } from "lucide-react";
18
19
  import { Button } from "@/components/ui/button";
19
- import {
20
- Dialog,
21
- DialogContent,
22
- DialogDescription,
23
- DialogFooter,
24
- DialogHeader,
25
- DialogTitle,
26
- } from "@/components/ui/dialog";
27
- import { SetupChecklist, type SetupState } from "@/components/setup-checklist";
20
+ import { Avatar } from "@/components/task-ui";
21
+ import { personName } from "@/lib/names";
28
22
  import {
29
23
  FREE_PROJECT_LIMIT,
30
24
  TRIAL_DAYS,
@@ -32,28 +26,7 @@ import {
32
26
  formatPrice,
33
27
  planById,
34
28
  } from "@/lib/plans";
35
- import {
36
- FolderKanban,
37
- Users2,
38
- CreditCard,
39
- ArrowRight,
40
- FolderPlus,
41
- Plus,
42
- Pencil,
43
- Archive,
44
- ArchiveRestore,
45
- Trash2,
46
- Check,
47
- } from "lucide-react";
48
-
49
- export interface Project {
50
- id: string;
51
- orgId: string;
52
- name: string;
53
- description?: string;
54
- status?: string;
55
- createdAt: string;
56
- }
29
+ import { inputCls } from "./projects-client";
57
30
 
58
31
  // OrgMember rows as returned by `serverData.list("OrgMember")` (the entity
59
32
  // shape — camelCase fields). The read policy returns the caller's memberships
@@ -101,566 +74,13 @@ function Card({
101
74
  children: React.ReactNode;
102
75
  }) {
103
76
  return (
104
- <div className="rounded-xl border border-zinc-200 bg-white p-5">
105
- <div className="flex items-center justify-between">
106
- <h2 className="text-sm font-semibold text-zinc-900">{title}</h2>
77
+ <section className="rounded-xl border border-zinc-200/80 bg-white">
78
+ <header className="flex items-center justify-between gap-3 border-b border-zinc-100 px-5 py-3">
79
+ <h2 className="text-[13.5px] font-semibold text-zinc-900">{title}</h2>
107
80
  {action}
108
- </div>
109
- <div className="mt-3">{children}</div>
110
- </div>
111
- );
112
- }
113
-
114
- // A stat tile: icon + label, a big value, and a small context line.
115
- function StatCard({
116
- icon,
117
- label,
118
- value,
119
- hint,
120
- }: {
121
- icon: React.ReactNode;
122
- label: string;
123
- value: React.ReactNode;
124
- hint?: React.ReactNode;
125
- }) {
126
- return (
127
- <div className="rounded-xl border border-zinc-200 bg-white p-4">
128
- <div className="flex items-center gap-2 text-zinc-400">
129
- {icon}
130
- <span className="text-[11px] font-medium uppercase tracking-wide">
131
- {label}
132
- </span>
133
- </div>
134
- <div className="mt-2 text-2xl font-semibold text-zinc-900">{value}</div>
135
- {hint != null && <div className="mt-0.5 text-xs text-zinc-400">{hint}</div>}
136
- </div>
137
- );
138
- }
139
-
140
- // Deterministic, timezone-independent date (UTC parts) so the SSR render and
141
- // client hydration always agree — a locale/tz-dependent format would mismatch
142
- // and throw a React #418 hydration error.
143
- const MONTHS = [
144
- "Jan", "Feb", "Mar", "Apr", "May", "Jun",
145
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
146
- ];
147
- function fmtDate(iso: string) {
148
- const d = new Date(iso);
149
- if (Number.isNaN(d.getTime())) return "";
150
- return `${MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()}`;
151
- }
152
-
153
- // A stable accent per project (hashed from its id) so each card/monogram gets a
154
- // consistent color without persisting one.
155
- const ACCENTS = [
156
- "bg-blue-50 text-blue-600",
157
- "bg-violet-50 text-violet-600",
158
- "bg-emerald-50 text-emerald-600",
159
- "bg-amber-50 text-amber-600",
160
- "bg-rose-50 text-rose-600",
161
- "bg-cyan-50 text-cyan-600",
162
- ];
163
- function accentFor(id: string) {
164
- let h = 0;
165
- for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) >>> 0;
166
- return ACCENTS[h % ACCENTS.length];
167
- }
168
- function monogram(name: string) {
169
- const parts = name.trim().split(/\s+/).filter(Boolean);
170
- const s =
171
- parts.length >= 2 ? parts[0][0] + parts[1][0] : name.trim()[0] ?? "?";
172
- return s.toUpperCase();
173
- }
174
-
175
- function StatusPill({ status }: { status: string }) {
176
- const archived = status === "archived";
177
- return (
178
- <span
179
- className={
180
- "inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium " +
181
- (archived
182
- ? "bg-zinc-100 text-zinc-500"
183
- : "bg-emerald-50 text-emerald-600")
184
- }
185
- >
186
- <span
187
- className={
188
- "size-1.5 rounded-full " +
189
- (archived ? "bg-zinc-400" : "bg-emerald-500")
190
- }
191
- />
192
- {archived ? "Archived" : "Active"}
193
- </span>
194
- );
195
- }
196
-
197
- const inputCls =
198
- "h-9 w-full rounded-md 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";
199
-
200
- /* ============================ Overview ============================ */
201
-
202
- export function Overview({
203
- tenantId,
204
- orgName,
205
- userEmail,
206
- projects,
207
- memberCount,
208
- plan,
209
- setup,
210
- }: {
211
- tenantId: string | null;
212
- orgName?: string;
213
- userEmail?: string;
214
- projects: Project[];
215
- memberCount: number;
216
- plan: string;
217
- /** Getting-started state; null once the checklist is dismissed. */
218
- setup?: SetupState | null;
219
- }) {
220
- if (!tenantId) return <NoOrg />;
221
- const activeCount = projects.filter(
222
- (p) => (p.status ?? "active") !== "archived",
223
- ).length;
224
- const archivedCount = projects.length - activeCount;
225
- const recent = [...projects]
226
- .sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))
227
- .slice(0, 5);
228
- const firstName = (userEmail ?? "").split("@")[0];
229
-
230
- return (
231
- <div className="space-y-6">
232
- <div>
233
- <h1 className="text-xl font-semibold tracking-tight text-zinc-900">
234
- {orgName ? `${orgName} overview` : "Overview"}
235
- </h1>
236
- <p className="mt-1 text-sm text-zinc-500">
237
- {firstName ? `Welcome back, ${firstName}. ` : ""}Here&apos;s what&apos;s
238
- happening in your workspace.
239
- </p>
240
- </div>
241
-
242
- {setup ? <SetupChecklist state={setup} /> : null}
243
-
244
- <div className="grid gap-4 sm:grid-cols-3">
245
- <StatCard
246
- icon={<FolderKanban className="size-4" />}
247
- label="Projects"
248
- value={activeCount}
249
- hint={archivedCount > 0 ? `${archivedCount} archived` : "active"}
250
- />
251
- <StatCard
252
- icon={<Users2 className="size-4" />}
253
- label="Members"
254
- value={memberCount}
255
- hint={memberCount === 1 ? "just you" : "in this workspace"}
256
- />
257
- <StatCard
258
- icon={<CreditCard className="size-4" />}
259
- label="Plan"
260
- value={<span className="capitalize">{plan}</span>}
261
- hint={
262
- plan === "free" ? (
263
- <a
264
- href="/dashboard/billing"
265
- className="text-brand hover:underline"
266
- >
267
- Upgrade to Pro →
268
- </a>
269
- ) : (
270
- "thanks for the support"
271
- )
272
- }
273
- />
274
- </div>
275
-
276
- <Card
277
- title="Recent projects"
278
- action={
279
- <a
280
- href="/dashboard/projects"
281
- className="inline-flex items-center gap-1 text-[13px] font-medium text-brand hover:underline"
282
- >
283
- View all <ArrowRight className="size-3.5" />
284
- </a>
285
- }
286
- >
287
- {recent.length === 0 ? (
288
- <div className="flex flex-col items-center gap-2 py-8 text-center">
289
- <span className="flex size-10 items-center justify-center rounded-xl bg-zinc-50 text-zinc-400">
290
- <FolderPlus className="size-5" />
291
- </span>
292
- <p className="text-sm text-zinc-500">No projects yet.</p>
293
- <a
294
- href="/dashboard/projects"
295
- className="text-[13px] font-medium text-brand hover:underline"
296
- >
297
- Create your first project →
298
- </a>
299
- </div>
300
- ) : (
301
- <ul className="-my-1 divide-y divide-zinc-100">
302
- {recent.map((p) => (
303
- <li key={p.id} className="flex items-center gap-3 py-2.5">
304
- <span
305
- className={
306
- "flex size-8 shrink-0 items-center justify-center rounded-lg text-[12px] font-semibold " +
307
- accentFor(p.id)
308
- }
309
- >
310
- {monogram(p.name)}
311
- </span>
312
- <div className="min-w-0 flex-1">
313
- <div className="truncate text-sm font-medium text-zinc-900">
314
- {p.name}
315
- </div>
316
- <div className="truncate text-xs text-zinc-400">
317
- Created {fmtDate(p.createdAt)}
318
- </div>
319
- </div>
320
- <StatusPill status={p.status ?? "active"} />
321
- </li>
322
- ))}
323
- </ul>
324
- )}
325
- </Card>
326
- </div>
327
- );
328
- }
329
-
330
- /* ============================ Projects ============================ */
331
-
332
- export function Projects({
333
- tenantId,
334
- initial,
335
- }: {
336
- tenantId: string | null;
337
- initial: Project[];
338
- }) {
339
- if (!tenantId) return <NoOrg />;
340
- return <ProjectsList orgId={tenantId} initial={initial} />;
341
- }
342
-
343
- // Live + optimistic, seeded from the server. `db.useQuery` is reactive
344
- // (db.insert/db.delete update it instantly across tabs), but until the first
345
- // server-confirmed sync settles we render the server-passed `initial` rows — so
346
- // there's no flash of an empty list on load. The policy gates reads on
347
- // `auth.tenantId == data.orgId`, so this is only ever this org's projects.
348
- function ProjectsList({
349
- orgId,
350
- initial,
351
- }: {
352
- orgId: string;
353
- initial: Project[];
354
- }) {
355
- const [name, setName] = useState("");
356
- const [desc, setDesc] = useState("");
357
- const [creating, setCreating] = useState(false);
358
- const [limitHit, setLimitHit] = useState(false);
359
- const [createError, setCreateError] = useState<string | null>(null);
360
- // Render the server-seeded `initial` rows on the server AND the client's
361
- // first paint, then swap to the live reactive query once mounted. Gating the
362
- // swap on a post-mount flag (rather than `loading`, which is already settled
363
- // during SSR) keeps the server HTML and the first client render identical —
364
- // no hydration mismatch, and still no empty-state flash.
365
- const [hydrated, setHydrated] = useState(false);
366
- useEffect(() => setHydrated(true), []);
367
- const { data, loading } = db.useQuery<Project>("Project");
368
- const rows =
369
- !hydrated || loading ? initial : data.filter((p) => p.orgId === orgId);
370
- const projects = rows
371
- .slice()
372
- .sort((a, b) => {
373
- // Active first, then newest first.
374
- const aa = (a.status ?? "active") === "archived" ? 1 : 0;
375
- const bb = (b.status ?? "active") === "archived" ? 1 : 0;
376
- if (aa !== bb) return aa - bb;
377
- return a.createdAt < b.createdAt ? 1 : -1;
378
- });
379
-
380
- // Creates go through the `createProject` function: it enforces the free
381
- // plan's cap on the server and answers LIMIT_REACHED at the cap, which
382
- // opens the upgrade dialog. The new row arrives through sync.
383
- async function add(e: React.FormEvent) {
384
- e.preventDefault();
385
- const value = name.trim();
386
- if (!value) return;
387
- const description = desc.trim();
388
- setCreating(true);
389
- setCreateError(null);
390
- try {
391
- await callFn("createProject", {
392
- orgId,
393
- name: value,
394
- ...(description ? { description } : {}),
395
- });
396
- setName("");
397
- setDesc("");
398
- } catch (err) {
399
- const msg = err instanceof Error ? err.message : String(err);
400
- if (/LIMIT_REACHED/.test(msg) || (err as { code?: string })?.code === "LIMIT_REACHED") {
401
- setLimitHit(true);
402
- } else {
403
- setCreateError(msg.replace(/^[A-Z_]+:\s*/, ""));
404
- }
405
- } finally {
406
- setCreating(false);
407
- }
408
- }
409
-
410
- return (
411
- <div className="space-y-5">
412
- <div className="rounded-xl border border-zinc-200 bg-white p-4">
413
- <form
414
- onSubmit={add}
415
- className="flex flex-col gap-2 sm:flex-row sm:items-center"
416
- >
417
- <input
418
- value={name}
419
- onChange={(e) => setName(e.target.value)}
420
- placeholder="Project name"
421
- aria-label="Project name"
422
- className={inputCls + " sm:flex-1"}
423
- />
424
- <input
425
- value={desc}
426
- onChange={(e) => setDesc(e.target.value)}
427
- placeholder="Short description (optional)"
428
- aria-label="Project description"
429
- className={inputCls + " sm:flex-1"}
430
- />
431
- <Button type="submit" size="sm" className="shrink-0" disabled={creating}>
432
- <Plus className="size-4" /> {creating ? "Creating…" : "New project"}
433
- </Button>
434
- </form>
435
- {createError ? (
436
- <p className="mt-2 text-xs text-red-600">{createError}</p>
437
- ) : (
438
- <p className="mt-2 text-xs text-zinc-400">
439
- Tenant-scoped + live — only this workspace&apos;s projects (enforced by
440
- policy), and every change syncs across tabs instantly.
441
- </p>
442
- )}
443
- </div>
444
-
445
- <UpgradeDialog open={limitHit} onClose={() => setLimitHit(false)} />
446
-
447
- {projects.length === 0 ? (
448
- <div className="flex flex-col items-center gap-2 rounded-xl border border-dashed border-zinc-300 py-14 text-center">
449
- <span className="flex size-11 items-center justify-center rounded-xl bg-zinc-50 text-zinc-400">
450
- <FolderPlus className="size-5" />
451
- </span>
452
- <p className="text-sm font-medium text-zinc-700">No projects yet</p>
453
- <p className="text-xs text-zinc-400">
454
- Create your first project above to get started.
455
- </p>
456
- </div>
457
- ) : (
458
- <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
459
- {projects.map((p) => (
460
- <ProjectCard key={p.id} p={p} />
461
- ))}
462
- </div>
463
- )}
464
- </div>
465
- );
466
- }
467
-
468
- // Shown when `createProject` answers LIMIT_REACHED. One action: go to
469
- // Billing, where the trial starts.
470
- function UpgradeDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
471
- const pro = planById("pro")!;
472
- return (
473
- <Dialog open={open} onOpenChange={(o) => !o && onClose()}>
474
- <DialogContent>
475
- <DialogHeader>
476
- <DialogTitle>You&apos;ve reached the free plan&apos;s limit</DialogTitle>
477
- <DialogDescription>
478
- Free workspaces can have {FREE_PROJECT_LIMIT} active projects. Archive one, or
479
- upgrade to Pro for unlimited projects. Pro starts with a {TRIAL_DAYS}-day free
480
- trial.
481
- </DialogDescription>
482
- </DialogHeader>
483
- <ul className="space-y-1.5 text-sm text-zinc-700">
484
- {pro.features.map((f) => (
485
- <li key={f} className="flex gap-2">
486
- <span className="text-zinc-900">✓</span> {f}
487
- </li>
488
- ))}
489
- </ul>
490
- <DialogFooter>
491
- <Button type="button" variant="outline" onClick={onClose}>
492
- Not now
493
- </Button>
494
- <a
495
- href="/dashboard/billing?upgrade=pro&interval=annual"
496
- 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"
497
- >
498
- {pro.cta}
499
- </a>
500
- </DialogFooter>
501
- </DialogContent>
502
- </Dialog>
503
- );
504
- }
505
-
506
- function IconBtn({
507
- label,
508
- danger,
509
- onClick,
510
- children,
511
- }: {
512
- label: string;
513
- danger?: boolean;
514
- onClick: () => void;
515
- children: React.ReactNode;
516
- }) {
517
- return (
518
- <button
519
- type="button"
520
- aria-label={label}
521
- title={label}
522
- onClick={onClick}
523
- className={
524
- "flex size-7 items-center justify-center rounded-md text-zinc-400 transition-colors hover:bg-zinc-100 " +
525
- (danger ? "hover:text-red-600" : "hover:text-zinc-700")
526
- }
527
- >
528
- {children}
529
- </button>
530
- );
531
- }
532
-
533
- // One project card. Archive + delete are optimistic client `db` writes (gated
534
- // by the tenant policy) that sync across tabs; editing details saves through the
535
- // updateProject server function. Editing swaps the card for an inline name +
536
- // description form.
537
- function ProjectCard({ p }: { p: Project }) {
538
- const archived = (p.status ?? "active") === "archived";
539
- const [editing, setEditing] = useState(false);
540
- const [name, setName] = useState(p.name);
541
- const [desc, setDesc] = useState(p.description ?? "");
542
-
543
- async function save(e: React.FormEvent) {
544
- e.preventDefault();
545
- const n = name.trim();
546
- if (!n) return;
547
- setEditing(false);
548
- // Saving details goes through the updateProject server function (server-side
549
- // validation + a workspace-membership re-check) rather than a bare
550
- // db.update — see functions/updateProject.ts. The reactive `db` still
551
- // re-renders this card the moment the write lands.
552
- await callFn("updateProject", {
553
- projectId: p.id,
554
- name: n,
555
- description: desc.trim() || undefined,
556
- });
557
- }
558
- function cancel() {
559
- setEditing(false);
560
- setName(p.name);
561
- setDesc(p.description ?? "");
562
- }
563
-
564
- if (editing) {
565
- return (
566
- <form
567
- onSubmit={save}
568
- className="flex flex-col gap-2 rounded-xl border border-zinc-300 bg-white p-4"
569
- >
570
- <input
571
- autoFocus
572
- value={name}
573
- onChange={(e) => setName(e.target.value)}
574
- aria-label="Project name"
575
- className={inputCls}
576
- />
577
- <textarea
578
- value={desc}
579
- onChange={(e) => setDesc(e.target.value)}
580
- rows={2}
581
- placeholder="Add a description…"
582
- aria-label="Project description"
583
- className={inputCls + " h-auto resize-none py-2"}
584
- />
585
- <div className="flex items-center gap-2">
586
- <Button type="submit" size="sm">
587
- <Check className="size-3.5" /> Save
588
- </Button>
589
- <button
590
- type="button"
591
- onClick={cancel}
592
- className="text-[13px] font-medium text-zinc-500 transition-colors hover:text-zinc-800"
593
- >
594
- Cancel
595
- </button>
596
- </div>
597
- </form>
598
- );
599
- }
600
-
601
- return (
602
- <div
603
- className={
604
- "flex flex-col rounded-xl border border-zinc-200 bg-white p-4 transition-colors hover:border-zinc-300 " +
605
- (archived ? "opacity-60" : "")
606
- }
607
- >
608
- <div className="flex items-start gap-3">
609
- <span
610
- className={
611
- "flex size-9 shrink-0 items-center justify-center rounded-lg text-sm font-semibold " +
612
- accentFor(p.id)
613
- }
614
- >
615
- {monogram(p.name)}
616
- </span>
617
- <div className="min-w-0 flex-1">
618
- <div className="truncate text-sm font-semibold text-zinc-900">
619
- {p.name}
620
- </div>
621
- <p
622
- className={
623
- "mt-0.5 line-clamp-2 text-xs " +
624
- (p.description ? "text-zinc-500" : "text-zinc-300")
625
- }
626
- >
627
- {p.description || "No description"}
628
- </p>
629
- </div>
630
- <div className="-mr-1 flex shrink-0 items-center">
631
- <IconBtn label="Edit project" onClick={() => setEditing(true)}>
632
- <Pencil className="size-3.5" />
633
- </IconBtn>
634
- <IconBtn
635
- label={archived ? "Unarchive project" : "Archive project"}
636
- onClick={() =>
637
- db.update("Project", p.id, {
638
- status: archived ? "active" : "archived",
639
- })
640
- }
641
- >
642
- {archived ? (
643
- <ArchiveRestore className="size-3.5" />
644
- ) : (
645
- <Archive className="size-3.5" />
646
- )}
647
- </IconBtn>
648
- <IconBtn
649
- label="Delete project"
650
- danger
651
- onClick={() => db.delete("Project", p.id)}
652
- >
653
- <Trash2 className="size-3.5" />
654
- </IconBtn>
655
- </div>
656
- </div>
657
- <div className="mt-4 flex items-center justify-between">
658
- <StatusPill status={p.status ?? "active"} />
659
- <span className="text-[11px] text-zinc-400">
660
- Created {fmtDate(p.createdAt)}
661
- </span>
662
- </div>
663
- </div>
81
+ </header>
82
+ <div className="px-5 py-4">{children}</div>
83
+ </section>
664
84
  );
665
85
  }
666
86
 
@@ -730,10 +150,17 @@ function MembersList({
730
150
  const [inviting, setInviting] = useState(false);
731
151
 
732
152
  async function load() {
733
- setMembers(await listOrgMembers(orgId));
153
+ const roster = await listOrgMembers(orgId);
154
+ setMembers(roster);
734
155
  // Pending invites are admin-gated server-side; only fetch when we'd be
735
- // allowed to see them (avoids a guaranteed 403 for plain members).
736
- if (canManage) setInvites(await listInvites(orgId));
156
+ // allowed to see them (avoids a guaranteed 403 for plain members). The
157
+ // invites endpoint also returns invites that were already accepted, so
158
+ // drop any whose email already belongs to a member.
159
+ if (canManage) {
160
+ const joined = new Set(roster.map((m) => (m.email ?? "").toLowerCase()));
161
+ const list = await listInvites(orgId);
162
+ setInvites(list.filter((i) => !joined.has(i.email.toLowerCase())));
163
+ }
737
164
  }
738
165
  useEffect(() => {
739
166
  void load();
@@ -793,7 +220,7 @@ function MembersList({
793
220
  }
794
221
 
795
222
  return (
796
- <div className="space-y-6">
223
+ <div className="mx-auto max-w-3xl space-y-6">
797
224
  <Card title="Members" action={members ? <Count n={members.length} /> : null}>
798
225
  {canManage && (
799
226
  <form onSubmit={invite} className="flex items-center gap-2">
@@ -823,21 +250,18 @@ function MembersList({
823
250
  </li>
824
251
  ))
825
252
  : members.map((m) => {
826
- const label = m.name || m.email || "Unknown member";
827
- const initial = (label.trim()[0] || "?").toUpperCase();
253
+ const label = personName({ name: m.name, email: m.email }) || "Unknown member";
828
254
  const isMe = m.user_id === currentUserId;
829
255
  return (
830
256
  <li
831
257
  key={m.user_id}
832
258
  className="flex items-center gap-3 py-2.5"
833
259
  >
834
- <span className="flex size-8 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-[12px] font-semibold text-zinc-600">
835
- {initial}
836
- </span>
260
+ <Avatar id={m.user_id} name={label} size="md" />
837
261
  <div className="min-w-0 flex-1">
838
262
  <div className="flex items-center gap-2">
839
263
  <span className="truncate text-sm font-medium text-zinc-900">
840
- {m.name || m.email || "Unknown member"}
264
+ {label}
841
265
  </span>
842
266
  {isMe && (
843
267
  <span className="rounded bg-zinc-100 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500">
@@ -845,7 +269,7 @@ function MembersList({
845
269
  </span>
846
270
  )}
847
271
  </div>
848
- {m.name && m.email && m.name !== m.email && (
272
+ {m.email && label !== m.email && (
849
273
  <div className="truncate text-xs text-zinc-500">
850
274
  {m.email}
851
275
  </div>
@@ -897,7 +321,7 @@ function MembersList({
897
321
  {invites.map((inv) => (
898
322
  <li key={inv.id} className="flex items-center gap-3 py-2.5">
899
323
  <span className="flex size-8 shrink-0 items-center justify-center rounded-full border border-dashed border-zinc-300 text-zinc-400">
900
- <MailIcon />
324
+ <Mail className="size-3.5" strokeWidth={1.75} />
901
325
  </span>
902
326
  <div className="min-w-0 flex-1">
903
327
  <div className="truncate text-sm font-medium text-zinc-900">
@@ -924,15 +348,6 @@ function MembersList({
924
348
  );
925
349
  }
926
350
 
927
- function MailIcon() {
928
- return (
929
- <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
930
- <rect x="3" y="5" width="18" height="14" rx="2" />
931
- <path d="m3 7 9 6 9-6" />
932
- </svg>
933
- );
934
- }
935
-
936
351
  // PendingInvite.expires_at is unix SECONDS; formatDate() wants an ISO string.
937
352
  function unixToIso(sec: number) {
938
353
  return new Date(sec * 1000).toISOString();
@@ -1040,7 +455,7 @@ function AccountSettings({ me }: { me: AccountInfo }) {
1040
455
  }
1041
456
 
1042
457
  return (
1043
- <div className="mt-6 max-w-2xl space-y-6">
458
+ <div className="mx-auto mt-6 max-w-2xl space-y-6">
1044
459
  <Card title="Your account">
1045
460
  <form onSubmit={saveName} className="space-y-3">
1046
461
  <label className="block">
@@ -1166,7 +581,7 @@ function SettingsView({
1166
581
  }
1167
582
 
1168
583
  return (
1169
- <div className="max-w-2xl space-y-6">
584
+ <div className="mx-auto max-w-2xl space-y-6">
1170
585
  <Card title="Workspace">
1171
586
  <form onSubmit={rename} className="space-y-3">
1172
587
  <label className="block">
@@ -1430,7 +845,7 @@ function BillingView({
1430
845
  }
1431
846
 
1432
847
  return (
1433
- <div className="max-w-2xl space-y-6">
848
+ <div className="mx-auto max-w-2xl space-y-6">
1434
849
  <Card title="Plan">
1435
850
  <div className="flex items-center justify-between">
1436
851
  <div>