@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,7 @@
1
+ # CLAUDE.md
2
+
3
+ Agent guidance for this Pylon project lives in **AGENTS.md**, the cross-editor
4
+ standard read by Claude Code, Cursor, Codex, and other agents. The import below
5
+ makes it available to Claude Code:
6
+
7
+ @AGENTS.md
@@ -0,0 +1,96 @@
1
+ # __APP_NAME__
2
+
3
+ A sales CRM for a small team — companies, contacts, and deals on a live
4
+ pipeline board. One [Pylon](https://pylonsync.com) app: SSR frontend, API, auth,
5
+ and realtime sync from one process on one port.
6
+
7
+ ## Develop
8
+
9
+ ```bash
10
+ __RUN_DEV__
11
+ ```
12
+
13
+ Open http://localhost:4321 and create an account. The first sign-in seeds a demo
14
+ pipeline so the board isn't empty; delete `functions/seedWorkspace.ts` and
15
+ `lib/seed.ts` once you have real customers.
16
+
17
+ ## What's interesting here
18
+
19
+ **The board is live.** Drag a deal to another column and it moves on every
20
+ teammate's board immediately — no refresh, no polling. Your own move takes the
21
+ same path as theirs: write → sync → re-render, so there's one behaviour to
22
+ reason about instead of two. Open the app in two windows and try it.
23
+
24
+ **⌘K searches everything.** Deals, companies, and contacts, ranked by how well
25
+ they match (`lib/search.ts`). It runs against the synced replica in memory, so
26
+ results appear as you type with no request per keystroke. `/` opens it too, and
27
+ `c` creates a deal.
28
+
29
+ **The forecast is derived, never stored.** Open pipeline, weighted value, and
30
+ win rate are computed from the deals themselves in `lib/pipeline.ts` — pure
31
+ functions with no React and no `db`, so the arithmetic is unit-tested directly
32
+ (`tests/pipeline.test.ts`).
33
+
34
+ ## Layout
35
+
36
+ ```
37
+ app.ts entities + policies
38
+ app/
39
+ page.tsx "/" — the pipeline board
40
+ pipeline-view.tsx board container
41
+ workspace.tsx app shell — the ONLY place that touches `db`
42
+ companies/, contacts/ list views
43
+ deals/[id]/ deal detail + activity timeline
44
+ login/ email/password auth
45
+ components/ presentational — props in, callbacks out
46
+ ui/ shadcn primitives
47
+ lib/pipeline.ts stages, money, metrics — pure
48
+ lib/search.ts ⌘K ranking — pure
49
+ functions/ server functions
50
+ tests/ pure logic + component tests
51
+ ```
52
+
53
+ The data boundary lives in exactly one module (`app/workspace.tsx`). Everything
54
+ under `components/` takes data as props and reports changes through callbacks,
55
+ which is why the component tests render them with fixtures and mock nothing.
56
+
57
+ ## Access
58
+
59
+ Every entity is readable and writable by any **signed-in** user, and by nobody
60
+ else — a shared pipeline is the point of a team CRM. `ownerId` records who
61
+ created a row without partitioning access.
62
+
63
+ For per-rep isolation, tighten `allowRead` in `app.ts`:
64
+
65
+ ```ts
66
+ allowRead: "auth.userId == data.ownerId"
67
+ ```
68
+
69
+ Contacts hold customer email and phone. There is no public route in this app and
70
+ no policy allows an anonymous read — keep it that way if you add one.
71
+
72
+ ## Theme
73
+
74
+ Dark by default (`class="dark"` in `app/layout.tsx`). The light tokens in
75
+ `app/globals.css` are complete, so removing that class flips the whole app. Both
76
+ use the standard shadcn variables, so `npx shadcn@latest add <component>` drops
77
+ in already themed.
78
+
79
+ ## Grow it
80
+
81
+ - **Add a field:** edit an entity in `app.ts` — the typed client and
82
+ REST/realtime API follow, no migration.
83
+ - **Add a stage:** `PIPELINE` in `lib/pipeline.ts`. The board, the forecast, and
84
+ the stage picker all read from it.
85
+ - **Add a view:** drop `app/reports/page.tsx` and add it to `NAV` in
86
+ `components/sidebar.tsx`.
87
+
88
+ ## Deploy
89
+
90
+ ```bash
91
+ pylon lint --strict
92
+ pylon test
93
+ pylon deploy
94
+ ```
95
+
96
+ Docs: https://docs.pylonsync.com
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import { db } from "@pylonsync/react";
5
+ import { Building2, Plus } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { PageHeader } from "@/components/page-header";
8
+ import { DataTable, type ColumnDef } from "@/components/data-table";
9
+ import { EmptyState } from "@/components/empty-state";
10
+ import { RecordDialog } from "@/components/record-dialog";
11
+ import { Avatar } from "@/components/avatar";
12
+ import { money, relativeTime } from "@/lib/pipeline";
13
+ import { Workspace, type CompanyRow, type DealRow } from "../workspace";
14
+
15
+ export function CompaniesView({ email }: { email: string }) {
16
+ const [open, setOpen] = useState(false);
17
+
18
+ return (
19
+ <Workspace email={email} pathname="/companies">
20
+ {(data) => {
21
+ // Pipeline per company, computed here rather than stored — one less
22
+ // thing to keep in sync when a deal moves.
23
+ const openValue = (companyId: string) =>
24
+ data.deals
25
+ .filter((deal: DealRow) => deal.companyId === companyId)
26
+ .reduce((sum, deal) => sum + (Number(deal.value) || 0), 0);
27
+
28
+ const columns: ColumnDef<CompanyRow>[] = [
29
+ {
30
+ key: "name",
31
+ header: "Company",
32
+ cell: (row) => (
33
+ <span className="flex items-center gap-2">
34
+ <Avatar name={row.name} size="sm" />
35
+ <span className="truncate font-medium">{row.name}</span>
36
+ </span>
37
+ ),
38
+ },
39
+ {
40
+ key: "domain",
41
+ header: "Domain",
42
+ cell: (row) => (
43
+ <span className="text-muted-foreground">{row.domain ?? "—"}</span>
44
+ ),
45
+ },
46
+ {
47
+ key: "industry",
48
+ header: "Industry",
49
+ cell: (row) => (
50
+ <span className="text-muted-foreground">{row.industry ?? "—"}</span>
51
+ ),
52
+ },
53
+ {
54
+ key: "size",
55
+ header: "Size",
56
+ cell: (row) => (
57
+ <span className="text-muted-foreground">{row.size ?? "—"}</span>
58
+ ),
59
+ },
60
+ {
61
+ key: "pipeline",
62
+ header: "Pipeline",
63
+ numeric: true,
64
+ cell: (row) => money(openValue(row.id)),
65
+ },
66
+ {
67
+ key: "created",
68
+ header: "Added",
69
+ cell: (row) => (
70
+ <span className="text-muted-foreground">
71
+ {relativeTime(row.createdAt)}
72
+ </span>
73
+ ),
74
+ },
75
+ ];
76
+
77
+ const rows = [...data.companies].sort((a, b) => a.name.localeCompare(b.name));
78
+
79
+ return (
80
+ <>
81
+ <PageHeader title="Companies" count={rows.length}>
82
+ <Button size="sm" onClick={() => setOpen(true)}>
83
+ <Plus />
84
+ New company
85
+ </Button>
86
+ </PageHeader>
87
+
88
+ <DataTable
89
+ rows={rows}
90
+ columns={columns}
91
+ empty={
92
+ <EmptyState
93
+ icon={<Building2 />}
94
+ title="No companies yet"
95
+ description="Companies are the accounts your deals and contacts hang off. Add the first one to start building a pipeline."
96
+ action={
97
+ <Button size="sm" onClick={() => setOpen(true)}>
98
+ <Plus />
99
+ New company
100
+ </Button>
101
+ }
102
+ />
103
+ }
104
+ />
105
+
106
+ <RecordDialog
107
+ open={open}
108
+ title="New company"
109
+ submitLabel="Create company"
110
+ onOpenChange={setOpen}
111
+ fields={[
112
+ { name: "name", label: "Name", required: true, placeholder: "Acme Inc" },
113
+ { name: "domain", label: "Domain", placeholder: "acme.com" },
114
+ { name: "industry", label: "Industry", placeholder: "Logistics" },
115
+ { name: "size", label: "Size", placeholder: "50-200" },
116
+ ]}
117
+ onCreate={async (values) => {
118
+ // A plain policy-checked insert — no server function needed,
119
+ // because there's nothing to validate beyond the field types
120
+ // and nothing to stamp that the policy doesn't already gate.
121
+ await db.insert("Company", values);
122
+ }}
123
+ />
124
+ </>
125
+ );
126
+ }}
127
+ </Workspace>
128
+ );
129
+ }
@@ -0,0 +1,17 @@
1
+ import React, { use } from "react";
2
+ import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { CompaniesView } from "./companies-view";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "Companies",
7
+ robots: "noindex",
8
+ };
9
+
10
+ export default function CompaniesPage({ 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 <CompaniesView email={me?.email ?? ""} />;
17
+ }
@@ -0,0 +1,139 @@
1
+ "use client";
2
+
3
+ import React, { useState } from "react";
4
+ import { db } from "@pylonsync/react";
5
+ import { Plus, Users } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { PageHeader } from "@/components/page-header";
8
+ import { DataTable, type ColumnDef } from "@/components/data-table";
9
+ import { EmptyState } from "@/components/empty-state";
10
+ import { RecordDialog } from "@/components/record-dialog";
11
+ import { Avatar } from "@/components/avatar";
12
+ import { relativeTime } from "@/lib/pipeline";
13
+ import { Workspace, type ContactRow } from "../workspace";
14
+
15
+ export function ContactsView({ email }: { email: string }) {
16
+ const [open, setOpen] = useState(false);
17
+
18
+ return (
19
+ <Workspace email={email} pathname="/contacts">
20
+ {(data) => {
21
+ const columns: ColumnDef<ContactRow>[] = [
22
+ {
23
+ key: "name",
24
+ header: "Name",
25
+ cell: (row) => (
26
+ <span className="flex items-center gap-2">
27
+ <Avatar name={row.name} size="sm" />
28
+ <span className="truncate font-medium">{row.name}</span>
29
+ </span>
30
+ ),
31
+ },
32
+ {
33
+ key: "title",
34
+ header: "Title",
35
+ cell: (row) => (
36
+ <span className="text-muted-foreground">{row.title ?? "—"}</span>
37
+ ),
38
+ },
39
+ {
40
+ key: "company",
41
+ header: "Company",
42
+ cell: (row) => (
43
+ <span className="text-muted-foreground">
44
+ {data.companyName(row.companyId) ?? "—"}
45
+ </span>
46
+ ),
47
+ },
48
+ {
49
+ key: "email",
50
+ header: "Email",
51
+ cell: (row) =>
52
+ row.email ? (
53
+ <a
54
+ href={`mailto:${row.email}`}
55
+ onClick={(event) => event.stopPropagation()}
56
+ className="text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
57
+ >
58
+ {row.email}
59
+ </a>
60
+ ) : (
61
+ <span className="text-muted-foreground">—</span>
62
+ ),
63
+ },
64
+ {
65
+ key: "phone",
66
+ header: "Phone",
67
+ cell: (row) => (
68
+ <span className="text-muted-foreground">{row.phone ?? "—"}</span>
69
+ ),
70
+ },
71
+ {
72
+ key: "created",
73
+ header: "Added",
74
+ cell: (row) => (
75
+ <span className="text-muted-foreground">
76
+ {relativeTime(row.createdAt)}
77
+ </span>
78
+ ),
79
+ },
80
+ ];
81
+
82
+ const rows = [...data.contacts].sort((a, b) => a.name.localeCompare(b.name));
83
+
84
+ return (
85
+ <>
86
+ <PageHeader title="Contacts" count={rows.length}>
87
+ <Button size="sm" onClick={() => setOpen(true)}>
88
+ <Plus />
89
+ New contact
90
+ </Button>
91
+ </PageHeader>
92
+
93
+ <DataTable
94
+ rows={rows}
95
+ columns={columns}
96
+ empty={
97
+ <EmptyState
98
+ icon={<Users />}
99
+ title="No contacts yet"
100
+ description="The people you actually talk to. Add one and link it to a company to keep the account together."
101
+ action={
102
+ <Button size="sm" onClick={() => setOpen(true)}>
103
+ <Plus />
104
+ New contact
105
+ </Button>
106
+ }
107
+ />
108
+ }
109
+ />
110
+
111
+ <RecordDialog
112
+ open={open}
113
+ title="New contact"
114
+ submitLabel="Create contact"
115
+ onOpenChange={setOpen}
116
+ fields={[
117
+ { name: "name", label: "Name", required: true, placeholder: "Dana Whitfield" },
118
+ { name: "title", label: "Title", placeholder: "VP Operations" },
119
+ {
120
+ name: "companyId",
121
+ label: "Company",
122
+ options: data.companies
123
+ .slice()
124
+ .sort((a, b) => a.name.localeCompare(b.name))
125
+ .map((company) => ({ value: company.id, label: company.name })),
126
+ },
127
+ { name: "email", label: "Email", type: "email", placeholder: "dana@acme.com" },
128
+ { name: "phone", label: "Phone", type: "tel", placeholder: "+1 555 0100" },
129
+ ]}
130
+ onCreate={async (values) => {
131
+ await db.insert("Contact", values);
132
+ }}
133
+ />
134
+ </>
135
+ );
136
+ }}
137
+ </Workspace>
138
+ );
139
+ }
@@ -0,0 +1,17 @@
1
+ import React, { use } from "react";
2
+ import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { ContactsView } from "./contacts-view";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "Contacts",
7
+ robots: "noindex",
8
+ };
9
+
10
+ export default function ContactsPage({ 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 <ContactsView email={me?.email ?? ""} />;
17
+ }
@@ -0,0 +1,189 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Link, callFn, db, useRouter } from "@pylonsync/react";
5
+ import { ArrowLeft, Trash2 } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { Select } from "@/components/ui/select";
8
+ import { PageHeader } from "@/components/page-header";
9
+ import { EmptyState } from "@/components/empty-state";
10
+ import { ActivityTimeline } from "@/components/activity-timeline";
11
+ import { StageBadge } from "@/components/stage-badge";
12
+ import { Avatar } from "@/components/avatar";
13
+ import { PIPELINE, daysUntil, money, relativeTime } from "@/lib/pipeline";
14
+ import { Workspace } from "../../workspace";
15
+
16
+ export function DealView({ email, dealId }: { email: string; dealId: string }) {
17
+ const router = useRouter();
18
+
19
+ return (
20
+ <Workspace email={email} pathname="/">
21
+ {(data) => {
22
+ const deal = data.deals.find((d) => d.id === dealId);
23
+
24
+ if (!deal) {
25
+ return (
26
+ <>
27
+ <PageHeader title="Deal" />
28
+ <EmptyState
29
+ title={data.loading ? "Loading…" : "Deal not found"}
30
+ description={
31
+ data.loading
32
+ ? undefined
33
+ : "It may have been deleted, or the link is wrong."
34
+ }
35
+ action={
36
+ data.loading ? undefined : (
37
+ <Button size="sm" variant="secondary" asChild>
38
+ <Link href="/">Back to pipeline</Link>
39
+ </Button>
40
+ )
41
+ }
42
+ />
43
+ </>
44
+ );
45
+ }
46
+
47
+ const contact = data.contacts.find((c) => c.id === deal.contactId);
48
+ const activities = data.activities.filter((a) => a.dealId === deal.id);
49
+ const days = daysUntil(deal.closeDate);
50
+
51
+ return (
52
+ <>
53
+ <PageHeader title={deal.title}>
54
+ <Button
55
+ size="sm"
56
+ variant="ghost"
57
+ onClick={async () => {
58
+ await db.delete("Deal", deal.id);
59
+ router.push("/");
60
+ }}
61
+ className="text-muted-foreground hover:text-destructive"
62
+ >
63
+ <Trash2 />
64
+ Delete
65
+ </Button>
66
+ </PageHeader>
67
+
68
+ <div className="min-h-0 flex-1 overflow-y-auto">
69
+ <div className="mx-auto flex max-w-3xl flex-col gap-6 px-6 py-6">
70
+ <Link
71
+ href="/"
72
+ className="inline-flex w-fit items-center gap-1.5 text-[12px] text-muted-foreground transition-colors hover:text-foreground"
73
+ >
74
+ <ArrowLeft className="size-3.5" />
75
+ Pipeline
76
+ </Link>
77
+
78
+ <div className="flex flex-wrap items-baseline gap-x-4 gap-y-2">
79
+ <h1 className="text-[22px] font-semibold tracking-tight">
80
+ {deal.title}
81
+ </h1>
82
+ <span className="tabular text-[18px] font-medium text-muted-foreground">
83
+ {money(deal.value)}
84
+ </span>
85
+ </div>
86
+
87
+ <dl className="grid grid-cols-2 gap-x-6 gap-y-4 rounded-lg border border-border bg-card p-4 sm:grid-cols-4">
88
+ <Field label="Stage">
89
+ {/* Changing stage here goes through the same mutation the
90
+ board uses, so it's logged identically. */}
91
+ <Select
92
+ aria-label="Stage"
93
+ value={deal.stage}
94
+ className="h-7 text-[12px]"
95
+ onChange={(event) =>
96
+ void callFn("moveDeal", {
97
+ dealId: deal.id,
98
+ stage: event.target.value,
99
+ })
100
+ }
101
+ >
102
+ {PIPELINE.map((stage) => (
103
+ <option key={stage.id} value={stage.id}>
104
+ {stage.label}
105
+ </option>
106
+ ))}
107
+ </Select>
108
+ </Field>
109
+
110
+ <Field label="Company">
111
+ {data.companyName(deal.companyId) ?? "—"}
112
+ </Field>
113
+
114
+ <Field label="Contact">
115
+ {contact ? (
116
+ <span className="flex items-center gap-1.5">
117
+ <Avatar name={contact.name} size="sm" />
118
+ {contact.name}
119
+ </span>
120
+ ) : (
121
+ "—"
122
+ )}
123
+ </Field>
124
+
125
+ <Field label="Expected close">
126
+ {deal.closeDate ? (
127
+ <span className={days !== null && days < 0 ? "text-destructive" : ""}>
128
+ {new Date(deal.closeDate).toLocaleDateString("en-US", {
129
+ month: "short",
130
+ day: "numeric",
131
+ year: "numeric",
132
+ })}
133
+ </span>
134
+ ) : (
135
+ "—"
136
+ )}
137
+ </Field>
138
+ </dl>
139
+
140
+ <div className="flex items-center gap-3 text-[12px] text-muted-foreground">
141
+ <StageBadge stage={deal.stage} />
142
+ <span aria-hidden="true">·</span>
143
+ <span>Created {relativeTime(deal.createdAt)}</span>
144
+ {data.ownerName(deal.ownerId) ? (
145
+ <>
146
+ <span aria-hidden="true">·</span>
147
+ <span>Owned by {data.ownerName(deal.ownerId)}</span>
148
+ </>
149
+ ) : null}
150
+ </div>
151
+
152
+ <section>
153
+ <h2 className="mb-3 text-[13px] font-semibold">Activity</h2>
154
+ <ActivityTimeline
155
+ activities={activities}
156
+ ownerName={data.ownerName}
157
+ onLog={(kind, body) =>
158
+ callFn("logActivity", {
159
+ kind,
160
+ body,
161
+ dealId: deal.id,
162
+ ...(deal.companyId ? { companyId: deal.companyId } : {}),
163
+ })
164
+ }
165
+ />
166
+ </section>
167
+ </div>
168
+ </div>
169
+ </>
170
+ );
171
+ }}
172
+ </Workspace>
173
+ );
174
+ }
175
+
176
+ function Field({
177
+ label,
178
+ children,
179
+ }: {
180
+ label: string;
181
+ children: React.ReactNode;
182
+ }) {
183
+ return (
184
+ <div className="min-w-0">
185
+ <dt className="text-[11px] text-muted-foreground">{label}</dt>
186
+ <dd className="mt-1 truncate text-[13px]">{children}</dd>
187
+ </div>
188
+ );
189
+ }
@@ -0,0 +1,23 @@
1
+ import React, { use } from "react";
2
+ import type { Metadata, PageProps } from "@pylonsync/react";
3
+ import { DealView } from "./deal-view";
4
+
5
+ export const metadata: Metadata = {
6
+ title: "Deal",
7
+ robots: "noindex",
8
+ };
9
+
10
+ /** `app/deals/[id]/page.tsx` → `/deals/:id`. */
11
+ export default function DealPage({
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 <DealView email={me?.email ?? ""} dealId={params.id} />;
23
+ }
@@ -0,0 +1,43 @@
1
+ import React from "react";
2
+ import { type ErrorBoundaryProps } from "@pylonsync/react";
3
+ import { Button } from "@/components/ui/button";
4
+
5
+ // `app/error.tsx` → the error boundary for this segment. It catches a throw
6
+ // in any page/layout below it and renders at HTTP 500. It's HYDRATED, so
7
+ // this is a real interactive client component: `reset()` re-attempts the
8
+ // route, and useState/onClick work. The thrown error reaches the client as
9
+ // `{ message, digest }` only — the stack stays in the dev overlay
10
+ // (PYLON_DEV_MODE) and the server logs, never in the page.
11
+ export default function Error({ error, reset }: ErrorBoundaryProps) {
12
+ const [tries, setTries] = React.useState(0);
13
+ return (
14
+ <div className="space-y-6">
15
+ <section>
16
+ <h1 className="text-2xl font-semibold tracking-tight">
17
+ Something went wrong
18
+ </h1>
19
+ <p className="mt-2 text-muted-foreground">{error.message}</p>
20
+ {error.digest ? (
21
+ <p className="mt-1 text-xs text-muted-foreground/70">
22
+ Reference: <code>{error.digest}</code>
23
+ </p>
24
+ ) : null}
25
+ </section>
26
+ <div className="flex items-center gap-3">
27
+ <Button
28
+ onClick={() => {
29
+ setTries((n) => n + 1);
30
+ reset();
31
+ }}
32
+ >
33
+ Try again
34
+ </Button>
35
+ {tries > 0 ? (
36
+ <span className="text-sm text-muted-foreground">
37
+ Retried {tries} {tries === 1 ? "time" : "times"}
38
+ </span>
39
+ ) : null}
40
+ </div>
41
+ </div>
42
+ );
43
+ }