@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,164 @@
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { Link, callFn, useRouter } from "@pylonsync/react";
5
+ import { ArrowLeft } 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 { Thread } from "@/components/thread";
11
+ import { PriorityBadge } from "@/components/priority-badge";
12
+ import { SlaIndicator } from "@/components/sla-indicator";
13
+ import { Avatar } from "@/components/avatar";
14
+ import { relativeTime } from "@/lib/format";
15
+ import { PRIORITIES, STATUSES, ticketNumber } from "@/lib/tickets";
16
+ import { Workspace } from "../../workspace";
17
+
18
+ export function TicketView({
19
+ email,
20
+ ticketId,
21
+ }: {
22
+ email: string;
23
+ ticketId: string;
24
+ }) {
25
+ const router = useRouter();
26
+
27
+ return (
28
+ <Workspace email={email} pathname="/">
29
+ {(data) => {
30
+ const ticket = data.tickets.find((t) => t.id === ticketId);
31
+
32
+ if (!ticket) {
33
+ return (
34
+ <>
35
+ <PageHeader title="Ticket" />
36
+ <EmptyState
37
+ title={data.loading ? "Loading…" : "Ticket not found"}
38
+ description={
39
+ data.loading
40
+ ? undefined
41
+ : "It may have been deleted, or the link is wrong."
42
+ }
43
+ action={
44
+ data.loading ? undefined : (
45
+ <Button size="sm" variant="secondary" asChild>
46
+ <Link href="/">Back to inbox</Link>
47
+ </Button>
48
+ )
49
+ }
50
+ />
51
+ </>
52
+ );
53
+ }
54
+
55
+ const customer = data.customers.find((c) => c.id === ticket.customerId);
56
+ const messages = data.messages.filter((m) => m.ticketId === ticket.id);
57
+
58
+ return (
59
+ <>
60
+ <PageHeader title={ticket.subject}>
61
+ <SlaIndicator ticket={ticket} />
62
+ <div className="w-28">
63
+ <Select
64
+ aria-label="Priority"
65
+ value={ticket.priority}
66
+ className="h-7 text-[12px]"
67
+ onChange={(event) =>
68
+ void callFn("setTicketState", {
69
+ ticketId: ticket.id,
70
+ priority: event.target.value,
71
+ })
72
+ }
73
+ >
74
+ {PRIORITIES.map((p) => (
75
+ <option key={p.id} value={p.id}>
76
+ {p.label}
77
+ </option>
78
+ ))}
79
+ </Select>
80
+ </div>
81
+ <div className="w-28">
82
+ <Select
83
+ aria-label="Status"
84
+ value={ticket.status}
85
+ className="h-7 text-[12px]"
86
+ onChange={(event) =>
87
+ void callFn("setTicketState", {
88
+ ticketId: ticket.id,
89
+ status: event.target.value,
90
+ })
91
+ }
92
+ >
93
+ {STATUSES.map((s) => (
94
+ <option key={s.id} value={s.id}>
95
+ {s.label}
96
+ </option>
97
+ ))}
98
+ </Select>
99
+ </div>
100
+ </PageHeader>
101
+
102
+ <div className="flex items-center gap-3 border-b border-border px-6 py-2.5 text-[12px] text-muted-foreground">
103
+ <Link
104
+ href="/"
105
+ className="inline-flex items-center gap-1.5 transition-colors hover:text-foreground"
106
+ >
107
+ <ArrowLeft className="size-3.5" />
108
+ Inbox
109
+ </Link>
110
+ <span aria-hidden="true">·</span>
111
+ <span className="tabular">
112
+ {ticketNumber(ticket.id, ticket.createdAt)}
113
+ </span>
114
+ <span aria-hidden="true">·</span>
115
+ <PriorityBadge priority={ticket.priority} />
116
+ {customer ? (
117
+ <>
118
+ <span aria-hidden="true">·</span>
119
+ <span className="flex items-center gap-1.5">
120
+ <Avatar name={customer.name} size="sm" />
121
+ {customer.name}
122
+ {customer.company ? ` · ${customer.company}` : ""}
123
+ </span>
124
+ </>
125
+ ) : null}
126
+ <span aria-hidden="true">·</span>
127
+ <span>Opened {relativeTime(ticket.createdAt)}</span>
128
+
129
+ <div className="ml-auto w-40">
130
+ <Select
131
+ aria-label="Assignee"
132
+ value={ticket.assigneeId ?? ""}
133
+ className="h-7 text-[12px]"
134
+ onChange={(event) =>
135
+ void callFn("setTicketState", {
136
+ ticketId: ticket.id,
137
+ assigneeId: event.target.value,
138
+ })
139
+ }
140
+ >
141
+ <option value="">Unassigned</option>
142
+ {data.agents.map((agent) => (
143
+ <option key={agent.id} value={agent.id}>
144
+ {agent.displayName || agent.email}
145
+ </option>
146
+ ))}
147
+ </Select>
148
+ </div>
149
+ </div>
150
+
151
+ <Thread
152
+ messages={messages}
153
+ authorName={data.agentName}
154
+ customerName={customer?.name ?? null}
155
+ onSend={(body, internal) =>
156
+ callFn("replyToTicket", { ticketId: ticket.id, body, internal })
157
+ }
158
+ />
159
+ </>
160
+ );
161
+ }}
162
+ </Workspace>
163
+ );
164
+ }
@@ -0,0 +1,210 @@
1
+ "use client";
2
+
3
+ import React, { useEffect, useMemo, useRef, useState } from "react";
4
+ import { callFn, db, useRouter } from "@pylonsync/react";
5
+ import { useAuth } from "@pylonsync/client";
6
+ import { Sidebar } from "@/components/sidebar";
7
+ import { CommandPalette } from "@/components/command-palette";
8
+ import type { SearchItem } from "@/lib/search";
9
+ import { counts, isOpen } from "@/lib/tickets";
10
+
11
+ export interface CustomerRow {
12
+ id: string;
13
+ name: string;
14
+ email: string;
15
+ company?: string | null;
16
+ createdAt?: string | null;
17
+ }
18
+ export interface TicketRow {
19
+ id: string;
20
+ subject: string;
21
+ status: string;
22
+ priority: string;
23
+ customerId?: string | null;
24
+ assigneeId?: string | null;
25
+ firstRespondedAt?: string | null;
26
+ createdAt?: string | null;
27
+ updatedAt?: string | null;
28
+ }
29
+ export interface MessageRow {
30
+ id: string;
31
+ ticketId: string;
32
+ body: string;
33
+ fromCustomer?: boolean | null;
34
+ internal?: boolean | null;
35
+ authorId?: string | null;
36
+ createdAt?: string | null;
37
+ }
38
+ export interface UserRow {
39
+ id: string;
40
+ email: string;
41
+ displayName?: string | null;
42
+ }
43
+
44
+ export interface WorkspaceData {
45
+ customers: CustomerRow[];
46
+ tickets: TicketRow[];
47
+ messages: MessageRow[];
48
+ agents: UserRow[];
49
+ customerName: (id: string | null | undefined) => string | null;
50
+ agentName: (id: string | null | undefined) => string | null;
51
+ loading: boolean;
52
+ }
53
+
54
+ /**
55
+ * The app shell, and the ONLY place that touches `db`.
56
+ *
57
+ * Every view below receives plain data through the render prop and reports
58
+ * changes through callbacks, which is what lets the components in `components/`
59
+ * render from fixtures in a test with nothing mocked.
60
+ *
61
+ * Each `db.useQuery` is live: a ticket another agent assigns, or a reply they
62
+ * send, arrives here and re-renders the queue. Your own writes take the same
63
+ * path, so there's one behaviour to reason about rather than two.
64
+ */
65
+ export function Workspace({
66
+ email,
67
+ pathname,
68
+ children,
69
+ }: {
70
+ email: string;
71
+ pathname: string;
72
+ children: (data: WorkspaceData) => React.ReactNode;
73
+ }) {
74
+ const router = useRouter();
75
+ const { signOut } = useAuth();
76
+ const { data: tickets, loading } = db.useQuery<TicketRow>("Ticket");
77
+ const { data: customers } = db.useQuery<CustomerRow>("Customer");
78
+ const { data: messages } = db.useQuery<MessageRow>("Message");
79
+ const { data: users } = db.useQuery<UserRow>("User");
80
+
81
+ const [paletteOpen, setPaletteOpen] = useState(false);
82
+ const seeded = useRef(false);
83
+
84
+ const ticketList = tickets ?? [];
85
+ const customerList = customers ?? [];
86
+ const agentList = users ?? [];
87
+
88
+ // A brand-new helpdesk gets a realistic queue once, so the inbox never opens
89
+ // empty. `seedWorkspace` no-ops when anything exists; the ref stops a
90
+ // re-render from firing a second call mid-flight.
91
+ useEffect(() => {
92
+ if (loading || seeded.current) return;
93
+ if (ticketList.length > 0) {
94
+ seeded.current = true;
95
+ return;
96
+ }
97
+ seeded.current = true;
98
+ callFn("seedWorkspace", {}).catch(() => {
99
+ // A failed seed leaves an empty inbox, which is usable — the queue has an
100
+ // empty state that offers to open the first ticket.
101
+ seeded.current = false;
102
+ });
103
+ }, [loading, ticketList.length]);
104
+
105
+ useEffect(() => {
106
+ function onKey(event: KeyboardEvent) {
107
+ const target = event.target as HTMLElement | null;
108
+ const typing =
109
+ !!target &&
110
+ (target.tagName === "INPUT" ||
111
+ target.tagName === "TEXTAREA" ||
112
+ target.isContentEditable);
113
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
114
+ event.preventDefault();
115
+ setPaletteOpen((open) => !open);
116
+ } else if (event.key === "/" && !typing) {
117
+ event.preventDefault();
118
+ setPaletteOpen(true);
119
+ }
120
+ }
121
+ window.addEventListener("keydown", onKey);
122
+ return () => window.removeEventListener("keydown", onKey);
123
+ }, []);
124
+
125
+ const lookups = useMemo(() => {
126
+ const customerById = new Map(customerList.map((c) => [c.id, c] as const));
127
+ const userById = new Map(agentList.map((u) => [u.id, u] as const));
128
+ return { customerById, userById };
129
+ }, [customerList, agentList]);
130
+
131
+ const searchIndex = useMemo<SearchItem[]>(
132
+ () => [
133
+ ...ticketList.map((ticket) => ({
134
+ id: `ticket:${ticket.id}`,
135
+ type: "deal" as const,
136
+ title: ticket.subject,
137
+ subtitle: lookups.customerById.get(ticket.customerId ?? "")?.name,
138
+ href: `/tickets/${ticket.id}`,
139
+ })),
140
+ ...customerList.map((customer) => ({
141
+ id: `customer:${customer.id}`,
142
+ type: "contact" as const,
143
+ title: customer.name,
144
+ subtitle: customer.company ?? undefined,
145
+ href: "/customers",
146
+ keywords: customer.email,
147
+ })),
148
+ ],
149
+ [ticketList, customerList, lookups],
150
+ );
151
+
152
+ const queue = counts(ticketList);
153
+
154
+ const data: WorkspaceData = {
155
+ customers: customerList,
156
+ tickets: ticketList,
157
+ messages: messages ?? [],
158
+ agents: agentList,
159
+ customerName: (id) => lookups.customerById.get(id ?? "")?.name ?? null,
160
+ agentName: (id) => {
161
+ const user = lookups.userById.get(id ?? "");
162
+ return user?.displayName || user?.email || null;
163
+ },
164
+ loading,
165
+ };
166
+
167
+ return (
168
+ <div className="flex h-screen overflow-hidden">
169
+ <Sidebar
170
+ workspace="Helpdesk"
171
+ email={email}
172
+ pathname={pathname}
173
+ counts={{
174
+ "/": ticketList.filter(isOpen).length,
175
+ "/customers": customerList.length,
176
+ }}
177
+ onOpenCommand={() => setPaletteOpen(true)}
178
+ onSignOut={async () => {
179
+ await signOut();
180
+ window.location.assign("/login");
181
+ }}
182
+ />
183
+ <main className="flex min-w-0 flex-1 flex-col">{children(data)}</main>
184
+
185
+ <CommandPalette
186
+ open={paletteOpen}
187
+ items={searchIndex}
188
+ actions={[
189
+ {
190
+ id: "new-ticket",
191
+ label: "New ticket",
192
+ run: () => router.push("/?new=ticket"),
193
+ },
194
+ {
195
+ id: "unassigned",
196
+ label: `Unassigned (${queue.unassigned})`,
197
+ run: () => router.push("/?filter=unassigned"),
198
+ },
199
+ {
200
+ id: "go-customers",
201
+ label: "Go to Customers",
202
+ run: () => router.push("/customers"),
203
+ },
204
+ ]}
205
+ onClose={() => setPaletteOpen(false)}
206
+ onSelect={(item) => router.push(item.href)}
207
+ />
208
+ </div>
209
+ );
210
+ }
@@ -0,0 +1,142 @@
1
+ import {
2
+ entity,
3
+ field,
4
+ policy,
5
+ auth,
6
+ buildManifest,
7
+ discoverAppRoutes,
8
+ font,
9
+ } from "@pylonsync/sdk";
10
+
11
+ // ---------------------------------------------------------------------------
12
+ // helpdesk — a support inbox for a small team: customers write in, agents triage
13
+ // by priority and SLA, and every reply lands on a shared thread.
14
+ //
15
+ // The realtime hook: the queue subscribes to Ticket, so a new ticket appears at
16
+ // the top of everyone's inbox as it arrives, and an assignment shows up before
17
+ // two agents start the same reply.
18
+ //
19
+ // • Customer — the person who wrote in. Holds email, so never public.
20
+ // • Ticket — subject, status, priority, assignee. The queue.
21
+ // • Message — one entry on a thread; `internal` marks an agent-only note.
22
+ // • User — an agent (email/password).
23
+ //
24
+ // TRUST MODEL: an internal tool for ONE support team. Every entity is readable
25
+ // and writable by any SIGNED-IN user and by nobody else. There is no customer
26
+ // portal here — adding one means a separate, narrower policy scoped to the
27
+ // customer's own tickets, NOT loosening these.
28
+ // ---------------------------------------------------------------------------
29
+
30
+ const TEAM = "auth.userId != null";
31
+
32
+ const Customer = entity(
33
+ "Customer",
34
+ {
35
+ name: field.string(),
36
+ email: field.string(),
37
+ company: field.string().optional(),
38
+ createdAt: field.datetime().defaultNow(),
39
+ },
40
+ { indexes: [{ name: "by_email", fields: ["email"], unique: false }] },
41
+ );
42
+
43
+ const Ticket = entity(
44
+ "Ticket",
45
+ {
46
+ subject: field.string(),
47
+ // "open" | "pending" | "solved" | "closed" — see STATUSES in lib/tickets.ts.
48
+ status: field.string().default("open"),
49
+ // "urgent" | "high" | "normal" | "low" — sets the first-response window.
50
+ priority: field.string().default("normal"),
51
+ customerId: field.id("Customer").optional(),
52
+ assigneeId: field.id("User").optional(),
53
+ // When an agent first replied. Null means the customer is still waiting,
54
+ // which is what the SLA state is computed from.
55
+ firstRespondedAt: field.datetime().optional(),
56
+ createdAt: field.datetime().defaultNow(),
57
+ updatedAt: field.datetime().defaultNow(),
58
+ },
59
+ {
60
+ indexes: [
61
+ { name: "by_status", fields: ["status"], unique: false },
62
+ { name: "by_assignee", fields: ["assigneeId"], unique: false },
63
+ ],
64
+ },
65
+ );
66
+
67
+ const Message = entity(
68
+ "Message",
69
+ {
70
+ ticketId: field.id("Ticket"),
71
+ body: field.string(),
72
+ // True when the customer wrote it rather than an agent.
73
+ fromCustomer: field.boolean().default(false),
74
+ // An internal note stays with the team if you later add a customer portal.
75
+ internal: field.boolean().default(false),
76
+ authorId: field.id("User").readonly().optional(),
77
+ createdAt: field.datetime().defaultNow(),
78
+ },
79
+ { indexes: [{ name: "by_ticket", fields: ["ticketId"], unique: false }] },
80
+ );
81
+
82
+ const User = entity(
83
+ "User",
84
+ {
85
+ email: field.string(),
86
+ displayName: field.string().optional(),
87
+ passwordHash: field.string().serverOnly().optional(),
88
+ avatarColor: field.string().optional(),
89
+ emailVerified: field.datetime().optional(),
90
+ createdAt: field.datetime().defaultNow(),
91
+ },
92
+ { indexes: [{ name: "by_email", fields: ["email"], unique: true }] },
93
+ );
94
+
95
+ const teamPolicy = (name: string, entityName: string) =>
96
+ policy({
97
+ name,
98
+ entity: entityName,
99
+ allowRead: TEAM,
100
+ allowInsert: TEAM,
101
+ allowUpdate: TEAM,
102
+ allowDelete: TEAM,
103
+ });
104
+
105
+ const userPolicy = policy({
106
+ name: "user_team_read",
107
+ entity: "User",
108
+ allowRead: TEAM,
109
+ allowInsert: "false",
110
+ allowUpdate: "false",
111
+ allowDelete: "false",
112
+ });
113
+
114
+ const manifest = buildManifest({
115
+ name: "__APP_NAME__",
116
+ version: "0.1.0",
117
+ entities: [Customer, Ticket, Message, User],
118
+ queries: [],
119
+ actions: [],
120
+ policies: [
121
+ teamPolicy("customer_team", "Customer"),
122
+ teamPolicy("ticket_team", "Ticket"),
123
+ teamPolicy("message_team", "Message"),
124
+ userPolicy,
125
+ ],
126
+ auth: auth(),
127
+ fonts: [
128
+ font({
129
+ family: "Inter",
130
+ variable: "--font-sans",
131
+ weights: ["400", "500", "600", "700"],
132
+ subsets: ["latin"],
133
+ display: "swap",
134
+ preload: true,
135
+ }),
136
+ ],
137
+ routes: await discoverAppRoutes(),
138
+ });
139
+
140
+ console.log(JSON.stringify(manifest, null, 2));
141
+
142
+ export default manifest;
@@ -0,0 +1,4 @@
1
+ [test]
2
+ # Register a DOM (happy-dom) before any test runs so React component tests can
3
+ # render under `pylon test` / `bun test`. Harmless for pure-logic tests.
4
+ preload = ["./tests/setup.ts"]
@@ -0,0 +1,40 @@
1
+ import React from "react";
2
+ import { cn } from "@/lib/utils";
3
+ import { accentIndex, initials } from "@/lib/format";
4
+
5
+ // Deterministic accent per record — the same company keeps its colour across
6
+ // sessions and machines without storing one. Hues are muted so a dense list
7
+ // doesn't turn into confetti.
8
+ const ACCENTS = [
9
+ "bg-indigo-500/15 text-indigo-500 dark:text-indigo-300",
10
+ "bg-emerald-500/15 text-emerald-600 dark:text-emerald-300",
11
+ "bg-amber-500/15 text-amber-600 dark:text-amber-300",
12
+ "bg-rose-500/15 text-rose-500 dark:text-rose-300",
13
+ "bg-sky-500/15 text-sky-600 dark:text-sky-300",
14
+ "bg-violet-500/15 text-violet-500 dark:text-violet-300",
15
+ ];
16
+
17
+ export function Avatar({
18
+ name,
19
+ size = "md",
20
+ className,
21
+ }: {
22
+ name: string | null | undefined;
23
+ size?: "sm" | "md";
24
+ className?: string;
25
+ }) {
26
+ const label = initials(name);
27
+ return (
28
+ <span
29
+ aria-hidden="true"
30
+ className={cn(
31
+ "inline-flex shrink-0 select-none items-center justify-center rounded-full font-medium",
32
+ size === "sm" ? "size-5 text-[9px]" : "size-6 text-[10px]",
33
+ ACCENTS[accentIndex(name ?? "?", ACCENTS.length)],
34
+ className,
35
+ )}
36
+ >
37
+ {label}
38
+ </span>
39
+ );
40
+ }