@pylonsync/create-pylon 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (696) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/_root/AGENTS.md +1 -1
  4. package/templates/agency/.env.example +7 -0
  5. package/templates/agency/AGENTS.md +1 -1
  6. package/templates/agency/README.md +28 -3
  7. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  8. package/templates/agency/app/(marketing)/page.tsx +3 -3
  9. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  10. package/templates/agency/app/auth-form.tsx +4 -4
  11. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  12. package/templates/agency/app/dashboard/page.tsx +14 -16
  13. package/templates/agency/app/icon.svg +3 -0
  14. package/templates/agency/app/login/page.tsx +12 -14
  15. package/templates/agency/components/marketing.tsx +12 -5
  16. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  17. package/templates/agency/lib/agency.ts +22 -0
  18. package/templates/agency/lib/demo.ts +118 -0
  19. package/templates/agency/lib/site.config.ts +19 -13
  20. package/templates/agency/public/favicon.ico +0 -0
  21. package/templates/agency/tests/agency.test.ts +57 -0
  22. package/templates/ai-chat/.env.example +13 -27
  23. package/templates/ai-chat/AGENTS.md +1 -1
  24. package/templates/ai-chat/README.md +53 -60
  25. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  26. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  27. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  28. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  29. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  30. package/templates/ai-chat/app/auth-form.tsx +45 -38
  31. package/templates/ai-chat/app/error.tsx +9 -12
  32. package/templates/ai-chat/app/globals.css +464 -102
  33. package/templates/ai-chat/app/layout.tsx +11 -19
  34. package/templates/ai-chat/app/login/page.tsx +39 -24
  35. package/templates/ai-chat/app.ts +118 -43
  36. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  37. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  38. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  39. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  40. package/templates/ai-chat/components/chat/message.tsx +183 -0
  41. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  42. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  43. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  44. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  45. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  46. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  47. package/templates/ai-chat/functions/createConversation.ts +28 -0
  48. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  49. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  50. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  51. package/templates/ai-chat/functions/respond.ts +88 -0
  52. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  53. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  54. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  55. package/templates/ai-chat/lib/chat.ts +277 -0
  56. package/templates/ai-chat/lib/claim.ts +15 -0
  57. package/templates/ai-chat/lib/examples.ts +179 -0
  58. package/templates/ai-chat/lib/highlight.ts +107 -0
  59. package/templates/ai-chat/lib/markdown.ts +317 -0
  60. package/templates/ai-chat/lib/provider.ts +38 -0
  61. package/templates/ai-chat/lib/site.config.ts +55 -39
  62. package/templates/ai-chat/tests/chat.test.ts +174 -0
  63. package/templates/ai-chat/tests/components.test.tsx +57 -0
  64. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  65. package/templates/ai-chat/tests/provider.test.ts +32 -0
  66. package/templates/ai-studio/AGENTS.md +1 -1
  67. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  68. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  69. package/templates/backend/consumer/apps/api/app.ts +166 -0
  70. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  71. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  72. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  73. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  74. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  75. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  76. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  77. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  78. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  79. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  80. package/templates/backend/consumer/apps/api/package.json +8 -2
  81. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  85. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  86. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  87. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  88. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  89. package/templates/barebones/AGENTS.md +1 -1
  90. package/templates/chat/AGENTS.md +1 -1
  91. package/templates/chat/README.md +43 -21
  92. package/templates/chat/app/chat-client.tsx +495 -85
  93. package/templates/chat/app/error.tsx +1 -1
  94. package/templates/chat/app/globals.css +170 -54
  95. package/templates/chat/app/layout.tsx +12 -9
  96. package/templates/chat/app/not-found.tsx +4 -2
  97. package/templates/chat/app/page.tsx +9 -19
  98. package/templates/chat/app.ts +96 -17
  99. package/templates/chat/components/chat/avatar.tsx +45 -0
  100. package/templates/chat/components/chat/composer.tsx +110 -0
  101. package/templates/chat/components/chat/message-list.tsx +285 -0
  102. package/templates/chat/components/chat/sidebar.tsx +190 -0
  103. package/templates/chat/functions/joinChat.ts +83 -0
  104. package/templates/chat/functions/renameMember.ts +25 -0
  105. package/templates/chat/lib/chat.ts +188 -0
  106. package/templates/chat/lib/seed.ts +78 -0
  107. package/templates/chat/tests/chat.test.ts +159 -0
  108. package/templates/chat/tests/composer.test.tsx +54 -0
  109. package/templates/consumer/AGENTS.md +1 -1
  110. package/templates/consumer/README.md +3 -3
  111. package/templates/consumer/app/globals.css +10 -3
  112. package/templates/consumer/app/icon.svg +1 -0
  113. package/templates/consumer/components/social/avatar.tsx +25 -11
  114. package/templates/consumer/functions/seedFeed.ts +3 -3
  115. package/templates/consumer/lib/seed.ts +43 -85
  116. package/templates/consumer/public/favicon.ico +0 -0
  117. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  118. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  119. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  120. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  121. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  122. package/templates/consumer/public/images/posts/run.jpg +0 -0
  123. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  124. package/templates/consumer/tests/seed.test.ts +39 -0
  125. package/templates/creator/.env.example +7 -0
  126. package/templates/creator/AGENTS.md +1 -1
  127. package/templates/creator/README.md +24 -5
  128. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  129. package/templates/creator/app/auth-form.tsx +7 -7
  130. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  131. package/templates/creator/app/dashboard/page.tsx +18 -18
  132. package/templates/creator/app/icon.svg +2 -0
  133. package/templates/creator/app/login/page.tsx +14 -14
  134. package/templates/creator/app.ts +4 -4
  135. package/templates/creator/functions/seedDemo.ts +32 -0
  136. package/templates/creator/functions/subscribe.ts +4 -15
  137. package/templates/creator/lib/demo.ts +94 -0
  138. package/templates/creator/lib/site.config.ts +15 -10
  139. package/templates/creator/lib/stats.ts +35 -0
  140. package/templates/creator/public/favicon.ico +0 -0
  141. package/templates/creator/tests/counter.test.ts +92 -0
  142. package/templates/creator/tests/live-count.test.tsx +45 -0
  143. package/templates/crm/AGENTS.md +1 -1
  144. package/templates/crm/README.md +22 -1
  145. package/templates/crm/app/apple-icon.png +0 -0
  146. package/templates/crm/app/companies/companies-view.tsx +32 -14
  147. package/templates/crm/app/companies/page.tsx +2 -1
  148. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  149. package/templates/crm/app/contacts/page.tsx +2 -1
  150. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  151. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  152. package/templates/crm/app/globals.css +29 -14
  153. package/templates/crm/app/icon.svg +1 -0
  154. package/templates/crm/app/layout.tsx +7 -5
  155. package/templates/crm/app/page.tsx +2 -1
  156. package/templates/crm/app/pipeline-view.tsx +5 -5
  157. package/templates/crm/app/workspace.tsx +29 -17
  158. package/templates/crm/app.ts +5 -5
  159. package/templates/crm/components/activity-timeline.tsx +14 -4
  160. package/templates/crm/components/app-shell.tsx +94 -0
  161. package/templates/crm/components/auth-form.tsx +35 -5
  162. package/templates/crm/components/avatar.tsx +5 -1
  163. package/templates/crm/components/board-skeleton.tsx +5 -5
  164. package/templates/crm/components/brand-mark.tsx +25 -0
  165. package/templates/crm/components/command-palette.tsx +17 -17
  166. package/templates/crm/components/data-table.tsx +97 -36
  167. package/templates/crm/components/deal-card.tsx +23 -27
  168. package/templates/crm/components/filter-tabs.tsx +101 -0
  169. package/templates/crm/components/metrics-bar.tsx +6 -6
  170. package/templates/crm/components/nav.tsx +15 -0
  171. package/templates/crm/components/page-header.tsx +43 -7
  172. package/templates/crm/components/pipeline-board.tsx +16 -10
  173. package/templates/crm/components/require-auth.tsx +24 -26
  174. package/templates/crm/components/sidebar.tsx +54 -49
  175. package/templates/crm/components/stage-badge.tsx +4 -2
  176. package/templates/crm/components/ui/button.tsx +5 -4
  177. package/templates/crm/components/ui/dialog.tsx +1 -1
  178. package/templates/crm/components/ui/input.tsx +1 -1
  179. package/templates/crm/components/ui/select.tsx +1 -1
  180. package/templates/crm/components/ui/textarea.tsx +1 -1
  181. package/templates/crm/functions/seedWorkspace.ts +32 -17
  182. package/templates/crm/lib/brand.ts +9 -0
  183. package/templates/crm/lib/demo-team.ts +51 -0
  184. package/templates/crm/lib/pipeline.ts +37 -0
  185. package/templates/crm/lib/search.ts +4 -3
  186. package/templates/crm/lib/seed.ts +53 -8
  187. package/templates/crm/public/favicon.ico +0 -0
  188. package/templates/crm/tests/components.test.tsx +5 -5
  189. package/templates/crm/tests/demo-team.test.ts +18 -0
  190. package/templates/crm/tests/pipeline.test.ts +59 -3
  191. package/templates/directory/.env.example +7 -0
  192. package/templates/directory/AGENTS.md +1 -1
  193. package/templates/directory/README.md +22 -3
  194. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  195. package/templates/directory/app/(marketing)/page.tsx +6 -4
  196. package/templates/directory/app/auth-form.tsx +4 -4
  197. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  198. package/templates/directory/app/dashboard/page.tsx +16 -14
  199. package/templates/directory/app/icon.svg +4 -0
  200. package/templates/directory/app/login/page.tsx +16 -15
  201. package/templates/directory/functions/seedDemo.ts +43 -0
  202. package/templates/directory/lib/demo.ts +145 -0
  203. package/templates/directory/lib/directory.ts +27 -0
  204. package/templates/directory/lib/site.config.ts +21 -16
  205. package/templates/directory/public/favicon.ico +0 -0
  206. package/templates/directory/tests/directory.test.ts +46 -0
  207. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  208. package/templates/expo/consumer/apps/expo/app.json +9 -1
  209. package/templates/expo/consumer/apps/expo/package.json +2 -1
  210. package/templates/helpdesk/AGENTS.md +1 -1
  211. package/templates/helpdesk/README.md +21 -0
  212. package/templates/helpdesk/app/apple-icon.png +0 -0
  213. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  214. package/templates/helpdesk/app/customers/page.tsx +2 -1
  215. package/templates/helpdesk/app/globals.css +29 -14
  216. package/templates/helpdesk/app/icon.svg +1 -0
  217. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  218. package/templates/helpdesk/app/layout.tsx +7 -5
  219. package/templates/helpdesk/app/page.tsx +4 -7
  220. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  221. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  222. package/templates/helpdesk/app/workspace.tsx +33 -22
  223. package/templates/helpdesk/app.ts +2 -2
  224. package/templates/helpdesk/components/app-shell.tsx +94 -0
  225. package/templates/helpdesk/components/auth-form.tsx +35 -5
  226. package/templates/helpdesk/components/avatar.tsx +5 -1
  227. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  228. package/templates/helpdesk/components/command-palette.tsx +17 -17
  229. package/templates/helpdesk/components/data-table.tsx +97 -36
  230. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  231. package/templates/helpdesk/components/nav.tsx +14 -0
  232. package/templates/helpdesk/components/page-header.tsx +43 -7
  233. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  234. package/templates/helpdesk/components/require-auth.tsx +24 -26
  235. package/templates/helpdesk/components/sidebar.tsx +54 -48
  236. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  237. package/templates/helpdesk/components/thread.tsx +7 -7
  238. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  239. package/templates/helpdesk/components/ui/button.tsx +5 -4
  240. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  241. package/templates/helpdesk/components/ui/input.tsx +1 -1
  242. package/templates/helpdesk/components/ui/select.tsx +1 -1
  243. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  244. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  245. package/templates/helpdesk/lib/brand.ts +9 -0
  246. package/templates/helpdesk/lib/demo-team.ts +51 -0
  247. package/templates/helpdesk/lib/search.ts +4 -3
  248. package/templates/helpdesk/lib/seed.ts +43 -2
  249. package/templates/helpdesk/lib/tickets.ts +68 -20
  250. package/templates/helpdesk/public/favicon.ico +0 -0
  251. package/templates/helpdesk/tests/components.test.tsx +10 -0
  252. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  253. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  254. package/templates/imessage/.env.example +49 -0
  255. package/templates/imessage/AGENTS.md +153 -0
  256. package/templates/imessage/CLAUDE.md +7 -0
  257. package/templates/imessage/README.md +187 -0
  258. package/templates/imessage/app/console.tsx +382 -0
  259. package/templates/imessage/app/contacts/page.tsx +13 -0
  260. package/templates/imessage/app/error.tsx +16 -0
  261. package/templates/imessage/app/gate.tsx +31 -0
  262. package/templates/imessage/app/globals.css +183 -0
  263. package/templates/imessage/app/layout.tsx +17 -0
  264. package/templates/imessage/app/login/login-form.tsx +115 -0
  265. package/templates/imessage/app/login/page.tsx +30 -0
  266. package/templates/imessage/app/not-found.tsx +13 -0
  267. package/templates/imessage/app/page.tsx +14 -0
  268. package/templates/imessage/app/robots.ts +6 -0
  269. package/templates/imessage/app/setup/page.tsx +13 -0
  270. package/templates/imessage/app/sign-out.tsx +20 -0
  271. package/templates/imessage/app.ts +299 -0
  272. package/templates/imessage/bunfig +4 -0
  273. package/templates/imessage/components/avatar.tsx +46 -0
  274. package/templates/imessage/components/contact-panel.tsx +263 -0
  275. package/templates/imessage/components/contacts-view.tsx +172 -0
  276. package/templates/imessage/components/conversation-list.tsx +129 -0
  277. package/templates/imessage/components/nav-rail.tsx +67 -0
  278. package/templates/imessage/components/setup-view.tsx +408 -0
  279. package/templates/imessage/components/thread.tsx +229 -0
  280. package/templates/imessage/components/ui/badge.tsx +37 -0
  281. package/templates/imessage/components/ui/button.tsx +56 -0
  282. package/templates/imessage/components/ui/card.tsx +90 -0
  283. package/templates/imessage/components/ui/dialog.tsx +110 -0
  284. package/templates/imessage/components/ui/input.tsx +30 -0
  285. package/templates/imessage/components/ui/label.tsx +21 -0
  286. package/templates/imessage/components/ui/select.tsx +39 -0
  287. package/templates/imessage/components/ui/table.tsx +124 -0
  288. package/templates/imessage/components/ui/textarea.tsx +24 -0
  289. package/templates/imessage/components.json +20 -0
  290. package/templates/imessage/functions/answerNow.ts +34 -0
  291. package/templates/imessage/functions/assistant.ts +96 -0
  292. package/templates/imessage/functions/beginTurn.ts +101 -0
  293. package/templates/imessage/functions/cancelReminder.ts +13 -0
  294. package/templates/imessage/functions/claimOutbound.ts +44 -0
  295. package/templates/imessage/functions/clearDemo.ts +18 -0
  296. package/templates/imessage/functions/deleteNote.ts +11 -0
  297. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  298. package/templates/imessage/functions/finishTurn.ts +84 -0
  299. package/templates/imessage/functions/fireReminder.ts +42 -0
  300. package/templates/imessage/functions/ingestInbound.ts +22 -0
  301. package/templates/imessage/functions/ownerAccount.ts +13 -0
  302. package/templates/imessage/functions/processTurn.ts +74 -0
  303. package/templates/imessage/functions/relayApply.ts +102 -0
  304. package/templates/imessage/functions/relaySync.ts +63 -0
  305. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  306. package/templates/imessage/functions/resolveTurn.ts +25 -0
  307. package/templates/imessage/functions/saveContact.ts +27 -0
  308. package/templates/imessage/functions/seedDemo.ts +70 -0
  309. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  310. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  311. package/templates/imessage/functions/setContactStatus.ts +17 -0
  312. package/templates/imessage/functions/settleOutbound.ts +35 -0
  313. package/templates/imessage/functions/setupStatus.ts +33 -0
  314. package/templates/imessage/functions/sweep.ts +58 -0
  315. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  316. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  317. package/templates/imessage/functions/toolListNotes.ts +16 -0
  318. package/templates/imessage/functions/toolListReminders.ts +17 -0
  319. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  320. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  321. package/templates/imessage/functions/updateSettings.ts +48 -0
  322. package/templates/imessage/gitignore +11 -0
  323. package/templates/imessage/lib/agent-call.ts +251 -0
  324. package/templates/imessage/lib/assistant.ts +74 -0
  325. package/templates/imessage/lib/chunk.ts +135 -0
  326. package/templates/imessage/lib/demo.ts +68 -0
  327. package/templates/imessage/lib/format.ts +170 -0
  328. package/templates/imessage/lib/gate.ts +104 -0
  329. package/templates/imessage/lib/handles.ts +67 -0
  330. package/templates/imessage/lib/provider.ts +38 -0
  331. package/templates/imessage/lib/relay-protocol.ts +95 -0
  332. package/templates/imessage/lib/reminders.ts +41 -0
  333. package/templates/imessage/lib/secrets.ts +59 -0
  334. package/templates/imessage/lib/sendblue.ts +173 -0
  335. package/templates/imessage/lib/store.ts +407 -0
  336. package/templates/imessage/lib/transport.ts +135 -0
  337. package/templates/imessage/lib/utils.ts +10 -0
  338. package/templates/imessage/package.json +40 -0
  339. package/templates/imessage/relay/.env.example +14 -0
  340. package/templates/imessage/relay/applescript.ts +70 -0
  341. package/templates/imessage/relay/chatdb.ts +165 -0
  342. package/templates/imessage/relay/client.ts +41 -0
  343. package/templates/imessage/relay/main.ts +137 -0
  344. package/templates/imessage/relay/state.ts +35 -0
  345. package/templates/imessage/tests/agent-call.test.ts +112 -0
  346. package/templates/imessage/tests/applescript.test.ts +88 -0
  347. package/templates/imessage/tests/chatdb.test.ts +139 -0
  348. package/templates/imessage/tests/chunk.test.ts +61 -0
  349. package/templates/imessage/tests/fake-ctx.ts +88 -0
  350. package/templates/imessage/tests/ingest.test.ts +141 -0
  351. package/templates/imessage/tests/lib.test.ts +99 -0
  352. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  353. package/templates/imessage/tests/secrets.test.ts +36 -0
  354. package/templates/imessage/tests/sendblue.test.ts +143 -0
  355. package/templates/imessage/tests/setup.ts +5 -0
  356. package/templates/imessage/tsconfig.json +18 -0
  357. package/templates/inventory/AGENTS.md +1 -1
  358. package/templates/inventory/README.md +21 -0
  359. package/templates/inventory/app/apple-icon.png +0 -0
  360. package/templates/inventory/app/globals.css +29 -14
  361. package/templates/inventory/app/icon.svg +1 -0
  362. package/templates/inventory/app/layout.tsx +7 -5
  363. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  364. package/templates/inventory/app/movements/page.tsx +2 -1
  365. package/templates/inventory/app/page.tsx +4 -6
  366. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  367. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  368. package/templates/inventory/app/products-view.tsx +74 -33
  369. package/templates/inventory/app/workspace.tsx +34 -16
  370. package/templates/inventory/app.ts +10 -2
  371. package/templates/inventory/components/app-shell.tsx +94 -0
  372. package/templates/inventory/components/auth-form.tsx +35 -5
  373. package/templates/inventory/components/avatar.tsx +5 -1
  374. package/templates/inventory/components/brand-mark.tsx +25 -0
  375. package/templates/inventory/components/category-tile.tsx +49 -0
  376. package/templates/inventory/components/command-palette.tsx +17 -17
  377. package/templates/inventory/components/data-table.tsx +97 -36
  378. package/templates/inventory/components/filter-tabs.tsx +101 -0
  379. package/templates/inventory/components/nav.tsx +14 -0
  380. package/templates/inventory/components/page-header.tsx +43 -7
  381. package/templates/inventory/components/require-auth.tsx +24 -26
  382. package/templates/inventory/components/sidebar.tsx +54 -48
  383. package/templates/inventory/components/stock-level.tsx +30 -14
  384. package/templates/inventory/components/summary-bar.tsx +8 -15
  385. package/templates/inventory/components/ui/button.tsx +5 -4
  386. package/templates/inventory/components/ui/dialog.tsx +1 -1
  387. package/templates/inventory/components/ui/input.tsx +1 -1
  388. package/templates/inventory/components/ui/select.tsx +1 -1
  389. package/templates/inventory/components/ui/textarea.tsx +1 -1
  390. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  391. package/templates/inventory/lib/brand.ts +9 -0
  392. package/templates/inventory/lib/demo-team.ts +51 -0
  393. package/templates/inventory/lib/search.ts +4 -3
  394. package/templates/inventory/lib/seed.ts +29 -2
  395. package/templates/inventory/lib/stock.ts +32 -0
  396. package/templates/inventory/public/favicon.ico +0 -0
  397. package/templates/inventory/tests/demo-team.test.ts +18 -0
  398. package/templates/inventory/tests/stock.test.ts +29 -0
  399. package/templates/invoices/AGENTS.md +1 -1
  400. package/templates/invoices/README.md +16 -0
  401. package/templates/invoices/app/apple-icon.png +0 -0
  402. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  403. package/templates/invoices/app/clients/page.tsx +2 -1
  404. package/templates/invoices/app/globals.css +29 -14
  405. package/templates/invoices/app/icon.svg +1 -0
  406. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  407. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  408. package/templates/invoices/app/invoices-view.tsx +81 -32
  409. package/templates/invoices/app/layout.tsx +7 -5
  410. package/templates/invoices/app/page.tsx +4 -6
  411. package/templates/invoices/app/workspace.tsx +26 -16
  412. package/templates/invoices/app.ts +2 -2
  413. package/templates/invoices/components/app-shell.tsx +94 -0
  414. package/templates/invoices/components/auth-form.tsx +35 -5
  415. package/templates/invoices/components/avatar.tsx +5 -1
  416. package/templates/invoices/components/brand-mark.tsx +26 -0
  417. package/templates/invoices/components/command-palette.tsx +17 -17
  418. package/templates/invoices/components/data-table.tsx +97 -36
  419. package/templates/invoices/components/filter-tabs.tsx +101 -0
  420. package/templates/invoices/components/line-items.tsx +19 -14
  421. package/templates/invoices/components/nav.tsx +14 -0
  422. package/templates/invoices/components/page-header.tsx +43 -7
  423. package/templates/invoices/components/require-auth.tsx +24 -26
  424. package/templates/invoices/components/sidebar.tsx +54 -48
  425. package/templates/invoices/components/status-badge.tsx +7 -7
  426. package/templates/invoices/components/summary-bar.tsx +5 -5
  427. package/templates/invoices/components/ui/button.tsx +5 -4
  428. package/templates/invoices/components/ui/dialog.tsx +1 -1
  429. package/templates/invoices/components/ui/input.tsx +1 -1
  430. package/templates/invoices/components/ui/select.tsx +1 -1
  431. package/templates/invoices/components/ui/textarea.tsx +1 -1
  432. package/templates/invoices/functions/recordPayment.ts +3 -0
  433. package/templates/invoices/lib/billing.ts +40 -0
  434. package/templates/invoices/lib/brand.ts +9 -0
  435. package/templates/invoices/lib/search.ts +4 -3
  436. package/templates/invoices/public/favicon.ico +0 -0
  437. package/templates/invoices/tests/billing.test.ts +27 -0
  438. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  444. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  445. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  446. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  447. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  448. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  449. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  450. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  451. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  452. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  453. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  454. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  455. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  456. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  457. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  458. package/templates/local-service/.env.example +7 -0
  459. package/templates/local-service/AGENTS.md +1 -1
  460. package/templates/local-service/README.md +26 -2
  461. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  462. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  463. package/templates/local-service/app/auth-form.tsx +8 -8
  464. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  465. package/templates/local-service/app/dashboard/page.tsx +14 -14
  466. package/templates/local-service/app/icon.svg +2 -0
  467. package/templates/local-service/app/login/page.tsx +15 -15
  468. package/templates/local-service/functions/seedDemo.ts +49 -0
  469. package/templates/local-service/lib/demo.ts +135 -0
  470. package/templates/local-service/lib/hours.ts +44 -0
  471. package/templates/local-service/lib/site.config.ts +8 -3
  472. package/templates/local-service/lib/slots.ts +3 -0
  473. package/templates/local-service/public/favicon.ico +0 -0
  474. package/templates/local-service/tests/booking.test.ts +104 -0
  475. package/templates/marketplace/AGENTS.md +1 -1
  476. package/templates/marketplace/README.md +23 -15
  477. package/templates/marketplace/app/globals.css +172 -114
  478. package/templates/marketplace/app/layout.tsx +75 -30
  479. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  480. package/templates/marketplace/app/me/page.tsx +4 -4
  481. package/templates/marketplace/app/page.tsx +221 -298
  482. package/templates/marketplace/app/sell/page.tsx +8 -11
  483. package/templates/marketplace/app.ts +32 -7
  484. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  485. package/templates/marketplace/client/AuthNav.tsx +24 -16
  486. package/templates/marketplace/client/ListingCard.tsx +118 -0
  487. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  488. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  489. package/templates/marketplace/client/LoginCard.tsx +114 -136
  490. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  491. package/templates/marketplace/client/MyMarket.tsx +371 -185
  492. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  493. package/templates/marketplace/client/SellForm.tsx +271 -192
  494. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  495. package/templates/marketplace/client/WatchButton.tsx +16 -16
  496. package/templates/marketplace/client/market.ts +61 -14
  497. package/templates/marketplace/functions/seedMarket.ts +143 -36
  498. package/templates/marketplace/functions/seedOffers.ts +101 -0
  499. package/templates/marketplace/lib/catalog.ts +72 -1
  500. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  501. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  502. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  503. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  504. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  505. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  506. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  507. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  508. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  509. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  510. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  511. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  512. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  513. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  514. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  515. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  516. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  517. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  518. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  519. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  520. package/templates/marketplace/tests/example.test.ts +64 -2
  521. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  522. package/templates/projects/AGENTS.md +1 -1
  523. package/templates/projects/README.md +24 -3
  524. package/templates/projects/app/apple-icon.png +0 -0
  525. package/templates/projects/app/clients/clients-view.tsx +20 -6
  526. package/templates/projects/app/clients/page.tsx +2 -1
  527. package/templates/projects/app/globals.css +29 -14
  528. package/templates/projects/app/icon.svg +1 -0
  529. package/templates/projects/app/layout.tsx +7 -5
  530. package/templates/projects/app/page.tsx +3 -5
  531. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  532. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  533. package/templates/projects/app/projects-view.tsx +105 -52
  534. package/templates/projects/app/workspace.tsx +41 -20
  535. package/templates/projects/app.ts +2 -2
  536. package/templates/projects/components/app-shell.tsx +94 -0
  537. package/templates/projects/components/auth-form.tsx +35 -5
  538. package/templates/projects/components/avatar.tsx +5 -1
  539. package/templates/projects/components/board-skeleton.tsx +3 -3
  540. package/templates/projects/components/brand-mark.tsx +25 -0
  541. package/templates/projects/components/command-palette.tsx +17 -17
  542. package/templates/projects/components/data-table.tsx +97 -36
  543. package/templates/projects/components/filter-tabs.tsx +101 -0
  544. package/templates/projects/components/meter.tsx +48 -0
  545. package/templates/projects/components/nav.tsx +14 -0
  546. package/templates/projects/components/page-header.tsx +43 -7
  547. package/templates/projects/components/require-auth.tsx +24 -26
  548. package/templates/projects/components/sidebar.tsx +54 -48
  549. package/templates/projects/components/summary-bar.tsx +49 -0
  550. package/templates/projects/components/task-board.tsx +10 -9
  551. package/templates/projects/components/time-dialog.tsx +29 -5
  552. package/templates/projects/components/ui/button.tsx +5 -4
  553. package/templates/projects/components/ui/dialog.tsx +1 -1
  554. package/templates/projects/components/ui/input.tsx +1 -1
  555. package/templates/projects/components/ui/select.tsx +1 -1
  556. package/templates/projects/components/ui/textarea.tsx +1 -1
  557. package/templates/projects/functions/seedWorkspace.ts +26 -12
  558. package/templates/projects/lib/brand.ts +9 -0
  559. package/templates/projects/lib/demo-team.ts +51 -0
  560. package/templates/projects/lib/search.ts +4 -3
  561. package/templates/projects/lib/seed.ts +35 -1
  562. package/templates/projects/lib/work.ts +63 -0
  563. package/templates/projects/public/favicon.ico +0 -0
  564. package/templates/projects/tests/demo-team.test.ts +18 -0
  565. package/templates/projects/tests/work.test.ts +65 -0
  566. package/templates/restaurant/.env.example +7 -0
  567. package/templates/restaurant/AGENTS.md +1 -1
  568. package/templates/restaurant/README.md +29 -3
  569. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  570. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  571. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  572. package/templates/restaurant/app/auth-form.tsx +8 -8
  573. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  574. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  575. package/templates/restaurant/app/icon.svg +4 -0
  576. package/templates/restaurant/app/login/page.tsx +15 -15
  577. package/templates/restaurant/functions/seedDemo.ts +44 -0
  578. package/templates/restaurant/lib/demo.ts +144 -0
  579. package/templates/restaurant/lib/hours.ts +45 -0
  580. package/templates/restaurant/lib/site.config.ts +3 -3
  581. package/templates/restaurant/public/favicon.ico +0 -0
  582. package/templates/restaurant/tests/reservations.test.ts +67 -0
  583. package/templates/saas/AGENTS.md +1 -1
  584. package/templates/saas/README.md +54 -35
  585. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  586. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  587. package/templates/saas/app/(auth)/layout.tsx +27 -33
  588. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  589. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  590. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  591. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  592. package/templates/saas/app/(marketing)/page.tsx +133 -372
  593. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  594. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  595. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  596. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  597. package/templates/saas/app/dashboard/layout.tsx +27 -13
  598. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  599. package/templates/saas/app/dashboard/page.tsx +29 -35
  600. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  601. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  602. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  603. package/templates/saas/app/globals.css +5 -4
  604. package/templates/saas/app/icon.svg +1 -0
  605. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  606. package/templates/saas/app/onboarding/page.tsx +6 -2
  607. package/templates/saas/app.ts +68 -13
  608. package/templates/saas/components/dashboard-shell.tsx +212 -132
  609. package/templates/saas/components/marketing.tsx +137 -274
  610. package/templates/saas/components/pricing-table.tsx +12 -11
  611. package/templates/saas/components/setup-checklist.tsx +31 -29
  612. package/templates/saas/components/task-ui.tsx +199 -0
  613. package/templates/saas/functions/createProject.ts +7 -13
  614. package/templates/saas/functions/createTask.ts +67 -0
  615. package/templates/saas/functions/deleteProject.ts +17 -0
  616. package/templates/saas/functions/deleteTask.ts +14 -0
  617. package/templates/saas/functions/seedWorkspace.ts +46 -0
  618. package/templates/saas/functions/setProjectStatus.ts +41 -0
  619. package/templates/saas/functions/updateProfile.ts +13 -0
  620. package/templates/saas/functions/updateProject.ts +4 -5
  621. package/templates/saas/functions/updateTask.ts +88 -0
  622. package/templates/saas/lib/members.ts +53 -0
  623. package/templates/saas/lib/names.ts +53 -0
  624. package/templates/saas/lib/plans.ts +11 -0
  625. package/templates/saas/lib/sample-workspace.ts +117 -0
  626. package/templates/saas/lib/site.config.ts +200 -463
  627. package/templates/saas/lib/tasks.ts +101 -0
  628. package/templates/saas/lib/use-live.ts +25 -0
  629. package/templates/saas/public/favicon.ico +0 -0
  630. package/templates/saas/public/screenshots/board.png +0 -0
  631. package/templates/saas/public/screenshots/members.png +0 -0
  632. package/templates/saas/public/screenshots/overview.png +0 -0
  633. package/templates/saas/public/screenshots/projects.png +0 -0
  634. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  635. package/templates/saas/tests/names.test.ts +35 -0
  636. package/templates/saas/tests/plans.test.ts +12 -0
  637. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  638. package/templates/saas/tests/tasks.test.ts +81 -0
  639. package/templates/shop/AGENTS.md +1 -1
  640. package/templates/shop/README.md +25 -13
  641. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  642. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  643. package/templates/shop/app/(marketing)/page.tsx +120 -59
  644. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  645. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  646. package/templates/shop/app/globals.css +94 -1
  647. package/templates/shop/app/layout.tsx +4 -4
  648. package/templates/shop/app/login/page.tsx +4 -4
  649. package/templates/shop/app.ts +18 -6
  650. package/templates/shop/components/cart-button.tsx +33 -0
  651. package/templates/shop/components/marketing.tsx +17 -87
  652. package/templates/shop/components/shelf-count.tsx +36 -0
  653. package/templates/shop/functions/checkout.ts +17 -1
  654. package/templates/shop/lib/cart-store.ts +57 -0
  655. package/templates/shop/lib/shelf-store.ts +35 -0
  656. package/templates/shop/lib/shop.ts +27 -0
  657. package/templates/shop/lib/site.config.ts +108 -98
  658. package/templates/shop/public/images/hero.jpg +0 -0
  659. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  660. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  661. package/templates/shop/public/images/products/linen.jpg +0 -0
  662. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  663. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  664. package/templates/shop/tests/shop.test.ts +64 -0
  665. package/templates/todo/AGENTS.md +1 -1
  666. package/templates/waitlist/.env.example +7 -0
  667. package/templates/waitlist/AGENTS.md +1 -1
  668. package/templates/waitlist/README.md +29 -13
  669. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  670. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  671. package/templates/waitlist/app/auth-form.tsx +8 -8
  672. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  673. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  674. package/templates/waitlist/app/icon.svg +2 -0
  675. package/templates/waitlist/app/login/page.tsx +14 -14
  676. package/templates/waitlist/app.ts +6 -6
  677. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  678. package/templates/waitlist/functions/seedDemo.ts +32 -0
  679. package/templates/waitlist/lib/demo.ts +94 -0
  680. package/templates/waitlist/lib/launch.ts +19 -0
  681. package/templates/waitlist/lib/site.config.ts +14 -9
  682. package/templates/waitlist/lib/stats.ts +35 -0
  683. package/templates/waitlist/public/favicon.ico +0 -0
  684. package/templates/waitlist/tests/counter.test.ts +107 -0
  685. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  686. package/templates/ai-chat/tests/example.test.ts +0 -12
  687. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  688. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  689. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  690. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  691. package/templates/chat/functions/_keep.ts +0 -13
  692. package/templates/chat/tests/example.test.ts +0 -12
  693. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  694. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  695. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  696. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -1,36 +1,72 @@
1
+ "use client";
2
+
1
3
  import React from "react";
4
+ import { Menu, Search } from "lucide-react";
2
5
  import { cn } from "@/lib/utils";
6
+ import { useShell } from "@/components/app-shell";
3
7
 
4
8
  /**
5
9
  * The bar at the top of every view: title on the left, actions on the right.
6
- * Fixed height so switching pages doesn't shift the content below it.
10
+ * Fixed height, so switching pages doesn't shift the content below it.
11
+ *
12
+ * Below the `md` breakpoint it also carries the menu button that opens the
13
+ * navigation drawer and a search button, since the sidebar is hidden there.
7
14
  */
8
15
  export function PageHeader({
9
16
  title,
10
17
  count,
18
+ leading,
11
19
  children,
12
20
  className,
13
21
  }: {
14
22
  title: string;
15
- /** Shown beside the title — the number of rows in view. */
23
+ /** Shown beside the title. Pass the same number the sidebar shows. */
16
24
  count?: number;
25
+ /** Rendered before the title, such as a back link on a detail page. */
26
+ leading?: React.ReactNode;
17
27
  children?: React.ReactNode;
18
28
  className?: string;
19
29
  }) {
30
+ const shell = useShell();
20
31
  return (
21
32
  <header
22
33
  className={cn(
23
- "flex h-12 shrink-0 items-center justify-between gap-3 border-b border-border px-4",
34
+ "flex h-13 shrink-0 items-center justify-between gap-2 border-b border-border px-3 md:gap-3 md:px-5",
24
35
  className,
25
36
  )}
26
37
  >
27
- <div className="flex min-w-0 items-baseline gap-2">
28
- <h1 className="truncate text-[13px] font-semibold">{title}</h1>
38
+ <div className="flex min-w-0 items-center gap-1.5">
39
+ {shell ? (
40
+ <button
41
+ type="button"
42
+ onClick={shell.openNav}
43
+ aria-label="Open navigation"
44
+ className="-ml-1 rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-foreground/[0.05] hover:text-foreground md:hidden"
45
+ >
46
+ <Menu className="size-[18px]" />
47
+ </button>
48
+ ) : null}
49
+ {leading}
50
+ <h1 className="truncate text-[14px] font-semibold tracking-[-0.01em]">{title}</h1>
29
51
  {typeof count === "number" ? (
30
- <span className="tabular text-[12px] text-muted-foreground">{count}</span>
52
+ <span className="tabular ml-0.5 rounded-full bg-foreground/[0.06] px-1.5 py-px text-[11.5px] font-medium text-muted-foreground">
53
+ {count}
54
+ </span>
55
+ ) : null}
56
+ </div>
57
+ <div className="flex shrink-0 items-center gap-1.5 md:gap-2">
58
+ {shell ? (
59
+ <button
60
+ type="button"
61
+ onClick={shell.openCommand}
62
+ aria-label="Search"
63
+ className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-foreground/[0.05] hover:text-foreground md:hidden"
64
+ >
65
+ <Search className="size-[18px]" />
66
+ </button>
31
67
  ) : null}
68
+ {children}
32
69
  </div>
33
- <div className="flex items-center gap-2">{children}</div>
34
70
  </header>
35
71
  );
36
72
  }
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
 
3
- import React from "react";
3
+ import React, { useState } from "react";
4
4
  import { useAuth } from "@pylonsync/client";
5
- import { AuthForm } from "@/components/auth-form";
5
+ import { BRAND } from "@/lib/brand";
6
+ import { AuthForm, type AuthMode } from "@/components/auth-form";
7
+ import { BrandMark } from "@/components/brand-mark";
6
8
 
7
9
  /**
8
10
  * Client-side auth gate.
@@ -18,44 +20,40 @@ import { AuthForm } from "@/components/auth-form";
18
20
  * It also removes a full page load from the sign-in path: authenticating swaps
19
21
  * this component's children in place instead of navigating.
20
22
  */
21
- export function RequireAuth({
22
- title,
23
- description,
24
- children,
25
- }: {
26
- title: string;
27
- description: string;
28
- children: React.ReactNode;
29
- }) {
23
+ export function RequireAuth({ children }: { children: React.ReactNode }) {
30
24
  const { isSignedIn, isLoaded } = useAuth();
25
+ const [mode, setMode] = useState<AuthMode>("login");
31
26
 
32
27
  // `isLoaded` covers the beat before the engine has resolved the stored token.
33
28
  // Rendering the form during it would flash a sign-in screen at someone who is
34
29
  // already signed in.
35
30
  if (!isLoaded) {
36
31
  return (
37
- <main className="flex min-h-screen items-center justify-center">
38
- <span className="text-[13px] text-muted-foreground">Loading…</span>
32
+ <main className="flex min-h-dvh items-center justify-center" aria-busy="true">
33
+ <BrandMark className="size-8 animate-pulse" />
34
+ <span className="sr-only">Loading {BRAND.name}</span>
39
35
  </main>
40
36
  );
41
37
  }
42
38
 
43
39
  if (!isSignedIn) {
44
40
  return (
45
- <main className="flex min-h-screen items-center justify-center px-6">
46
- <div className="w-full max-w-[320px]">
47
- <div className="mb-7 flex items-center gap-2">
48
- <span className="flex size-7 items-center justify-center rounded-md bg-primary text-[12px] font-bold text-primary-foreground">
49
- {title.slice(0, 1).toUpperCase()}
50
- </span>
51
- <span className="text-[15px] font-semibold tracking-tight">{title}</span>
41
+ <main className="flex min-h-dvh items-center justify-center bg-surface-1 px-4 py-10">
42
+ <div className="w-full max-w-[380px]">
43
+ <div className="mb-6 flex items-center justify-center gap-2.5">
44
+ <BrandMark className="size-8 rounded-[9px]" />
45
+ <span className="text-[17px] font-semibold tracking-[-0.015em]">{BRAND.name}</span>
52
46
  </div>
53
- <h1 className="text-[19px] font-semibold tracking-tight">
54
- Sign in to your workspace
55
- </h1>
56
- <p className="mt-1 text-[13px] text-muted-foreground">{description}</p>
57
- <div className="mt-6">
58
- <AuthForm />
47
+ <div className="rounded-xl border border-border bg-background p-6 shadow-[0_1px_2px_rgb(0_0_0/0.04),0_12px_32px_-12px_rgb(0_0_0/0.12)] sm:p-7">
48
+ <h1 className="text-[18px] font-semibold tracking-[-0.015em]">
49
+ {mode === "login" ? `Sign in to ${BRAND.name}` : `Create your ${BRAND.name} account`}
50
+ </h1>
51
+ <p className="mt-1.5 text-[13px] leading-5 text-muted-foreground">
52
+ {BRAND.description}
53
+ </p>
54
+ <div className="mt-6">
55
+ <AuthForm mode={mode} onModeChange={setMode} />
56
+ </div>
59
57
  </div>
60
58
  </div>
61
59
  </main>
@@ -1,111 +1,117 @@
1
1
  import React from "react";
2
2
  import { Link } from "@pylonsync/react";
3
- import { ArrowLeftRight, Boxes, Command, LogOut } from "lucide-react";
3
+ import { LogOut, Search } from "lucide-react";
4
4
  import { cn } from "@/lib/utils";
5
+ import { BRAND } from "@/lib/brand";
6
+ import { NAV } from "@/components/nav";
5
7
  import { Avatar } from "@/components/avatar";
8
+ import { BrandMark } from "@/components/brand-mark";
6
9
  import { Kbd } from "@/components/kbd";
7
10
 
8
- export interface NavItem {
9
- href: string;
10
- label: string;
11
- icon: React.ReactNode;
12
- count?: number;
11
+ /** True when `href` is the section `pathname` belongs to. */
12
+ export function isActive(href: string, pathname: string): boolean {
13
+ if (href === "/") return pathname === "/";
14
+ return pathname === href || pathname.startsWith(`${href}/`);
13
15
  }
14
16
 
15
- export const NAV: Array<Omit<NavItem, "count">> = [
16
- { href: "/", label: "Products", icon: <Boxes /> },
17
- { href: "/movements", label: "Movements", icon: <ArrowLeftRight /> },
18
- ];
19
-
20
17
  /**
21
- * The fixed left rail. Presentational: the active path, the counts, and the
22
- * sign-out handler all arrive as props, so it renders identically in a test.
18
+ * The navigation rail: brand, search, links with counts, and the signed-in
19
+ * person. The same component renders as the fixed rail on wide screens and
20
+ * inside the drawer on phones (see components/app-shell.tsx).
21
+ *
22
+ * Presentational: the path, counts, and handlers arrive as props, so it
23
+ * renders the same in a test.
23
24
  */
24
25
  export function Sidebar({
25
- workspace,
26
- email,
26
+ user,
27
27
  pathname,
28
28
  counts,
29
29
  onOpenCommand,
30
+ onNavigate,
30
31
  onSignOut,
32
+ className,
31
33
  }: {
32
- workspace: string;
33
- email: string;
34
+ user: { name: string; email: string };
34
35
  pathname: string;
36
+ /** Badge per nav href. Omit a key to show no number. */
35
37
  counts?: Record<string, number>;
36
38
  onOpenCommand: () => void;
39
+ /** Called after a link is chosen, so the phone drawer can close. */
40
+ onNavigate?: () => void;
37
41
  onSignOut: () => void;
42
+ className?: string;
38
43
  }) {
39
44
  return (
40
- <aside className="flex w-[224px] shrink-0 flex-col border-r border-border bg-surface-1">
41
- <div className="flex h-12 items-center gap-2 px-3">
42
- <span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-primary text-[11px] font-bold text-primary-foreground">
43
- {workspace.slice(0, 1).toUpperCase()}
45
+ <div className={cn("flex h-full w-full flex-col bg-surface-1", className)}>
46
+ <div className="flex h-13 shrink-0 items-center gap-2.5 px-4">
47
+ <BrandMark />
48
+ <span className="truncate text-[14px] font-semibold tracking-[-0.01em]">
49
+ {BRAND.name}
44
50
  </span>
45
- <span className="truncate text-[13px] font-semibold">{workspace}</span>
46
51
  </div>
47
52
 
48
- <div className="px-2 pb-2">
53
+ <div className="px-3 pb-3">
49
54
  <button
50
55
  type="button"
51
56
  onClick={onOpenCommand}
52
57
  className={cn(
53
- "flex w-full items-center gap-2 rounded-md border border-border bg-background px-2 py-1.5",
54
- "text-[12px] text-muted-foreground transition-colors hover:border-ring/40 hover:text-foreground",
58
+ "flex h-8 w-full items-center gap-2 rounded-md border border-border bg-background px-2.5",
59
+ "text-[12.5px] text-muted-foreground shadow-[0_1px_1px_rgb(0_0_0/0.03)] transition-colors",
60
+ "hover:border-foreground/15 hover:text-foreground",
55
61
  )}
56
62
  >
57
- <Command className="size-3.5" />
58
- <span className="flex-1 text-left">Search…</span>
59
- <Kbd>⌘K</Kbd>
63
+ <Search className="size-3.5" />
64
+ <span className="flex-1 text-left">Search</span>
65
+ <Kbd className="max-md:hidden">⌘K</Kbd>
60
66
  </button>
61
67
  </div>
62
68
 
63
- <nav className="flex-1 space-y-0.5 px-2" aria-label="Main">
69
+ <nav className="flex-1 space-y-px overflow-y-auto px-3" aria-label="Main">
64
70
  {NAV.map((item) => {
65
- const active =
66
- item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
71
+ const active = isActive(item.href, pathname);
72
+ const count = counts?.[item.href];
67
73
  return (
68
74
  <Link
69
75
  key={item.href}
70
76
  href={item.href}
77
+ onClick={onNavigate}
71
78
  aria-current={active ? "page" : undefined}
72
79
  className={cn(
73
- "flex h-7 items-center gap-2 rounded-md px-2 text-[13px] transition-colors",
74
- "[&_svg]:size-3.5 [&_svg]:shrink-0",
80
+ "flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] transition-colors md:h-7.5",
81
+ "[&_svg]:size-4 [&_svg]:shrink-0",
75
82
  active
76
- ? "bg-surface-2 font-medium text-foreground"
77
- : "text-muted-foreground hover:bg-surface-2/60 hover:text-foreground",
83
+ ? "bg-foreground/[0.06] font-medium text-foreground [&_svg]:text-primary"
84
+ : "text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground",
78
85
  )}
79
86
  >
80
87
  {item.icon}
81
88
  <span className="flex-1 truncate">{item.label}</span>
82
- {counts?.[item.href] ? (
83
- <span className="tabular text-[11px] text-muted-foreground">
84
- {counts[item.href]}
85
- </span>
89
+ {typeof count === "number" ? (
90
+ <span className="tabular text-[11.5px] text-muted-foreground">{count}</span>
86
91
  ) : null}
87
92
  </Link>
88
93
  );
89
94
  })}
90
95
  </nav>
91
96
 
92
- <div className="border-t border-border p-2">
93
- <div className="flex items-center gap-2 rounded-md px-1.5 py-1">
94
- <Avatar name={email} size="sm" />
95
- <span className="min-w-0 flex-1 truncate text-[12px] text-muted-foreground">
96
- {email}
97
- </span>
97
+ <div className="border-t border-border p-3">
98
+ <div className="flex items-center gap-2.5">
99
+ <Avatar name={user.name || user.email} />
100
+ <div className="min-w-0 flex-1 leading-tight">
101
+ <p className="truncate text-[12.5px] font-medium">{user.name || "Signed in"}</p>
102
+ <p className="truncate text-[11.5px] text-muted-foreground">{user.email}</p>
103
+ </div>
98
104
  <button
99
105
  type="button"
100
106
  onClick={onSignOut}
101
107
  title="Sign out"
102
108
  aria-label="Sign out"
103
- className="rounded p-1 text-muted-foreground transition-colors hover:bg-surface-2 hover:text-foreground"
109
+ className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-foreground/[0.06] hover:text-foreground"
104
110
  >
105
- <LogOut className="size-3.5" />
111
+ <LogOut className="size-4" />
106
112
  </button>
107
113
  </div>
108
114
  </div>
109
- </aside>
115
+ </div>
110
116
  );
111
117
  }
@@ -2,18 +2,22 @@ import React from "react";
2
2
  import { cn } from "@/lib/utils";
3
3
  import { stockState } from "@/lib/stock";
4
4
 
5
- const STYLE: Record<string, string> = {
5
+ const TEXT: Record<string, string> = {
6
6
  out: "text-destructive",
7
- low: "text-stage-proposal",
7
+ low: "text-[color-mix(in_oklab,var(--stage-proposal)_75%,black)]",
8
8
  ok: "text-foreground",
9
9
  };
10
10
 
11
+ const BAR: Record<string, string> = {
12
+ out: "bg-destructive",
13
+ low: "bg-stage-proposal",
14
+ ok: "bg-primary/70",
15
+ };
16
+
11
17
  /**
12
- * On-hand, coloured by whether it needs attention.
13
- *
14
- * The number itself carries the colour rather than a separate badge: in a dense
15
- * table the quantity is what the eye goes to, and a chip beside it is a second
16
- * thing to read for the same information.
18
+ * On-hand as a number and a short bar, coloured by whether it needs
19
+ * attention. The bar is full at three times the reorder point, so a line
20
+ * sitting at its reorder point shows a third full.
17
21
  */
18
22
  export function StockLevel({
19
23
  quantity,
@@ -25,9 +29,12 @@ export function StockLevel({
25
29
  className?: string;
26
30
  }) {
27
31
  const state = stockState(quantity, reorderPoint);
32
+ const point = Number(reorderPoint) || 0;
33
+ const scale = point > 0 ? point * 3 : Math.max(quantity, 1);
34
+ const fill = Math.max(0, Math.min(1, quantity / scale));
28
35
  return (
29
36
  <span
30
- className={cn("tabular font-medium", STYLE[state], className)}
37
+ className={cn("inline-flex items-center justify-end gap-3", className)}
31
38
  title={
32
39
  state === "out"
33
40
  ? "Out of stock"
@@ -36,12 +43,21 @@ export function StockLevel({
36
43
  : undefined
37
44
  }
38
45
  >
39
- {quantity}
40
- {state === "out" ? (
41
- <span className="ml-1.5 text-[11px] font-normal">out</span>
42
- ) : state === "low" ? (
43
- <span className="ml-1.5 text-[11px] font-normal">low</span>
44
- ) : null}
46
+ <span
47
+ aria-hidden="true"
48
+ className="hidden h-1.5 w-14 overflow-hidden rounded-full bg-foreground/[0.08] md:inline-block"
49
+ >
50
+ <span
51
+ className={cn("block h-full rounded-full", BAR[state])}
52
+ style={{ width: `${Math.max(fill * 100, state === "out" ? 0 : 6)}%` }}
53
+ />
54
+ </span>
55
+ <span className={cn("tabular min-w-[3.25rem] text-right font-medium", TEXT[state])}>
56
+ {quantity}
57
+ {state !== "ok" ? (
58
+ <span className="ml-1 text-[11px] font-normal">{state === "out" ? "out" : "low"}</span>
59
+ ) : null}
60
+ </span>
45
61
  </span>
46
62
  );
47
63
  }
@@ -1,20 +1,13 @@
1
1
  import React from "react";
2
- import { money, summarize, type Movement, type Product } from "@/lib/stock";
2
+ import { money, type Summary } from "@/lib/stock";
3
3
 
4
4
  /**
5
- * What\'s on the shelves and what needs attention. All derived from the ledger —
5
+ * What's on the shelves and what needs attention. All derived from the ledger —
6
6
  * see lib/stock.ts, where the arithmetic is unit-tested.
7
7
  */
8
- export function SummaryBar({
9
- products,
10
- movements,
11
- }: {
12
- products: Product[];
13
- movements: Movement[];
14
- }) {
15
- const s = summarize(products, movements);
8
+ export function SummaryBar({ summary: s }: { summary: Summary }) {
16
9
  return (
17
- <dl className="grid grid-cols-2 gap-px border-b border-border bg-border sm:grid-cols-4">
10
+ <dl className="grid shrink-0 grid-cols-2 gap-px border-b border-border bg-border lg:grid-cols-4">
18
11
  <Metric label="Products" value={String(s.skuCount)} hint="active SKUs" />
19
12
  <Metric label="Units on hand" value={s.unitCount.toLocaleString("en-US")} hint="across all lines" />
20
13
  <Metric label="Stock value" value={money(s.valuationCents)} hint="at cost" />
@@ -40,17 +33,17 @@ function Metric({
40
33
  alarming?: boolean;
41
34
  }) {
42
35
  return (
43
- <div className="bg-background px-4 py-3">
44
- <dt className="text-[11px] text-muted-foreground">{label}</dt>
36
+ <div className="min-w-0 bg-background px-4 py-2.5 md:px-5 md:py-3.5">
37
+ <dt className="truncate text-[12px] text-muted-foreground">{label}</dt>
45
38
  <dd
46
39
  className={
47
- "tabular mt-0.5 text-[18px] font-semibold tracking-tight" +
40
+ "tabular mt-1 text-[18px] font-semibold leading-none tracking-[-0.02em] md:text-[22px]" +
48
41
  (alarming ? " text-destructive" : "")
49
42
  }
50
43
  >
51
44
  {value}
52
45
  </dd>
53
- <p className="mt-0.5 text-[11px] text-muted-foreground">{hint}</p>
46
+ <p className="mt-1 truncate text-[11.5px] text-muted-foreground md:mt-1.5">{hint}</p>
54
47
  </div>
55
48
  );
56
49
  }
@@ -5,15 +5,16 @@ import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { cn } from "@/lib/utils";
6
6
 
7
7
  const buttonVariants = cva(
8
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-[13px] font-medium transition-[color,background-color,box-shadow,transform] duration-150 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
9
  {
10
10
  variants: {
11
11
  variant: {
12
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
+ default:
13
+ "bg-primary text-primary-foreground shadow-[inset_0_1px_0_rgb(255_255_255/0.15),0_1px_2px_rgb(0_0_0/0.1)] hover:bg-primary/90",
13
14
  destructive:
14
15
  "bg-destructive text-white hover:bg-destructive/90",
15
16
  outline:
16
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ "border border-input bg-background shadow-[0_1px_1px_rgb(0_0_0/0.03)] hover:bg-accent hover:text-accent-foreground",
17
18
  secondary:
18
19
  "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
20
  ghost: "hover:bg-accent hover:text-accent-foreground",
@@ -21,7 +22,7 @@ const buttonVariants = cva(
21
22
  },
22
23
  size: {
23
24
  default: "h-9 px-4 py-2",
24
- sm: "h-8 rounded-md px-3 text-xs",
25
+ sm: "h-8 rounded-md px-3 text-[12.5px] md:h-7.5 [&_svg]:size-3.5",
25
26
  lg: "h-10 rounded-md px-8",
26
27
  icon: "h-9 w-9",
27
28
  },
@@ -35,7 +35,7 @@ const DialogContent = React.forwardRef<
35
35
  <DialogPrimitive.Content
36
36
  ref={ref}
37
37
  className={cn(
38
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border bg-background p-6 shadow-lg duration-200",
38
+ "fixed left-[50%] top-[50%] z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-xl border bg-background p-5 shadow-[0_24px_48px_-12px_rgb(0_0_0/0.25)] duration-200 sm:p-6",
39
39
  "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
40
40
  className,
41
41
  )}
@@ -12,7 +12,7 @@ function Input({
12
12
  type={type}
13
13
  data-slot="input"
14
14
  className={cn(
15
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors",
15
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm md:text-[13px] transition-colors",
16
16
  "placeholder:text-muted-foreground",
17
17
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
18
18
  "disabled:cursor-not-allowed disabled:opacity-50",
@@ -22,7 +22,7 @@ function Select({
22
22
  <select
23
23
  data-slot="select"
24
24
  className={cn(
25
- "flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-sm shadow-sm transition-colors",
25
+ "flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-base shadow-sm md:text-[13px] transition-colors",
26
26
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
27
27
  "disabled:cursor-not-allowed disabled:opacity-50",
28
28
  className,
@@ -10,7 +10,7 @@ function Textarea({
10
10
  <textarea
11
11
  data-slot="textarea"
12
12
  className={cn(
13
- "flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors",
13
+ "flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm md:text-[13px] transition-colors",
14
14
  "placeholder:text-muted-foreground",
15
15
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
16
16
  "disabled:cursor-not-allowed disabled:opacity-50",
@@ -1,26 +1,36 @@
1
1
  import { mutation } from "@pylonsync/functions";
2
- import { shapeSeed } from "../lib/seed";
2
+ import { SEED_TEAM, shapeSeed } from "../lib/seed";
3
+ import { ensureDemoUser } from "../lib/demo-team";
3
4
 
4
5
  /**
5
6
  * Fill a brand-new stock workspace once.
6
7
  *
7
8
  * Seeds a plausible SEQUENCE of receipts and sales rather than a starting
8
- * quantity, because on-hand here is the sum of the ledger — a seed that wrote
9
- * levels directly would contradict the app\'s own model.
9
+ * quantity, because on-hand here is the sum of the ledger; a seed that wrote
10
+ * levels directly would contradict the app's own model.
10
11
  *
11
- * Returns immediately if any product exists, so it is safe on every load.
12
- * Delete this function and lib/seed.ts once you stock real products.
12
+ * Returns immediately if any product exists, so it is safe on every load. An
13
+ * advisory lock stops two first loads from seeding twice. Movements are
14
+ * recorded by the person who signed in and four demo teammates (User rows
15
+ * with no password, see SEED_TEAM). Delete this function, lib/seed.ts, and the
16
+ * `seedWorkspace` call in app/workspace.tsx once you stock real products, then
17
+ * delete the demo users.
13
18
  */
14
19
  export default mutation<Record<string, never>, { seeded: boolean }>({
15
20
  auth: "user",
16
21
  args: {},
17
22
  async handler(ctx) {
18
- const existing = await ctx.db.query("Product", { $limit: 1 });
19
- if (existing.length > 0) return { seeded: false };
23
+ if ((await ctx.db.query("Product", { $limit: 1 })).length > 0) return { seeded: false };
24
+ await ctx.db.advisoryLock("inventory_seed_workspace");
25
+ if ((await ctx.db.query("Product", { $limit: 1 })).length > 0) return { seeded: false };
20
26
 
21
27
  const seed = shapeSeed();
22
28
  const me = ctx.auth.userId;
23
29
 
30
+ // Position 0 is the person who signed in; SEED_TEAM follows.
31
+ const team: Array<string | null> = [me];
32
+ for (const person of SEED_TEAM) team.push(await ensureDemoUser(ctx.db, person));
33
+
24
34
  const productIds = new Map<string, string>();
25
35
  for (const product of seed.products) {
26
36
  const id = await ctx.db.insert("Product", product.row);
@@ -31,7 +41,7 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
31
41
  await ctx.db.insert("Movement", {
32
42
  ...movement.row,
33
43
  productId: productIds.get(movement.product) ?? null,
34
- actorId: me,
44
+ actorId: team[movement.actor] ?? me,
35
45
  });
36
46
  }
37
47
 
@@ -0,0 +1,9 @@
1
+ // The product name, in one place. The sidebar, the sign-in screen, the page
2
+ // title, and the drawer on phones all read it. Rename the product here; the
3
+ // mark is in components/brand-mark.tsx and the favicon is app/icon.svg.
4
+
5
+ export const BRAND = {
6
+ name: "Stockroom",
7
+ /** One line on the sign-in screen: what the app holds and who sees it. */
8
+ description: "Your team's products and stock ledger. Every receipt, sale, and count is recorded here.",
9
+ } as const;
@@ -0,0 +1,51 @@
1
+ // Creates the demo teammates the seed spreads records across. Each is a User
2
+ // row with a `@demo.invalid` address and no password. Outside dev mode nobody
3
+ // can sign in as one: the address can't receive a sign-in or reset email.
4
+ // In dev mode (`PYLON_DEV_MODE=true`) the auth API returns sign-in codes
5
+ // directly, so anyone can sign in as a demo teammate there.
6
+
7
+ import type { DbWriter } from "@pylonsync/functions";
8
+
9
+ export interface DemoPerson {
10
+ name: string;
11
+ email: string;
12
+ }
13
+
14
+ /**
15
+ * The address to try on `attempt` (0, 1, 2, …): the person's own address
16
+ * first, then `local+2@demo.invalid`, `local+3@…`, and so on.
17
+ */
18
+ export function demoEmail(email: string, attempt: number): string {
19
+ if (attempt === 0) return email;
20
+ const at = email.indexOf("@");
21
+ return `${email.slice(0, at)}+${attempt + 1}${email.slice(at)}`;
22
+ }
23
+
24
+ /**
25
+ * A User row the seed may reuse: one it created itself on an earlier run.
26
+ * A row with a password or a verified email belongs to someone who
27
+ * registered that address, so the seed never assigns demo records to it.
28
+ */
29
+ export function isDemoRow(row: Record<string, unknown>): boolean {
30
+ return !row.passwordHash && !row.emailVerified;
31
+ }
32
+
33
+ /** Returns the id of a demo User row for `person`, creating it if needed. */
34
+ export async function ensureDemoUser(db: DbWriter, person: DemoPerson): Promise<string> {
35
+ for (let attempt = 0; attempt < 20; attempt += 1) {
36
+ const email = demoEmail(person.email, attempt);
37
+ // Users are written by auth, and their policy denies client inserts, so
38
+ // demo rows go through the server-trust surface. The read sees
39
+ // passwordHash, which is how a registered account is told apart.
40
+ const [existing] = await db.unsafe.query("User", { email, $limit: 1 });
41
+ if (!existing) {
42
+ return db.unsafe.insert("User", {
43
+ email,
44
+ displayName: person.name,
45
+ createdAt: new Date(Date.now() - 200 * 86_400_000).toISOString(),
46
+ });
47
+ }
48
+ if (isDemoRow(existing)) return existing.id as string;
49
+ }
50
+ throw new Error(`No free demo address for ${person.email}`);
51
+ }