@pylonsync/create-pylon 0.3.343 → 0.3.345

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 (422) hide show
  1. package/bin/create-pylon.js +35 -0
  2. package/package.json +1 -1
  3. package/templates/agency/components/ui/badge.tsx +37 -0
  4. package/templates/agency/components/ui/dialog.tsx +110 -0
  5. package/templates/agency/components/ui/input.tsx +30 -0
  6. package/templates/agency/components/ui/label.tsx +21 -0
  7. package/templates/agency/components/ui/select.tsx +39 -0
  8. package/templates/agency/components/ui/table.tsx +124 -0
  9. package/templates/agency/components/ui/textarea.tsx +24 -0
  10. package/templates/agency/package.json +1 -0
  11. package/templates/ai-chat/components/ui/badge.tsx +37 -0
  12. package/templates/ai-chat/components/ui/button.tsx +56 -0
  13. package/templates/ai-chat/components/ui/card.tsx +90 -0
  14. package/templates/ai-chat/components/ui/dialog.tsx +110 -0
  15. package/templates/ai-chat/components/ui/input.tsx +30 -0
  16. package/templates/ai-chat/components/ui/label.tsx +21 -0
  17. package/templates/ai-chat/components/ui/select.tsx +39 -0
  18. package/templates/ai-chat/components/ui/table.tsx +124 -0
  19. package/templates/ai-chat/components/ui/textarea.tsx +24 -0
  20. package/templates/ai-chat/package.json +1 -0
  21. package/templates/ai-studio/components/ui/badge.tsx +37 -0
  22. package/templates/ai-studio/components/ui/button.tsx +56 -0
  23. package/templates/ai-studio/components/ui/card.tsx +90 -0
  24. package/templates/ai-studio/components/ui/dialog.tsx +110 -0
  25. package/templates/ai-studio/components/ui/input.tsx +30 -0
  26. package/templates/ai-studio/components/ui/label.tsx +21 -0
  27. package/templates/ai-studio/components/ui/select.tsx +39 -0
  28. package/templates/ai-studio/components/ui/table.tsx +124 -0
  29. package/templates/ai-studio/components/ui/textarea.tsx +24 -0
  30. package/templates/ai-studio/package.json +1 -0
  31. package/templates/barebones/components/ui/badge.tsx +37 -0
  32. package/templates/barebones/components/ui/dialog.tsx +110 -0
  33. package/templates/barebones/components/ui/input.tsx +30 -0
  34. package/templates/barebones/components/ui/label.tsx +21 -0
  35. package/templates/barebones/components/ui/select.tsx +39 -0
  36. package/templates/barebones/components/ui/table.tsx +124 -0
  37. package/templates/barebones/components/ui/textarea.tsx +24 -0
  38. package/templates/barebones/package.json +1 -0
  39. package/templates/chat/components/ui/badge.tsx +37 -0
  40. package/templates/chat/components/ui/dialog.tsx +110 -0
  41. package/templates/chat/components/ui/input.tsx +30 -0
  42. package/templates/chat/components/ui/label.tsx +21 -0
  43. package/templates/chat/components/ui/select.tsx +39 -0
  44. package/templates/chat/components/ui/table.tsx +124 -0
  45. package/templates/chat/components/ui/textarea.tsx +24 -0
  46. package/templates/chat/package.json +1 -0
  47. package/templates/consumer/components/ui/badge.tsx +37 -0
  48. package/templates/consumer/components/ui/dialog.tsx +110 -0
  49. package/templates/consumer/components/ui/input.tsx +30 -0
  50. package/templates/consumer/components/ui/label.tsx +21 -0
  51. package/templates/consumer/components/ui/select.tsx +39 -0
  52. package/templates/consumer/components/ui/table.tsx +124 -0
  53. package/templates/consumer/components/ui/textarea.tsx +24 -0
  54. package/templates/consumer/package.json +1 -0
  55. package/templates/creator/components/ui/badge.tsx +37 -0
  56. package/templates/creator/components/ui/dialog.tsx +110 -0
  57. package/templates/creator/components/ui/input.tsx +30 -0
  58. package/templates/creator/components/ui/label.tsx +21 -0
  59. package/templates/creator/components/ui/select.tsx +39 -0
  60. package/templates/creator/components/ui/table.tsx +124 -0
  61. package/templates/creator/components/ui/textarea.tsx +24 -0
  62. package/templates/creator/package.json +1 -0
  63. package/templates/crm/AGENTS.md +116 -0
  64. package/templates/crm/CLAUDE.md +7 -0
  65. package/templates/crm/README.md +96 -0
  66. package/templates/crm/app/companies/companies-view.tsx +129 -0
  67. package/templates/crm/app/companies/page.tsx +17 -0
  68. package/templates/crm/app/contacts/contacts-view.tsx +139 -0
  69. package/templates/crm/app/contacts/page.tsx +17 -0
  70. package/templates/crm/app/deals/[id]/deal-view.tsx +189 -0
  71. package/templates/crm/app/deals/[id]/page.tsx +23 -0
  72. package/templates/crm/app/error.tsx +43 -0
  73. package/templates/crm/app/globals.css +201 -0
  74. package/templates/crm/app/layout.tsx +31 -0
  75. package/templates/crm/app/login/auth-form.tsx +122 -0
  76. package/templates/crm/app/login/page.tsx +38 -0
  77. package/templates/crm/app/not-found.tsx +29 -0
  78. package/templates/crm/app/page.tsx +38 -0
  79. package/templates/crm/app/pipeline-view.tsx +99 -0
  80. package/templates/crm/app/robots.ts +12 -0
  81. package/templates/crm/app/sitemap.ts +27 -0
  82. package/templates/crm/app/workspace.tsx +231 -0
  83. package/templates/crm/app.ts +171 -0
  84. package/templates/crm/bunfig +4 -0
  85. package/templates/crm/components/activity-timeline.tsx +133 -0
  86. package/templates/crm/components/avatar.tsx +40 -0
  87. package/templates/crm/components/board-skeleton.tsx +44 -0
  88. package/templates/crm/components/command-palette.tsx +162 -0
  89. package/templates/crm/components/data-table.tsx +90 -0
  90. package/templates/crm/components/deal-card.tsx +91 -0
  91. package/templates/crm/components/deal-dialog.tsx +169 -0
  92. package/templates/crm/components/empty-state.tsx +43 -0
  93. package/templates/crm/components/kbd.tsx +23 -0
  94. package/templates/crm/components/metrics-bar.tsx +46 -0
  95. package/templates/crm/components/page-header.tsx +36 -0
  96. package/templates/crm/components/pipeline-board.tsx +105 -0
  97. package/templates/crm/components/record-dialog.tsx +126 -0
  98. package/templates/crm/components/sidebar.tsx +112 -0
  99. package/templates/crm/components/stage-badge.tsx +33 -0
  100. package/templates/crm/components/ui/badge.tsx +37 -0
  101. package/templates/crm/components/ui/button.tsx +56 -0
  102. package/templates/crm/components/ui/card.tsx +90 -0
  103. package/templates/crm/components/ui/dialog.tsx +110 -0
  104. package/templates/crm/components/ui/input.tsx +30 -0
  105. package/templates/crm/components/ui/label.tsx +21 -0
  106. package/templates/crm/components/ui/select.tsx +39 -0
  107. package/templates/crm/components/ui/table.tsx +124 -0
  108. package/templates/crm/components/ui/textarea.tsx +24 -0
  109. package/templates/crm/components.json +20 -0
  110. package/templates/crm/functions/createDeal.ts +59 -0
  111. package/templates/crm/functions/logActivity.ts +49 -0
  112. package/templates/crm/functions/moveDeal.ts +53 -0
  113. package/templates/crm/functions/seedWorkspace.ts +63 -0
  114. package/templates/crm/gitignore +10 -0
  115. package/templates/crm/lib/pipeline.ts +221 -0
  116. package/templates/crm/lib/search.ts +78 -0
  117. package/templates/crm/lib/seed.ts +130 -0
  118. package/templates/crm/lib/utils.ts +10 -0
  119. package/templates/crm/package.json +39 -0
  120. package/templates/crm/tests/components.test.tsx +245 -0
  121. package/templates/crm/tests/pipeline.test.ts +219 -0
  122. package/templates/crm/tests/search.test.ts +83 -0
  123. package/templates/crm/tests/setup.ts +5 -0
  124. package/templates/crm/tsconfig.json +33 -0
  125. package/templates/default/components/ui/badge.tsx +37 -0
  126. package/templates/default/components/ui/dialog.tsx +110 -0
  127. package/templates/default/components/ui/input.tsx +30 -0
  128. package/templates/default/components/ui/label.tsx +21 -0
  129. package/templates/default/components/ui/select.tsx +39 -0
  130. package/templates/default/components/ui/table.tsx +124 -0
  131. package/templates/default/components/ui/textarea.tsx +24 -0
  132. package/templates/default/package.json +1 -0
  133. package/templates/directory/components/ui/badge.tsx +37 -0
  134. package/templates/directory/components/ui/dialog.tsx +110 -0
  135. package/templates/directory/components/ui/input.tsx +30 -0
  136. package/templates/directory/components/ui/label.tsx +21 -0
  137. package/templates/directory/components/ui/select.tsx +39 -0
  138. package/templates/directory/components/ui/table.tsx +124 -0
  139. package/templates/directory/components/ui/textarea.tsx +24 -0
  140. package/templates/directory/package.json +1 -0
  141. package/templates/helpdesk/AGENTS.md +116 -0
  142. package/templates/helpdesk/CLAUDE.md +7 -0
  143. package/templates/helpdesk/README.md +90 -0
  144. package/templates/helpdesk/app/customers/customers-view.tsx +140 -0
  145. package/templates/helpdesk/app/customers/page.tsx +17 -0
  146. package/templates/helpdesk/app/error.tsx +43 -0
  147. package/templates/helpdesk/app/globals.css +201 -0
  148. package/templates/helpdesk/app/inbox-view.tsx +147 -0
  149. package/templates/helpdesk/app/layout.tsx +31 -0
  150. package/templates/helpdesk/app/login/auth-form.tsx +122 -0
  151. package/templates/helpdesk/app/login/page.tsx +38 -0
  152. package/templates/helpdesk/app/not-found.tsx +29 -0
  153. package/templates/helpdesk/app/page.tsx +38 -0
  154. package/templates/helpdesk/app/robots.ts +12 -0
  155. package/templates/helpdesk/app/sitemap.ts +27 -0
  156. package/templates/helpdesk/app/tickets/[id]/page.tsx +23 -0
  157. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +164 -0
  158. package/templates/helpdesk/app/workspace.tsx +210 -0
  159. package/templates/helpdesk/app.ts +142 -0
  160. package/templates/helpdesk/bunfig +4 -0
  161. package/templates/helpdesk/components/avatar.tsx +40 -0
  162. package/templates/helpdesk/components/command-palette.tsx +162 -0
  163. package/templates/helpdesk/components/data-table.tsx +90 -0
  164. package/templates/helpdesk/components/empty-state.tsx +43 -0
  165. package/templates/helpdesk/components/kbd.tsx +23 -0
  166. package/templates/helpdesk/components/new-ticket-dialog.tsx +147 -0
  167. package/templates/helpdesk/components/page-header.tsx +36 -0
  168. package/templates/helpdesk/components/priority-badge.tsx +61 -0
  169. package/templates/helpdesk/components/record-dialog.tsx +126 -0
  170. package/templates/helpdesk/components/sidebar.tsx +111 -0
  171. package/templates/helpdesk/components/sla-indicator.tsx +47 -0
  172. package/templates/helpdesk/components/thread.tsx +143 -0
  173. package/templates/helpdesk/components/ticket-list.tsx +78 -0
  174. package/templates/helpdesk/components/ui/badge.tsx +37 -0
  175. package/templates/helpdesk/components/ui/button.tsx +56 -0
  176. package/templates/helpdesk/components/ui/card.tsx +90 -0
  177. package/templates/helpdesk/components/ui/dialog.tsx +110 -0
  178. package/templates/helpdesk/components/ui/input.tsx +30 -0
  179. package/templates/helpdesk/components/ui/label.tsx +21 -0
  180. package/templates/helpdesk/components/ui/select.tsx +39 -0
  181. package/templates/helpdesk/components/ui/table.tsx +124 -0
  182. package/templates/helpdesk/components/ui/textarea.tsx +24 -0
  183. package/templates/helpdesk/components.json +20 -0
  184. package/templates/helpdesk/functions/createTicket.ts +62 -0
  185. package/templates/helpdesk/functions/replyToTicket.ts +55 -0
  186. package/templates/helpdesk/functions/seedWorkspace.ts +52 -0
  187. package/templates/helpdesk/functions/setTicketState.ts +58 -0
  188. package/templates/helpdesk/gitignore +10 -0
  189. package/templates/helpdesk/lib/format.ts +53 -0
  190. package/templates/helpdesk/lib/search.ts +78 -0
  191. package/templates/helpdesk/lib/seed.ts +185 -0
  192. package/templates/helpdesk/lib/tickets.ts +184 -0
  193. package/templates/helpdesk/lib/utils.ts +10 -0
  194. package/templates/helpdesk/package.json +39 -0
  195. package/templates/helpdesk/tests/components.test.tsx +202 -0
  196. package/templates/helpdesk/tests/search.test.ts +83 -0
  197. package/templates/helpdesk/tests/setup.ts +5 -0
  198. package/templates/helpdesk/tests/tickets.test.ts +215 -0
  199. package/templates/helpdesk/tsconfig.json +33 -0
  200. package/templates/inventory/AGENTS.md +116 -0
  201. package/templates/inventory/CLAUDE.md +7 -0
  202. package/templates/inventory/README.md +103 -0
  203. package/templates/inventory/app/error.tsx +43 -0
  204. package/templates/inventory/app/globals.css +201 -0
  205. package/templates/inventory/app/layout.tsx +31 -0
  206. package/templates/inventory/app/login/auth-form.tsx +122 -0
  207. package/templates/inventory/app/login/page.tsx +38 -0
  208. package/templates/inventory/app/movements/movements-view.tsx +103 -0
  209. package/templates/inventory/app/movements/page.tsx +17 -0
  210. package/templates/inventory/app/not-found.tsx +29 -0
  211. package/templates/inventory/app/page.tsx +34 -0
  212. package/templates/inventory/app/products/[id]/page.tsx +23 -0
  213. package/templates/inventory/app/products/[id]/product-view.tsx +178 -0
  214. package/templates/inventory/app/products-view.tsx +213 -0
  215. package/templates/inventory/app/robots.ts +12 -0
  216. package/templates/inventory/app/sitemap.ts +27 -0
  217. package/templates/inventory/app/workspace.tsx +172 -0
  218. package/templates/inventory/app.ts +154 -0
  219. package/templates/inventory/bunfig +4 -0
  220. package/templates/inventory/components/avatar.tsx +40 -0
  221. package/templates/inventory/components/command-palette.tsx +162 -0
  222. package/templates/inventory/components/data-table.tsx +90 -0
  223. package/templates/inventory/components/empty-state.tsx +43 -0
  224. package/templates/inventory/components/kbd.tsx +23 -0
  225. package/templates/inventory/components/movement-dialog.tsx +182 -0
  226. package/templates/inventory/components/page-header.tsx +36 -0
  227. package/templates/inventory/components/record-dialog.tsx +126 -0
  228. package/templates/inventory/components/sidebar.tsx +111 -0
  229. package/templates/inventory/components/stock-level.tsx +47 -0
  230. package/templates/inventory/components/summary-bar.tsx +56 -0
  231. package/templates/inventory/components/ui/badge.tsx +37 -0
  232. package/templates/inventory/components/ui/button.tsx +56 -0
  233. package/templates/inventory/components/ui/card.tsx +90 -0
  234. package/templates/inventory/components/ui/dialog.tsx +110 -0
  235. package/templates/inventory/components/ui/input.tsx +30 -0
  236. package/templates/inventory/components/ui/label.tsx +21 -0
  237. package/templates/inventory/components/ui/select.tsx +39 -0
  238. package/templates/inventory/components/ui/table.tsx +124 -0
  239. package/templates/inventory/components/ui/textarea.tsx +24 -0
  240. package/templates/inventory/components.json +20 -0
  241. package/templates/inventory/functions/recordMovement.ts +72 -0
  242. package/templates/inventory/functions/seedWorkspace.ts +40 -0
  243. package/templates/inventory/gitignore +10 -0
  244. package/templates/inventory/lib/format.ts +53 -0
  245. package/templates/inventory/lib/search.ts +78 -0
  246. package/templates/inventory/lib/seed.ts +102 -0
  247. package/templates/inventory/lib/stock.ts +207 -0
  248. package/templates/inventory/lib/utils.ts +10 -0
  249. package/templates/inventory/package.json +39 -0
  250. package/templates/inventory/tests/components.test.tsx +155 -0
  251. package/templates/inventory/tests/search.test.ts +83 -0
  252. package/templates/inventory/tests/setup.ts +5 -0
  253. package/templates/inventory/tests/stock.test.ts +255 -0
  254. package/templates/inventory/tsconfig.json +33 -0
  255. package/templates/invoices/AGENTS.md +116 -0
  256. package/templates/invoices/CLAUDE.md +7 -0
  257. package/templates/invoices/README.md +99 -0
  258. package/templates/invoices/app/clients/clients-view.tsx +130 -0
  259. package/templates/invoices/app/clients/page.tsx +17 -0
  260. package/templates/invoices/app/error.tsx +43 -0
  261. package/templates/invoices/app/globals.css +201 -0
  262. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +253 -0
  263. package/templates/invoices/app/invoices/[id]/page.tsx +23 -0
  264. package/templates/invoices/app/invoices-view.tsx +183 -0
  265. package/templates/invoices/app/layout.tsx +31 -0
  266. package/templates/invoices/app/login/auth-form.tsx +122 -0
  267. package/templates/invoices/app/login/page.tsx +38 -0
  268. package/templates/invoices/app/not-found.tsx +29 -0
  269. package/templates/invoices/app/page.tsx +36 -0
  270. package/templates/invoices/app/robots.ts +12 -0
  271. package/templates/invoices/app/sitemap.ts +27 -0
  272. package/templates/invoices/app/workspace.tsx +174 -0
  273. package/templates/invoices/app.ts +169 -0
  274. package/templates/invoices/bunfig +4 -0
  275. package/templates/invoices/components/avatar.tsx +40 -0
  276. package/templates/invoices/components/command-palette.tsx +162 -0
  277. package/templates/invoices/components/data-table.tsx +90 -0
  278. package/templates/invoices/components/empty-state.tsx +43 -0
  279. package/templates/invoices/components/kbd.tsx +23 -0
  280. package/templates/invoices/components/line-items.tsx +172 -0
  281. package/templates/invoices/components/page-header.tsx +36 -0
  282. package/templates/invoices/components/payment-dialog.tsx +132 -0
  283. package/templates/invoices/components/record-dialog.tsx +126 -0
  284. package/templates/invoices/components/sidebar.tsx +111 -0
  285. package/templates/invoices/components/status-badge.tsx +35 -0
  286. package/templates/invoices/components/summary-bar.tsx +65 -0
  287. package/templates/invoices/components/totals-panel.tsx +70 -0
  288. package/templates/invoices/components/ui/badge.tsx +37 -0
  289. package/templates/invoices/components/ui/button.tsx +56 -0
  290. package/templates/invoices/components/ui/card.tsx +90 -0
  291. package/templates/invoices/components/ui/dialog.tsx +110 -0
  292. package/templates/invoices/components/ui/input.tsx +30 -0
  293. package/templates/invoices/components/ui/label.tsx +21 -0
  294. package/templates/invoices/components/ui/select.tsx +39 -0
  295. package/templates/invoices/components/ui/table.tsx +124 -0
  296. package/templates/invoices/components/ui/textarea.tsx +24 -0
  297. package/templates/invoices/components.json +20 -0
  298. package/templates/invoices/functions/createInvoice.ts +49 -0
  299. package/templates/invoices/functions/recordPayment.ts +72 -0
  300. package/templates/invoices/functions/seedWorkspace.ts +57 -0
  301. package/templates/invoices/functions/setInvoiceStatus.ts +35 -0
  302. package/templates/invoices/gitignore +10 -0
  303. package/templates/invoices/lib/billing.ts +259 -0
  304. package/templates/invoices/lib/format.ts +53 -0
  305. package/templates/invoices/lib/search.ts +78 -0
  306. package/templates/invoices/lib/seed.ts +190 -0
  307. package/templates/invoices/lib/utils.ts +10 -0
  308. package/templates/invoices/package.json +39 -0
  309. package/templates/invoices/tests/billing.test.ts +283 -0
  310. package/templates/invoices/tests/components.test.tsx +172 -0
  311. package/templates/invoices/tests/search.test.ts +83 -0
  312. package/templates/invoices/tests/setup.ts +5 -0
  313. package/templates/invoices/tsconfig.json +33 -0
  314. package/templates/local-service/components/ui/badge.tsx +37 -0
  315. package/templates/local-service/components/ui/dialog.tsx +110 -0
  316. package/templates/local-service/components/ui/input.tsx +30 -0
  317. package/templates/local-service/components/ui/label.tsx +21 -0
  318. package/templates/local-service/components/ui/select.tsx +39 -0
  319. package/templates/local-service/components/ui/table.tsx +124 -0
  320. package/templates/local-service/components/ui/textarea.tsx +24 -0
  321. package/templates/local-service/package.json +1 -0
  322. package/templates/marketplace/components/ui/badge.tsx +37 -0
  323. package/templates/marketplace/components/ui/button.tsx +56 -0
  324. package/templates/marketplace/components/ui/card.tsx +90 -0
  325. package/templates/marketplace/components/ui/dialog.tsx +110 -0
  326. package/templates/marketplace/components/ui/input.tsx +30 -0
  327. package/templates/marketplace/components/ui/label.tsx +21 -0
  328. package/templates/marketplace/components/ui/select.tsx +39 -0
  329. package/templates/marketplace/components/ui/table.tsx +124 -0
  330. package/templates/marketplace/components/ui/textarea.tsx +24 -0
  331. package/templates/marketplace/lib/utils.ts +10 -0
  332. package/templates/marketplace/package.json +1 -0
  333. package/templates/projects/AGENTS.md +116 -0
  334. package/templates/projects/CLAUDE.md +7 -0
  335. package/templates/projects/README.md +96 -0
  336. package/templates/projects/app/clients/clients-view.tsx +126 -0
  337. package/templates/projects/app/clients/page.tsx +17 -0
  338. package/templates/projects/app/error.tsx +43 -0
  339. package/templates/projects/app/globals.css +201 -0
  340. package/templates/projects/app/layout.tsx +31 -0
  341. package/templates/projects/app/login/auth-form.tsx +122 -0
  342. package/templates/projects/app/login/page.tsx +38 -0
  343. package/templates/projects/app/not-found.tsx +29 -0
  344. package/templates/projects/app/page.tsx +30 -0
  345. package/templates/projects/app/projects/[id]/page.tsx +23 -0
  346. package/templates/projects/app/projects/[id]/project-view.tsx +179 -0
  347. package/templates/projects/app/projects-view.tsx +181 -0
  348. package/templates/projects/app/robots.ts +12 -0
  349. package/templates/projects/app/sitemap.ts +27 -0
  350. package/templates/projects/app/workspace.tsx +190 -0
  351. package/templates/projects/app.ts +186 -0
  352. package/templates/projects/bunfig +4 -0
  353. package/templates/projects/components/avatar.tsx +40 -0
  354. package/templates/projects/components/board-skeleton.tsx +37 -0
  355. package/templates/projects/components/budget-bar.tsx +61 -0
  356. package/templates/projects/components/command-palette.tsx +162 -0
  357. package/templates/projects/components/data-table.tsx +90 -0
  358. package/templates/projects/components/empty-state.tsx +43 -0
  359. package/templates/projects/components/kbd.tsx +23 -0
  360. package/templates/projects/components/page-header.tsx +36 -0
  361. package/templates/projects/components/record-dialog.tsx +126 -0
  362. package/templates/projects/components/sidebar.tsx +111 -0
  363. package/templates/projects/components/task-board.tsx +109 -0
  364. package/templates/projects/components/task-card.tsx +77 -0
  365. package/templates/projects/components/time-dialog.tsx +114 -0
  366. package/templates/projects/components/ui/badge.tsx +37 -0
  367. package/templates/projects/components/ui/button.tsx +56 -0
  368. package/templates/projects/components/ui/card.tsx +90 -0
  369. package/templates/projects/components/ui/dialog.tsx +110 -0
  370. package/templates/projects/components/ui/input.tsx +30 -0
  371. package/templates/projects/components/ui/label.tsx +21 -0
  372. package/templates/projects/components/ui/select.tsx +39 -0
  373. package/templates/projects/components/ui/table.tsx +124 -0
  374. package/templates/projects/components/ui/textarea.tsx +24 -0
  375. package/templates/projects/components.json +20 -0
  376. package/templates/projects/functions/logTime.ts +49 -0
  377. package/templates/projects/functions/moveTask.ts +40 -0
  378. package/templates/projects/functions/seedWorkspace.ts +59 -0
  379. package/templates/projects/gitignore +10 -0
  380. package/templates/projects/lib/format.ts +53 -0
  381. package/templates/projects/lib/search.ts +78 -0
  382. package/templates/projects/lib/seed.ts +140 -0
  383. package/templates/projects/lib/utils.ts +10 -0
  384. package/templates/projects/lib/work.ts +233 -0
  385. package/templates/projects/package.json +39 -0
  386. package/templates/projects/tests/components.test.tsx +200 -0
  387. package/templates/projects/tests/search.test.ts +83 -0
  388. package/templates/projects/tests/setup.ts +5 -0
  389. package/templates/projects/tests/work.test.ts +238 -0
  390. package/templates/projects/tsconfig.json +33 -0
  391. package/templates/restaurant/components/ui/badge.tsx +37 -0
  392. package/templates/restaurant/components/ui/dialog.tsx +110 -0
  393. package/templates/restaurant/components/ui/input.tsx +30 -0
  394. package/templates/restaurant/components/ui/label.tsx +21 -0
  395. package/templates/restaurant/components/ui/select.tsx +39 -0
  396. package/templates/restaurant/components/ui/table.tsx +124 -0
  397. package/templates/restaurant/components/ui/textarea.tsx +24 -0
  398. package/templates/restaurant/package.json +1 -0
  399. package/templates/shop/components/ui/badge.tsx +37 -0
  400. package/templates/shop/components/ui/dialog.tsx +110 -0
  401. package/templates/shop/components/ui/input.tsx +30 -0
  402. package/templates/shop/components/ui/label.tsx +21 -0
  403. package/templates/shop/components/ui/select.tsx +39 -0
  404. package/templates/shop/components/ui/table.tsx +124 -0
  405. package/templates/shop/components/ui/textarea.tsx +24 -0
  406. package/templates/shop/package.json +1 -0
  407. package/templates/todo/components/ui/badge.tsx +37 -0
  408. package/templates/todo/components/ui/dialog.tsx +110 -0
  409. package/templates/todo/components/ui/input.tsx +30 -0
  410. package/templates/todo/components/ui/label.tsx +21 -0
  411. package/templates/todo/components/ui/select.tsx +39 -0
  412. package/templates/todo/components/ui/table.tsx +124 -0
  413. package/templates/todo/components/ui/textarea.tsx +24 -0
  414. package/templates/todo/package.json +1 -0
  415. package/templates/waitlist/components/ui/badge.tsx +37 -0
  416. package/templates/waitlist/components/ui/dialog.tsx +110 -0
  417. package/templates/waitlist/components/ui/input.tsx +30 -0
  418. package/templates/waitlist/components/ui/label.tsx +21 -0
  419. package/templates/waitlist/components/ui/select.tsx +39 -0
  420. package/templates/waitlist/components/ui/table.tsx +124 -0
  421. package/templates/waitlist/components/ui/textarea.tsx +24 -0
  422. package/templates/waitlist/package.json +1 -0
@@ -0,0 +1,201 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @source "../app/**/*.{tsx,ts,jsx,js}";
5
+ @source "../components/**/*.{tsx,ts,jsx,js}";
6
+
7
+ @custom-variant dark (&:where(.dark, .dark *));
8
+
9
+ /* ---------------------------------------------------------------------------
10
+ Design tokens.
11
+
12
+ Tuned for a dense internal tool rather than a marketing page: a near-neutral
13
+ surface ramp, hairline borders instead of shadows, and one accent used
14
+ sparingly so status colour still means something. Dark is the primary theme —
15
+ `app/layout.tsx` sets `class="dark"` — and light is a real, tested
16
+ alternative, not an afterthought.
17
+
18
+ These are the same variable names shadcn/ui generates against, so
19
+ `npx shadcn@latest add <component>` drops in already themed.
20
+ --------------------------------------------------------------------------- */
21
+ :root {
22
+ --radius: 0.5rem;
23
+
24
+ --background: oklch(1 0 0);
25
+ --foreground: oklch(0.21 0.006 285.9);
26
+ --card: oklch(1 0 0);
27
+ --card-foreground: var(--foreground);
28
+ --popover: oklch(1 0 0);
29
+ --popover-foreground: var(--foreground);
30
+
31
+ --primary: oklch(0.55 0.204 274.5);
32
+ --primary-foreground: oklch(1 0 0);
33
+ --secondary: oklch(0.968 0.002 286.4);
34
+ --secondary-foreground: oklch(0.27 0.006 286);
35
+ --muted: oklch(0.972 0.002 286.4);
36
+ --muted-foreground: oklch(0.53 0.013 286);
37
+ --accent: oklch(0.96 0.003 286.4);
38
+ --accent-foreground: oklch(0.27 0.006 286);
39
+ --destructive: oklch(0.58 0.216 27.3);
40
+
41
+ --border: oklch(0.918 0.004 286.3);
42
+ --input: oklch(0.918 0.004 286.3);
43
+ --ring: oklch(0.55 0.204 274.5);
44
+
45
+ /* Surfaces above the page: sidebar, popovers, hover rows. */
46
+ --surface-1: oklch(0.985 0.001 286.4);
47
+ --surface-2: oklch(0.968 0.002 286.4);
48
+
49
+ /* Pipeline + status. Muted on purpose — a board of saturated chips is
50
+ unreadable, and the eye should land on the one that's actually urgent. */
51
+ --stage-lead: oklch(0.62 0.03 264);
52
+ --stage-qualified: oklch(0.6 0.13 240);
53
+ --stage-proposal: oklch(0.68 0.15 78);
54
+ --stage-won: oklch(0.6 0.15 155);
55
+ --stage-lost: oklch(0.6 0.11 25);
56
+ }
57
+
58
+ .dark {
59
+ --background: oklch(0.165 0.004 285.9);
60
+ --foreground: oklch(0.93 0.002 286.3);
61
+ --card: oklch(0.196 0.005 285.9);
62
+ --card-foreground: var(--foreground);
63
+ --popover: oklch(0.216 0.006 285.9);
64
+ --popover-foreground: var(--foreground);
65
+
66
+ --primary: oklch(0.62 0.19 274.5);
67
+ --primary-foreground: oklch(1 0 0);
68
+ --secondary: oklch(0.25 0.006 286);
69
+ --secondary-foreground: oklch(0.93 0.002 286.3);
70
+ --muted: oklch(0.25 0.006 286);
71
+ --muted-foreground: oklch(0.65 0.012 286);
72
+ --accent: oklch(0.26 0.006 286);
73
+ --accent-foreground: oklch(0.93 0.002 286.3);
74
+ --destructive: oklch(0.65 0.19 25);
75
+
76
+ --border: oklch(1 0 0 / 9%);
77
+ --input: oklch(1 0 0 / 12%);
78
+ --ring: oklch(0.62 0.19 274.5);
79
+
80
+ --surface-1: oklch(0.186 0.004 285.9);
81
+ --surface-2: oklch(0.216 0.006 285.9);
82
+
83
+ --stage-lead: oklch(0.68 0.03 264);
84
+ --stage-qualified: oklch(0.68 0.13 240);
85
+ --stage-proposal: oklch(0.75 0.14 78);
86
+ --stage-won: oklch(0.7 0.15 155);
87
+ --stage-lost: oklch(0.68 0.14 25);
88
+ }
89
+
90
+ @theme inline {
91
+ --radius-sm: calc(var(--radius) - 3px);
92
+ --radius-md: calc(var(--radius) - 1px);
93
+ --radius-lg: var(--radius);
94
+ --radius-xl: calc(var(--radius) + 4px);
95
+
96
+ --color-background: var(--background);
97
+ --color-foreground: var(--foreground);
98
+ --color-card: var(--card);
99
+ --color-card-foreground: var(--card-foreground);
100
+ --color-popover: var(--popover);
101
+ --color-popover-foreground: var(--popover-foreground);
102
+ --color-primary: var(--primary);
103
+ --color-primary-foreground: var(--primary-foreground);
104
+ --color-secondary: var(--secondary);
105
+ --color-secondary-foreground: var(--secondary-foreground);
106
+ --color-muted: var(--muted);
107
+ --color-muted-foreground: var(--muted-foreground);
108
+ --color-accent: var(--accent);
109
+ --color-accent-foreground: var(--accent-foreground);
110
+ --color-destructive: var(--destructive);
111
+ --color-border: var(--border);
112
+ --color-input: var(--input);
113
+ --color-ring: var(--ring);
114
+
115
+ --color-surface-1: var(--surface-1);
116
+ --color-surface-2: var(--surface-2);
117
+
118
+ --color-stage-lead: var(--stage-lead);
119
+ --color-stage-qualified: var(--stage-qualified);
120
+ --color-stage-proposal: var(--stage-proposal);
121
+ --color-stage-won: var(--stage-won);
122
+ --color-stage-lost: var(--stage-lost);
123
+ }
124
+
125
+ @layer base {
126
+ *,
127
+ ::after,
128
+ ::before,
129
+ ::backdrop,
130
+ ::file-selector-button {
131
+ border-color: var(--color-border, currentColor);
132
+ outline-color: var(--color-ring);
133
+ }
134
+
135
+ html {
136
+ /* An internal tool is a fixed shell with scrolling panes, not a scrolling
137
+ document — this keeps the sidebar and header from drifting. */
138
+ height: 100%;
139
+ -webkit-tap-highlight-color: transparent;
140
+ }
141
+
142
+ body {
143
+ height: 100%;
144
+ background-color: var(--color-background);
145
+ color: var(--color-foreground);
146
+ /* 13px is the density these screens are designed at; the slight negative
147
+ tracking is what keeps Inter from looking loose at that size. */
148
+ font-size: 13px;
149
+ line-height: 1.5;
150
+ letter-spacing: -0.006em;
151
+ -webkit-font-smoothing: antialiased;
152
+ text-rendering: optimizeLegibility;
153
+ }
154
+
155
+ button {
156
+ cursor: pointer;
157
+ }
158
+
159
+ /* Numbers that sit in columns must not jitter as they change. */
160
+ .tabular,
161
+ table {
162
+ font-variant-numeric: tabular-nums;
163
+ }
164
+
165
+ /* Thin, self-effacing scrollbars — the board has several at once. */
166
+ * {
167
+ scrollbar-width: thin;
168
+ scrollbar-color: color-mix(in oklab, var(--color-foreground) 18%, transparent)
169
+ transparent;
170
+ }
171
+ ::-webkit-scrollbar {
172
+ width: 10px;
173
+ height: 10px;
174
+ }
175
+ ::-webkit-scrollbar-thumb {
176
+ background-color: color-mix(in oklab, var(--color-foreground) 16%, transparent);
177
+ border: 3px solid transparent;
178
+ background-clip: content-box;
179
+ border-radius: 999px;
180
+ }
181
+ ::-webkit-scrollbar-thumb:hover {
182
+ background-color: color-mix(in oklab, var(--color-foreground) 28%, transparent);
183
+ }
184
+ ::-webkit-scrollbar-corner {
185
+ background: transparent;
186
+ }
187
+
188
+ /* Only show focus rings for keyboard users; a mouse click shouldn't ring. */
189
+ :focus-visible {
190
+ outline: 2px solid var(--color-ring);
191
+ outline-offset: 1px;
192
+ }
193
+ }
194
+
195
+ @layer components {
196
+ /* A hairline that reads at 1px on any DPR without the doubling you get from
197
+ a plain border on retina. */
198
+ .hairline {
199
+ box-shadow: inset 0 -1px 0 0 var(--color-border);
200
+ }
201
+ }
@@ -0,0 +1,31 @@
1
+ import React from "react";
2
+
3
+ interface LayoutProps {
4
+ children: React.ReactNode;
5
+ }
6
+
7
+ /**
8
+ * The document shell. The app chrome — sidebar, command palette, shortcuts —
9
+ * lives in `app/workspace.tsx` instead, because it needs the synced data; this
10
+ * layer only sets up the page.
11
+ *
12
+ * `class="dark"` makes dark the default theme. The light tokens in globals.css
13
+ * are complete and tested, so removing the class flips the whole app; swap it
14
+ * for a stored preference when you want a toggle.
15
+ */
16
+ export default function RootLayout({ children }: LayoutProps) {
17
+ return (
18
+ <html lang="en" className="dark">
19
+ <head>
20
+ <meta charSet="utf-8" />
21
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
22
+ <title>__APP_NAME__</title>
23
+ {/* Tailwind is compiled by Pylon from app/globals.css and the
24
+ stylesheet link is injected here automatically. Inter is declared in
25
+ app.ts (fonts: [...]) and self-hosted by the build — @font-face, the
26
+ preload link, and a size-adjusted fallback are injected too. */}
27
+ </head>
28
+ <body className="h-full">{children}</body>
29
+ </html>
30
+ );
31
+ }
@@ -0,0 +1,122 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import {
5
+ ApiError,
6
+ passwordLogin,
7
+ passwordRegister,
8
+ persistSession,
9
+ } from "@pylonsync/client";
10
+ import { Button } from "@/components/ui/button";
11
+ import { Input } from "@/components/ui/input";
12
+ import { Label } from "@/components/ui/label";
13
+
14
+ /**
15
+ * One form, two modes. Calls the built-in auth API (`/api/auth/password/*`),
16
+ * then `persistSession` writes the token so the sync engine and `callFn`
17
+ * authenticate as this user on the next load.
18
+ *
19
+ * The navigation is a full `assign`, not a client route change, so the SSR
20
+ * runtime re-resolves auth from the HttpOnly cookie and the first paint of the
21
+ * workspace is already signed in.
22
+ */
23
+ export function AuthForm() {
24
+ const [mode, setMode] = useState<"login" | "signup">("login");
25
+ const [email, setEmail] = useState("");
26
+ const [password, setPassword] = useState("");
27
+ const [error, setError] = useState<string | null>(null);
28
+ const [pending, setPending] = useState(false);
29
+
30
+ async function onSubmit(event: React.FormEvent) {
31
+ event.preventDefault();
32
+ setError(null);
33
+ setPending(true);
34
+ try {
35
+ const session =
36
+ mode === "login"
37
+ ? await passwordLogin({ email, password })
38
+ : await passwordRegister({ email, password });
39
+ persistSession(session);
40
+ window.location.assign("/");
41
+ } catch (err) {
42
+ setError(messageFor(err));
43
+ setPending(false);
44
+ }
45
+ }
46
+
47
+ return (
48
+ <div className="space-y-5">
49
+ <form onSubmit={onSubmit} className="space-y-3.5">
50
+ <div className="space-y-1.5">
51
+ <Label htmlFor="email">Email</Label>
52
+ <Input
53
+ id="email"
54
+ type="email"
55
+ value={email}
56
+ onChange={(event) => setEmail(event.target.value)}
57
+ required
58
+ autoComplete="email"
59
+ placeholder="you@company.com"
60
+ />
61
+ </div>
62
+ <div className="space-y-1.5">
63
+ <Label htmlFor="password">Password</Label>
64
+ <Input
65
+ id="password"
66
+ type="password"
67
+ value={password}
68
+ onChange={(event) => setPassword(event.target.value)}
69
+ required
70
+ autoComplete={mode === "login" ? "current-password" : "new-password"}
71
+ placeholder={mode === "login" ? "Your password" : "At least 10 characters"}
72
+ />
73
+ </div>
74
+ {error ? (
75
+ <p className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-[12px] leading-snug text-destructive">
76
+ {error}
77
+ </p>
78
+ ) : null}
79
+ <Button type="submit" disabled={pending} className="w-full">
80
+ {pending ? "…" : mode === "login" ? "Sign in" : "Create account"}
81
+ </Button>
82
+ </form>
83
+
84
+ <p className="text-center text-[12px] text-muted-foreground">
85
+ {mode === "login" ? "First time here?" : "Already have an account?"}{" "}
86
+ <button
87
+ type="button"
88
+ onClick={() => {
89
+ setMode(mode === "login" ? "signup" : "login");
90
+ setError(null);
91
+ }}
92
+ className="font-medium text-foreground underline underline-offset-2"
93
+ >
94
+ {mode === "login" ? "Create an account" : "Sign in"}
95
+ </button>
96
+ </p>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ // `ApiError` carries a stable `.code`, so branch on the code rather than the
102
+ // message — the copy here is ours, the message is the framework's.
103
+ function messageFor(err: unknown): string {
104
+ if (err instanceof ApiError) {
105
+ switch (err.code) {
106
+ case "INVALID_CREDENTIALS":
107
+ return "Wrong email or password.";
108
+ case "USER_EXISTS":
109
+ return "That email is already registered — sign in instead.";
110
+ case "WEAK_PASSWORD":
111
+ return "Pick a longer password — at least 10 characters.";
112
+ case "PWNED_PASSWORD":
113
+ return "That password has appeared in a known breach. Choose another.";
114
+ case "RATE_LIMITED":
115
+ return "Too many attempts — try again in a minute.";
116
+ default:
117
+ return err.message;
118
+ }
119
+ }
120
+ if (err instanceof Error) return err.message;
121
+ return "Something went wrong. Try again.";
122
+ }
@@ -0,0 +1,38 @@
1
+ import React from "react";
2
+ import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { AuthForm } from "./auth-form";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "Sign in",
7
+ robots: "noindex",
8
+ };
9
+
10
+ export default function LoginPage({ auth, response }: PageProps) {
11
+ // Already signed in? Nothing to do here.
12
+ if (auth.user_id && !auth.user_id.startsWith("guest_")) {
13
+ response.redirect("/");
14
+ return null;
15
+ }
16
+
17
+ return (
18
+ <main className="flex min-h-screen items-center justify-center px-6">
19
+ <div className="w-full max-w-[320px]">
20
+ <div className="mb-7 flex items-center gap-2">
21
+ <span className="flex size-7 items-center justify-center rounded-md bg-primary text-[12px] font-bold text-primary-foreground">
22
+ C
23
+ </span>
24
+ <span className="text-[15px] font-semibold tracking-tight">CRM</span>
25
+ </div>
26
+ <h1 className="text-[19px] font-semibold tracking-tight">
27
+ Sign in to your workspace
28
+ </h1>
29
+ <p className="mt-1 text-[13px] text-muted-foreground">
30
+ Your team shares one pipeline. Anyone with an account sees it.
31
+ </p>
32
+ <div className="mt-6">
33
+ <AuthForm />
34
+ </div>
35
+ </div>
36
+ </main>
37
+ );
38
+ }
@@ -0,0 +1,103 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { ArrowLeftRight } from "lucide-react";
5
+ import { PageHeader } from "@/components/page-header";
6
+ import { DataTable, type ColumnDef } from "@/components/data-table";
7
+ import { EmptyState } from "@/components/empty-state";
8
+ import { relativeTime } from "@/lib/format";
9
+ import { reasonById, signedQuantity } from "@/lib/stock";
10
+ import { Workspace, type MovementRow } from "../workspace";
11
+
12
+ /**
13
+ * The ledger, newest first. Read-only by design: a movement is never edited or
14
+ * deleted — the policy forbids it — because rewriting history would change a
15
+ * past valuation and make the current level unexplainable. Corrections are new
16
+ * rows in the opposite direction.
17
+ */
18
+ export function MovementsView({ email }: { email: string }) {
19
+ return (
20
+ <Workspace email={email} pathname="/movements">
21
+ {(data) => {
22
+ const columns: ColumnDef<MovementRow>[] = [
23
+ {
24
+ key: "when",
25
+ header: "When",
26
+ cell: (row) => (
27
+ <span className="text-muted-foreground">{relativeTime(row.createdAt)}</span>
28
+ ),
29
+ },
30
+ {
31
+ key: "product",
32
+ header: "Product",
33
+ cell: (row) => (
34
+ <span className="truncate font-medium">
35
+ {data.productName(row.productId) ?? "—"}
36
+ </span>
37
+ ),
38
+ },
39
+ {
40
+ key: "reason",
41
+ header: "Reason",
42
+ cell: (row) => (
43
+ <span className="text-muted-foreground">
44
+ {reasonById(row.reason)?.label ?? row.reason}
45
+ </span>
46
+ ),
47
+ },
48
+ {
49
+ key: "delta",
50
+ header: "Change",
51
+ numeric: true,
52
+ cell: (row) => (
53
+ <span
54
+ className={
55
+ row.delta > 0 ? "text-stage-won" : row.delta < 0 ? "text-destructive" : ""
56
+ }
57
+ >
58
+ {signedQuantity(row.delta)}
59
+ </span>
60
+ ),
61
+ },
62
+ {
63
+ key: "note",
64
+ header: "Note",
65
+ cell: (row) => (
66
+ <span className="text-muted-foreground">{row.note ?? "—"}</span>
67
+ ),
68
+ },
69
+ {
70
+ key: "who",
71
+ header: "By",
72
+ cell: (row) => (
73
+ <span className="text-muted-foreground">
74
+ {data.actorName(row.actorId) ?? "—"}
75
+ </span>
76
+ ),
77
+ },
78
+ ];
79
+
80
+ const rows = [...data.movements].sort((a, b) =>
81
+ (b.createdAt ?? "").localeCompare(a.createdAt ?? ""),
82
+ );
83
+
84
+ return (
85
+ <>
86
+ <PageHeader title="Movements" count={rows.length} />
87
+ <DataTable
88
+ rows={rows}
89
+ columns={columns}
90
+ empty={
91
+ <EmptyState
92
+ icon={<ArrowLeftRight />}
93
+ title={data.loading ? "Loading…" : "No movements yet"}
94
+ description="Every change to stock is recorded here, permanently. On-hand is the sum of these rows."
95
+ />
96
+ }
97
+ />
98
+ </>
99
+ );
100
+ }}
101
+ </Workspace>
102
+ );
103
+ }
@@ -0,0 +1,17 @@
1
+ import React, { use } from "react";
2
+ import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { MovementsView } from "./movements-view";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "Movements",
7
+ robots: "noindex",
8
+ };
9
+
10
+ export default function MovementsPage({ auth, response, serverData }: PageProps) {
11
+ if (!auth.user_id || auth.user_id.startsWith("guest_")) {
12
+ response.redirect("/login");
13
+ return null;
14
+ }
15
+ const me = use(serverData.get<{ email?: string }>("User", auth.user_id));
16
+ return <MovementsView email={me?.email ?? ""} />;
17
+ }
@@ -0,0 +1,29 @@
1
+ import React from "react";
2
+ import { Link, useRouter, type NotFoundProps } from "@pylonsync/react";
3
+ import { Button } from "@/components/ui/button";
4
+
5
+ // `app/not-found.tsx` → rendered at HTTP 404 for any unmatched URL (and when
6
+ // a page calls `response.notFound()`). It's HYDRATED, so it's interactive:
7
+ // the buttons below use the client router. Not-found boundaries receive the
8
+ // standard page props (and, matching Next, no `reset`).
9
+ export default function NotFound(_props: NotFoundProps) {
10
+ const router = useRouter();
11
+ return (
12
+ <div className="space-y-6">
13
+ <section>
14
+ <h1 className="text-2xl font-semibold tracking-tight">404</h1>
15
+ <p className="mt-2 text-muted-foreground">
16
+ We couldn&apos;t find that page.
17
+ </p>
18
+ </section>
19
+ <div className="flex items-center gap-3">
20
+ <Button onClick={() => router.back()} variant="outline">
21
+ ← Go back
22
+ </Button>
23
+ <Button asChild>
24
+ <Link href="/">Home</Link>
25
+ </Button>
26
+ </div>
27
+ </div>
28
+ );
29
+ }
@@ -0,0 +1,34 @@
1
+ import React, { use } from "react";
2
+ import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { ProductsView } from "./products-view";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "Products",
7
+ robots: "noindex",
8
+ };
9
+
10
+ /**
11
+ * `/` — the stock list.
12
+ *
13
+ * Server-side auth gate only. Reading `auth` opts this render out of caching,
14
+ * which is correct: every screen here is private.
15
+ */
16
+ export default function ProductsPage({
17
+ auth,
18
+ response,
19
+ searchParams,
20
+ serverData,
21
+ }: PageProps) {
22
+ if (!auth.user_id || auth.user_id.startsWith("guest_")) {
23
+ response.redirect("/login");
24
+ return null;
25
+ }
26
+ const me = use(serverData.get<{ email?: string }>("User", auth.user_id));
27
+ return (
28
+ <ProductsView
29
+ email={me?.email ?? ""}
30
+ openNew={searchParams?.new === "product"}
31
+ initialFilter={searchParams?.filter}
32
+ />
33
+ );
34
+ }
@@ -0,0 +1,23 @@
1
+ import React, { use } from "react";
2
+ import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { ProductView } from "./product-view";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "Product",
7
+ robots: "noindex",
8
+ };
9
+
10
+ /** `app/products/[id]/page.tsx` -> `/products/:id`. */
11
+ export default function ProductPage({
12
+ auth,
13
+ response,
14
+ params,
15
+ serverData,
16
+ }: PageProps<{ id: string }>) {
17
+ if (!auth.user_id || auth.user_id.startsWith("guest_")) {
18
+ response.redirect("/login");
19
+ return null;
20
+ }
21
+ const me = use(serverData.get<{ email?: string }>("User", auth.user_id));
22
+ return <ProductView email={me?.email ?? ""} productId={params.id} />;
23
+ }