@pylonsync/create-pylon 0.19.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (696) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/_root/AGENTS.md +1 -1
  4. package/templates/agency/.env.example +7 -0
  5. package/templates/agency/AGENTS.md +1 -1
  6. package/templates/agency/README.md +28 -3
  7. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  8. package/templates/agency/app/(marketing)/page.tsx +3 -3
  9. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  10. package/templates/agency/app/auth-form.tsx +4 -4
  11. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  12. package/templates/agency/app/dashboard/page.tsx +14 -16
  13. package/templates/agency/app/icon.svg +3 -0
  14. package/templates/agency/app/login/page.tsx +12 -14
  15. package/templates/agency/components/marketing.tsx +12 -5
  16. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  17. package/templates/agency/lib/agency.ts +22 -0
  18. package/templates/agency/lib/demo.ts +118 -0
  19. package/templates/agency/lib/site.config.ts +19 -13
  20. package/templates/agency/public/favicon.ico +0 -0
  21. package/templates/agency/tests/agency.test.ts +57 -0
  22. package/templates/ai-chat/.env.example +13 -27
  23. package/templates/ai-chat/AGENTS.md +1 -1
  24. package/templates/ai-chat/README.md +53 -60
  25. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  26. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  27. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  28. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  29. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  30. package/templates/ai-chat/app/auth-form.tsx +45 -38
  31. package/templates/ai-chat/app/error.tsx +9 -12
  32. package/templates/ai-chat/app/globals.css +464 -102
  33. package/templates/ai-chat/app/layout.tsx +11 -19
  34. package/templates/ai-chat/app/login/page.tsx +39 -24
  35. package/templates/ai-chat/app.ts +118 -43
  36. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  37. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  38. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  39. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  40. package/templates/ai-chat/components/chat/message.tsx +183 -0
  41. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  42. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  43. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  44. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  45. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  46. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  47. package/templates/ai-chat/functions/createConversation.ts +28 -0
  48. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  49. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  50. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  51. package/templates/ai-chat/functions/respond.ts +88 -0
  52. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  53. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  54. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  55. package/templates/ai-chat/lib/chat.ts +277 -0
  56. package/templates/ai-chat/lib/claim.ts +15 -0
  57. package/templates/ai-chat/lib/examples.ts +179 -0
  58. package/templates/ai-chat/lib/highlight.ts +107 -0
  59. package/templates/ai-chat/lib/markdown.ts +317 -0
  60. package/templates/ai-chat/lib/provider.ts +38 -0
  61. package/templates/ai-chat/lib/site.config.ts +55 -39
  62. package/templates/ai-chat/tests/chat.test.ts +174 -0
  63. package/templates/ai-chat/tests/components.test.tsx +57 -0
  64. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  65. package/templates/ai-chat/tests/provider.test.ts +32 -0
  66. package/templates/ai-studio/AGENTS.md +1 -1
  67. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  68. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  69. package/templates/backend/consumer/apps/api/app.ts +166 -0
  70. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  71. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  72. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  73. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  74. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  75. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  76. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  77. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  78. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  79. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  80. package/templates/backend/consumer/apps/api/package.json +8 -2
  81. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  85. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  86. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  87. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  88. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  89. package/templates/barebones/AGENTS.md +1 -1
  90. package/templates/chat/AGENTS.md +1 -1
  91. package/templates/chat/README.md +43 -21
  92. package/templates/chat/app/chat-client.tsx +495 -85
  93. package/templates/chat/app/error.tsx +1 -1
  94. package/templates/chat/app/globals.css +170 -54
  95. package/templates/chat/app/layout.tsx +12 -9
  96. package/templates/chat/app/not-found.tsx +4 -2
  97. package/templates/chat/app/page.tsx +9 -19
  98. package/templates/chat/app.ts +96 -17
  99. package/templates/chat/components/chat/avatar.tsx +45 -0
  100. package/templates/chat/components/chat/composer.tsx +110 -0
  101. package/templates/chat/components/chat/message-list.tsx +285 -0
  102. package/templates/chat/components/chat/sidebar.tsx +190 -0
  103. package/templates/chat/functions/joinChat.ts +83 -0
  104. package/templates/chat/functions/renameMember.ts +25 -0
  105. package/templates/chat/lib/chat.ts +188 -0
  106. package/templates/chat/lib/seed.ts +78 -0
  107. package/templates/chat/tests/chat.test.ts +159 -0
  108. package/templates/chat/tests/composer.test.tsx +54 -0
  109. package/templates/consumer/AGENTS.md +1 -1
  110. package/templates/consumer/README.md +3 -3
  111. package/templates/consumer/app/globals.css +10 -3
  112. package/templates/consumer/app/icon.svg +1 -0
  113. package/templates/consumer/components/social/avatar.tsx +25 -11
  114. package/templates/consumer/functions/seedFeed.ts +3 -3
  115. package/templates/consumer/lib/seed.ts +43 -85
  116. package/templates/consumer/public/favicon.ico +0 -0
  117. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  118. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  119. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  120. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  121. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  122. package/templates/consumer/public/images/posts/run.jpg +0 -0
  123. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  124. package/templates/consumer/tests/seed.test.ts +39 -0
  125. package/templates/creator/.env.example +7 -0
  126. package/templates/creator/AGENTS.md +1 -1
  127. package/templates/creator/README.md +24 -5
  128. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  129. package/templates/creator/app/auth-form.tsx +7 -7
  130. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  131. package/templates/creator/app/dashboard/page.tsx +18 -18
  132. package/templates/creator/app/icon.svg +2 -0
  133. package/templates/creator/app/login/page.tsx +14 -14
  134. package/templates/creator/app.ts +4 -4
  135. package/templates/creator/functions/seedDemo.ts +32 -0
  136. package/templates/creator/functions/subscribe.ts +4 -15
  137. package/templates/creator/lib/demo.ts +94 -0
  138. package/templates/creator/lib/site.config.ts +15 -10
  139. package/templates/creator/lib/stats.ts +35 -0
  140. package/templates/creator/public/favicon.ico +0 -0
  141. package/templates/creator/tests/counter.test.ts +92 -0
  142. package/templates/creator/tests/live-count.test.tsx +45 -0
  143. package/templates/crm/AGENTS.md +1 -1
  144. package/templates/crm/README.md +22 -1
  145. package/templates/crm/app/apple-icon.png +0 -0
  146. package/templates/crm/app/companies/companies-view.tsx +32 -14
  147. package/templates/crm/app/companies/page.tsx +2 -1
  148. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  149. package/templates/crm/app/contacts/page.tsx +2 -1
  150. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  151. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  152. package/templates/crm/app/globals.css +29 -14
  153. package/templates/crm/app/icon.svg +1 -0
  154. package/templates/crm/app/layout.tsx +7 -5
  155. package/templates/crm/app/page.tsx +2 -1
  156. package/templates/crm/app/pipeline-view.tsx +5 -5
  157. package/templates/crm/app/workspace.tsx +29 -17
  158. package/templates/crm/app.ts +5 -5
  159. package/templates/crm/components/activity-timeline.tsx +14 -4
  160. package/templates/crm/components/app-shell.tsx +94 -0
  161. package/templates/crm/components/auth-form.tsx +35 -5
  162. package/templates/crm/components/avatar.tsx +5 -1
  163. package/templates/crm/components/board-skeleton.tsx +5 -5
  164. package/templates/crm/components/brand-mark.tsx +25 -0
  165. package/templates/crm/components/command-palette.tsx +17 -17
  166. package/templates/crm/components/data-table.tsx +97 -36
  167. package/templates/crm/components/deal-card.tsx +23 -27
  168. package/templates/crm/components/filter-tabs.tsx +101 -0
  169. package/templates/crm/components/metrics-bar.tsx +6 -6
  170. package/templates/crm/components/nav.tsx +15 -0
  171. package/templates/crm/components/page-header.tsx +43 -7
  172. package/templates/crm/components/pipeline-board.tsx +16 -10
  173. package/templates/crm/components/require-auth.tsx +24 -26
  174. package/templates/crm/components/sidebar.tsx +54 -49
  175. package/templates/crm/components/stage-badge.tsx +4 -2
  176. package/templates/crm/components/ui/button.tsx +5 -4
  177. package/templates/crm/components/ui/dialog.tsx +1 -1
  178. package/templates/crm/components/ui/input.tsx +1 -1
  179. package/templates/crm/components/ui/select.tsx +1 -1
  180. package/templates/crm/components/ui/textarea.tsx +1 -1
  181. package/templates/crm/functions/seedWorkspace.ts +32 -17
  182. package/templates/crm/lib/brand.ts +9 -0
  183. package/templates/crm/lib/demo-team.ts +51 -0
  184. package/templates/crm/lib/pipeline.ts +37 -0
  185. package/templates/crm/lib/search.ts +4 -3
  186. package/templates/crm/lib/seed.ts +53 -8
  187. package/templates/crm/public/favicon.ico +0 -0
  188. package/templates/crm/tests/components.test.tsx +5 -5
  189. package/templates/crm/tests/demo-team.test.ts +18 -0
  190. package/templates/crm/tests/pipeline.test.ts +59 -3
  191. package/templates/directory/.env.example +7 -0
  192. package/templates/directory/AGENTS.md +1 -1
  193. package/templates/directory/README.md +22 -3
  194. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  195. package/templates/directory/app/(marketing)/page.tsx +6 -4
  196. package/templates/directory/app/auth-form.tsx +4 -4
  197. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  198. package/templates/directory/app/dashboard/page.tsx +16 -14
  199. package/templates/directory/app/icon.svg +4 -0
  200. package/templates/directory/app/login/page.tsx +16 -15
  201. package/templates/directory/functions/seedDemo.ts +43 -0
  202. package/templates/directory/lib/demo.ts +145 -0
  203. package/templates/directory/lib/directory.ts +27 -0
  204. package/templates/directory/lib/site.config.ts +21 -16
  205. package/templates/directory/public/favicon.ico +0 -0
  206. package/templates/directory/tests/directory.test.ts +46 -0
  207. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  208. package/templates/expo/consumer/apps/expo/app.json +9 -1
  209. package/templates/expo/consumer/apps/expo/package.json +2 -1
  210. package/templates/helpdesk/AGENTS.md +1 -1
  211. package/templates/helpdesk/README.md +21 -0
  212. package/templates/helpdesk/app/apple-icon.png +0 -0
  213. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  214. package/templates/helpdesk/app/customers/page.tsx +2 -1
  215. package/templates/helpdesk/app/globals.css +29 -14
  216. package/templates/helpdesk/app/icon.svg +1 -0
  217. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  218. package/templates/helpdesk/app/layout.tsx +7 -5
  219. package/templates/helpdesk/app/page.tsx +4 -7
  220. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  221. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  222. package/templates/helpdesk/app/workspace.tsx +33 -22
  223. package/templates/helpdesk/app.ts +2 -2
  224. package/templates/helpdesk/components/app-shell.tsx +94 -0
  225. package/templates/helpdesk/components/auth-form.tsx +35 -5
  226. package/templates/helpdesk/components/avatar.tsx +5 -1
  227. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  228. package/templates/helpdesk/components/command-palette.tsx +17 -17
  229. package/templates/helpdesk/components/data-table.tsx +97 -36
  230. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  231. package/templates/helpdesk/components/nav.tsx +14 -0
  232. package/templates/helpdesk/components/page-header.tsx +43 -7
  233. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  234. package/templates/helpdesk/components/require-auth.tsx +24 -26
  235. package/templates/helpdesk/components/sidebar.tsx +54 -48
  236. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  237. package/templates/helpdesk/components/thread.tsx +7 -7
  238. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  239. package/templates/helpdesk/components/ui/button.tsx +5 -4
  240. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  241. package/templates/helpdesk/components/ui/input.tsx +1 -1
  242. package/templates/helpdesk/components/ui/select.tsx +1 -1
  243. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  244. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  245. package/templates/helpdesk/lib/brand.ts +9 -0
  246. package/templates/helpdesk/lib/demo-team.ts +51 -0
  247. package/templates/helpdesk/lib/search.ts +4 -3
  248. package/templates/helpdesk/lib/seed.ts +43 -2
  249. package/templates/helpdesk/lib/tickets.ts +68 -20
  250. package/templates/helpdesk/public/favicon.ico +0 -0
  251. package/templates/helpdesk/tests/components.test.tsx +10 -0
  252. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  253. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  254. package/templates/imessage/.env.example +49 -0
  255. package/templates/imessage/AGENTS.md +153 -0
  256. package/templates/imessage/CLAUDE.md +7 -0
  257. package/templates/imessage/README.md +187 -0
  258. package/templates/imessage/app/console.tsx +382 -0
  259. package/templates/imessage/app/contacts/page.tsx +13 -0
  260. package/templates/imessage/app/error.tsx +16 -0
  261. package/templates/imessage/app/gate.tsx +31 -0
  262. package/templates/imessage/app/globals.css +183 -0
  263. package/templates/imessage/app/layout.tsx +17 -0
  264. package/templates/imessage/app/login/login-form.tsx +115 -0
  265. package/templates/imessage/app/login/page.tsx +30 -0
  266. package/templates/imessage/app/not-found.tsx +13 -0
  267. package/templates/imessage/app/page.tsx +14 -0
  268. package/templates/imessage/app/robots.ts +6 -0
  269. package/templates/imessage/app/setup/page.tsx +13 -0
  270. package/templates/imessage/app/sign-out.tsx +20 -0
  271. package/templates/imessage/app.ts +299 -0
  272. package/templates/imessage/bunfig +4 -0
  273. package/templates/imessage/components/avatar.tsx +46 -0
  274. package/templates/imessage/components/contact-panel.tsx +263 -0
  275. package/templates/imessage/components/contacts-view.tsx +172 -0
  276. package/templates/imessage/components/conversation-list.tsx +129 -0
  277. package/templates/imessage/components/nav-rail.tsx +67 -0
  278. package/templates/imessage/components/setup-view.tsx +408 -0
  279. package/templates/imessage/components/thread.tsx +229 -0
  280. package/templates/imessage/components/ui/badge.tsx +37 -0
  281. package/templates/imessage/components/ui/button.tsx +56 -0
  282. package/templates/imessage/components/ui/card.tsx +90 -0
  283. package/templates/imessage/components/ui/dialog.tsx +110 -0
  284. package/templates/imessage/components/ui/input.tsx +30 -0
  285. package/templates/imessage/components/ui/label.tsx +21 -0
  286. package/templates/imessage/components/ui/select.tsx +39 -0
  287. package/templates/imessage/components/ui/table.tsx +124 -0
  288. package/templates/imessage/components/ui/textarea.tsx +24 -0
  289. package/templates/imessage/components.json +20 -0
  290. package/templates/imessage/functions/answerNow.ts +34 -0
  291. package/templates/imessage/functions/assistant.ts +96 -0
  292. package/templates/imessage/functions/beginTurn.ts +101 -0
  293. package/templates/imessage/functions/cancelReminder.ts +13 -0
  294. package/templates/imessage/functions/claimOutbound.ts +44 -0
  295. package/templates/imessage/functions/clearDemo.ts +18 -0
  296. package/templates/imessage/functions/deleteNote.ts +11 -0
  297. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  298. package/templates/imessage/functions/finishTurn.ts +84 -0
  299. package/templates/imessage/functions/fireReminder.ts +42 -0
  300. package/templates/imessage/functions/ingestInbound.ts +22 -0
  301. package/templates/imessage/functions/ownerAccount.ts +13 -0
  302. package/templates/imessage/functions/processTurn.ts +74 -0
  303. package/templates/imessage/functions/relayApply.ts +102 -0
  304. package/templates/imessage/functions/relaySync.ts +63 -0
  305. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  306. package/templates/imessage/functions/resolveTurn.ts +25 -0
  307. package/templates/imessage/functions/saveContact.ts +27 -0
  308. package/templates/imessage/functions/seedDemo.ts +70 -0
  309. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  310. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  311. package/templates/imessage/functions/setContactStatus.ts +17 -0
  312. package/templates/imessage/functions/settleOutbound.ts +35 -0
  313. package/templates/imessage/functions/setupStatus.ts +33 -0
  314. package/templates/imessage/functions/sweep.ts +58 -0
  315. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  316. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  317. package/templates/imessage/functions/toolListNotes.ts +16 -0
  318. package/templates/imessage/functions/toolListReminders.ts +17 -0
  319. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  320. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  321. package/templates/imessage/functions/updateSettings.ts +48 -0
  322. package/templates/imessage/gitignore +11 -0
  323. package/templates/imessage/lib/agent-call.ts +251 -0
  324. package/templates/imessage/lib/assistant.ts +74 -0
  325. package/templates/imessage/lib/chunk.ts +135 -0
  326. package/templates/imessage/lib/demo.ts +68 -0
  327. package/templates/imessage/lib/format.ts +170 -0
  328. package/templates/imessage/lib/gate.ts +104 -0
  329. package/templates/imessage/lib/handles.ts +67 -0
  330. package/templates/imessage/lib/provider.ts +38 -0
  331. package/templates/imessage/lib/relay-protocol.ts +95 -0
  332. package/templates/imessage/lib/reminders.ts +41 -0
  333. package/templates/imessage/lib/secrets.ts +59 -0
  334. package/templates/imessage/lib/sendblue.ts +173 -0
  335. package/templates/imessage/lib/store.ts +407 -0
  336. package/templates/imessage/lib/transport.ts +135 -0
  337. package/templates/imessage/lib/utils.ts +10 -0
  338. package/templates/imessage/package.json +40 -0
  339. package/templates/imessage/relay/.env.example +14 -0
  340. package/templates/imessage/relay/applescript.ts +70 -0
  341. package/templates/imessage/relay/chatdb.ts +165 -0
  342. package/templates/imessage/relay/client.ts +41 -0
  343. package/templates/imessage/relay/main.ts +137 -0
  344. package/templates/imessage/relay/state.ts +35 -0
  345. package/templates/imessage/tests/agent-call.test.ts +112 -0
  346. package/templates/imessage/tests/applescript.test.ts +88 -0
  347. package/templates/imessage/tests/chatdb.test.ts +139 -0
  348. package/templates/imessage/tests/chunk.test.ts +61 -0
  349. package/templates/imessage/tests/fake-ctx.ts +88 -0
  350. package/templates/imessage/tests/ingest.test.ts +141 -0
  351. package/templates/imessage/tests/lib.test.ts +99 -0
  352. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  353. package/templates/imessage/tests/secrets.test.ts +36 -0
  354. package/templates/imessage/tests/sendblue.test.ts +143 -0
  355. package/templates/imessage/tests/setup.ts +5 -0
  356. package/templates/imessage/tsconfig.json +18 -0
  357. package/templates/inventory/AGENTS.md +1 -1
  358. package/templates/inventory/README.md +21 -0
  359. package/templates/inventory/app/apple-icon.png +0 -0
  360. package/templates/inventory/app/globals.css +29 -14
  361. package/templates/inventory/app/icon.svg +1 -0
  362. package/templates/inventory/app/layout.tsx +7 -5
  363. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  364. package/templates/inventory/app/movements/page.tsx +2 -1
  365. package/templates/inventory/app/page.tsx +4 -6
  366. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  367. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  368. package/templates/inventory/app/products-view.tsx +74 -33
  369. package/templates/inventory/app/workspace.tsx +34 -16
  370. package/templates/inventory/app.ts +10 -2
  371. package/templates/inventory/components/app-shell.tsx +94 -0
  372. package/templates/inventory/components/auth-form.tsx +35 -5
  373. package/templates/inventory/components/avatar.tsx +5 -1
  374. package/templates/inventory/components/brand-mark.tsx +25 -0
  375. package/templates/inventory/components/category-tile.tsx +49 -0
  376. package/templates/inventory/components/command-palette.tsx +17 -17
  377. package/templates/inventory/components/data-table.tsx +97 -36
  378. package/templates/inventory/components/filter-tabs.tsx +101 -0
  379. package/templates/inventory/components/nav.tsx +14 -0
  380. package/templates/inventory/components/page-header.tsx +43 -7
  381. package/templates/inventory/components/require-auth.tsx +24 -26
  382. package/templates/inventory/components/sidebar.tsx +54 -48
  383. package/templates/inventory/components/stock-level.tsx +30 -14
  384. package/templates/inventory/components/summary-bar.tsx +8 -15
  385. package/templates/inventory/components/ui/button.tsx +5 -4
  386. package/templates/inventory/components/ui/dialog.tsx +1 -1
  387. package/templates/inventory/components/ui/input.tsx +1 -1
  388. package/templates/inventory/components/ui/select.tsx +1 -1
  389. package/templates/inventory/components/ui/textarea.tsx +1 -1
  390. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  391. package/templates/inventory/lib/brand.ts +9 -0
  392. package/templates/inventory/lib/demo-team.ts +51 -0
  393. package/templates/inventory/lib/search.ts +4 -3
  394. package/templates/inventory/lib/seed.ts +29 -2
  395. package/templates/inventory/lib/stock.ts +32 -0
  396. package/templates/inventory/public/favicon.ico +0 -0
  397. package/templates/inventory/tests/demo-team.test.ts +18 -0
  398. package/templates/inventory/tests/stock.test.ts +29 -0
  399. package/templates/invoices/AGENTS.md +1 -1
  400. package/templates/invoices/README.md +16 -0
  401. package/templates/invoices/app/apple-icon.png +0 -0
  402. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  403. package/templates/invoices/app/clients/page.tsx +2 -1
  404. package/templates/invoices/app/globals.css +29 -14
  405. package/templates/invoices/app/icon.svg +1 -0
  406. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  407. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  408. package/templates/invoices/app/invoices-view.tsx +81 -32
  409. package/templates/invoices/app/layout.tsx +7 -5
  410. package/templates/invoices/app/page.tsx +4 -6
  411. package/templates/invoices/app/workspace.tsx +26 -16
  412. package/templates/invoices/app.ts +2 -2
  413. package/templates/invoices/components/app-shell.tsx +94 -0
  414. package/templates/invoices/components/auth-form.tsx +35 -5
  415. package/templates/invoices/components/avatar.tsx +5 -1
  416. package/templates/invoices/components/brand-mark.tsx +26 -0
  417. package/templates/invoices/components/command-palette.tsx +17 -17
  418. package/templates/invoices/components/data-table.tsx +97 -36
  419. package/templates/invoices/components/filter-tabs.tsx +101 -0
  420. package/templates/invoices/components/line-items.tsx +19 -14
  421. package/templates/invoices/components/nav.tsx +14 -0
  422. package/templates/invoices/components/page-header.tsx +43 -7
  423. package/templates/invoices/components/require-auth.tsx +24 -26
  424. package/templates/invoices/components/sidebar.tsx +54 -48
  425. package/templates/invoices/components/status-badge.tsx +7 -7
  426. package/templates/invoices/components/summary-bar.tsx +5 -5
  427. package/templates/invoices/components/ui/button.tsx +5 -4
  428. package/templates/invoices/components/ui/dialog.tsx +1 -1
  429. package/templates/invoices/components/ui/input.tsx +1 -1
  430. package/templates/invoices/components/ui/select.tsx +1 -1
  431. package/templates/invoices/components/ui/textarea.tsx +1 -1
  432. package/templates/invoices/functions/recordPayment.ts +3 -0
  433. package/templates/invoices/lib/billing.ts +40 -0
  434. package/templates/invoices/lib/brand.ts +9 -0
  435. package/templates/invoices/lib/search.ts +4 -3
  436. package/templates/invoices/public/favicon.ico +0 -0
  437. package/templates/invoices/tests/billing.test.ts +27 -0
  438. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  444. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  445. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  446. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  447. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  448. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  449. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  450. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  451. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  452. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  453. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  454. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  455. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  456. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  457. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  458. package/templates/local-service/.env.example +7 -0
  459. package/templates/local-service/AGENTS.md +1 -1
  460. package/templates/local-service/README.md +26 -2
  461. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  462. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  463. package/templates/local-service/app/auth-form.tsx +8 -8
  464. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  465. package/templates/local-service/app/dashboard/page.tsx +14 -14
  466. package/templates/local-service/app/icon.svg +2 -0
  467. package/templates/local-service/app/login/page.tsx +15 -15
  468. package/templates/local-service/functions/seedDemo.ts +49 -0
  469. package/templates/local-service/lib/demo.ts +135 -0
  470. package/templates/local-service/lib/hours.ts +44 -0
  471. package/templates/local-service/lib/site.config.ts +8 -3
  472. package/templates/local-service/lib/slots.ts +3 -0
  473. package/templates/local-service/public/favicon.ico +0 -0
  474. package/templates/local-service/tests/booking.test.ts +104 -0
  475. package/templates/marketplace/AGENTS.md +1 -1
  476. package/templates/marketplace/README.md +23 -15
  477. package/templates/marketplace/app/globals.css +172 -114
  478. package/templates/marketplace/app/layout.tsx +75 -30
  479. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  480. package/templates/marketplace/app/me/page.tsx +4 -4
  481. package/templates/marketplace/app/page.tsx +221 -298
  482. package/templates/marketplace/app/sell/page.tsx +8 -11
  483. package/templates/marketplace/app.ts +32 -7
  484. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  485. package/templates/marketplace/client/AuthNav.tsx +24 -16
  486. package/templates/marketplace/client/ListingCard.tsx +118 -0
  487. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  488. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  489. package/templates/marketplace/client/LoginCard.tsx +114 -136
  490. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  491. package/templates/marketplace/client/MyMarket.tsx +371 -185
  492. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  493. package/templates/marketplace/client/SellForm.tsx +271 -192
  494. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  495. package/templates/marketplace/client/WatchButton.tsx +16 -16
  496. package/templates/marketplace/client/market.ts +61 -14
  497. package/templates/marketplace/functions/seedMarket.ts +143 -36
  498. package/templates/marketplace/functions/seedOffers.ts +101 -0
  499. package/templates/marketplace/lib/catalog.ts +72 -1
  500. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  501. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  502. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  503. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  504. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  505. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  506. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  507. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  508. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  509. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  510. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  511. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  512. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  513. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  514. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  515. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  516. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  517. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  518. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  519. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  520. package/templates/marketplace/tests/example.test.ts +64 -2
  521. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  522. package/templates/projects/AGENTS.md +1 -1
  523. package/templates/projects/README.md +24 -3
  524. package/templates/projects/app/apple-icon.png +0 -0
  525. package/templates/projects/app/clients/clients-view.tsx +20 -6
  526. package/templates/projects/app/clients/page.tsx +2 -1
  527. package/templates/projects/app/globals.css +29 -14
  528. package/templates/projects/app/icon.svg +1 -0
  529. package/templates/projects/app/layout.tsx +7 -5
  530. package/templates/projects/app/page.tsx +3 -5
  531. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  532. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  533. package/templates/projects/app/projects-view.tsx +105 -52
  534. package/templates/projects/app/workspace.tsx +41 -20
  535. package/templates/projects/app.ts +2 -2
  536. package/templates/projects/components/app-shell.tsx +94 -0
  537. package/templates/projects/components/auth-form.tsx +35 -5
  538. package/templates/projects/components/avatar.tsx +5 -1
  539. package/templates/projects/components/board-skeleton.tsx +3 -3
  540. package/templates/projects/components/brand-mark.tsx +25 -0
  541. package/templates/projects/components/command-palette.tsx +17 -17
  542. package/templates/projects/components/data-table.tsx +97 -36
  543. package/templates/projects/components/filter-tabs.tsx +101 -0
  544. package/templates/projects/components/meter.tsx +48 -0
  545. package/templates/projects/components/nav.tsx +14 -0
  546. package/templates/projects/components/page-header.tsx +43 -7
  547. package/templates/projects/components/require-auth.tsx +24 -26
  548. package/templates/projects/components/sidebar.tsx +54 -48
  549. package/templates/projects/components/summary-bar.tsx +49 -0
  550. package/templates/projects/components/task-board.tsx +10 -9
  551. package/templates/projects/components/time-dialog.tsx +29 -5
  552. package/templates/projects/components/ui/button.tsx +5 -4
  553. package/templates/projects/components/ui/dialog.tsx +1 -1
  554. package/templates/projects/components/ui/input.tsx +1 -1
  555. package/templates/projects/components/ui/select.tsx +1 -1
  556. package/templates/projects/components/ui/textarea.tsx +1 -1
  557. package/templates/projects/functions/seedWorkspace.ts +26 -12
  558. package/templates/projects/lib/brand.ts +9 -0
  559. package/templates/projects/lib/demo-team.ts +51 -0
  560. package/templates/projects/lib/search.ts +4 -3
  561. package/templates/projects/lib/seed.ts +35 -1
  562. package/templates/projects/lib/work.ts +63 -0
  563. package/templates/projects/public/favicon.ico +0 -0
  564. package/templates/projects/tests/demo-team.test.ts +18 -0
  565. package/templates/projects/tests/work.test.ts +65 -0
  566. package/templates/restaurant/.env.example +7 -0
  567. package/templates/restaurant/AGENTS.md +1 -1
  568. package/templates/restaurant/README.md +29 -3
  569. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  570. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  571. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  572. package/templates/restaurant/app/auth-form.tsx +8 -8
  573. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  574. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  575. package/templates/restaurant/app/icon.svg +4 -0
  576. package/templates/restaurant/app/login/page.tsx +15 -15
  577. package/templates/restaurant/functions/seedDemo.ts +44 -0
  578. package/templates/restaurant/lib/demo.ts +144 -0
  579. package/templates/restaurant/lib/hours.ts +45 -0
  580. package/templates/restaurant/lib/site.config.ts +3 -3
  581. package/templates/restaurant/public/favicon.ico +0 -0
  582. package/templates/restaurant/tests/reservations.test.ts +67 -0
  583. package/templates/saas/AGENTS.md +1 -1
  584. package/templates/saas/README.md +54 -35
  585. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  586. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  587. package/templates/saas/app/(auth)/layout.tsx +27 -33
  588. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  589. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  590. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  591. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  592. package/templates/saas/app/(marketing)/page.tsx +133 -372
  593. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  594. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  595. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  596. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  597. package/templates/saas/app/dashboard/layout.tsx +27 -13
  598. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  599. package/templates/saas/app/dashboard/page.tsx +29 -35
  600. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  601. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  602. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  603. package/templates/saas/app/globals.css +5 -4
  604. package/templates/saas/app/icon.svg +1 -0
  605. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  606. package/templates/saas/app/onboarding/page.tsx +6 -2
  607. package/templates/saas/app.ts +68 -13
  608. package/templates/saas/components/dashboard-shell.tsx +212 -132
  609. package/templates/saas/components/marketing.tsx +137 -274
  610. package/templates/saas/components/pricing-table.tsx +12 -11
  611. package/templates/saas/components/setup-checklist.tsx +31 -29
  612. package/templates/saas/components/task-ui.tsx +199 -0
  613. package/templates/saas/functions/createProject.ts +7 -13
  614. package/templates/saas/functions/createTask.ts +67 -0
  615. package/templates/saas/functions/deleteProject.ts +17 -0
  616. package/templates/saas/functions/deleteTask.ts +14 -0
  617. package/templates/saas/functions/seedWorkspace.ts +46 -0
  618. package/templates/saas/functions/setProjectStatus.ts +41 -0
  619. package/templates/saas/functions/updateProfile.ts +13 -0
  620. package/templates/saas/functions/updateProject.ts +4 -5
  621. package/templates/saas/functions/updateTask.ts +88 -0
  622. package/templates/saas/lib/members.ts +53 -0
  623. package/templates/saas/lib/names.ts +53 -0
  624. package/templates/saas/lib/plans.ts +11 -0
  625. package/templates/saas/lib/sample-workspace.ts +117 -0
  626. package/templates/saas/lib/site.config.ts +200 -463
  627. package/templates/saas/lib/tasks.ts +101 -0
  628. package/templates/saas/lib/use-live.ts +25 -0
  629. package/templates/saas/public/favicon.ico +0 -0
  630. package/templates/saas/public/screenshots/board.png +0 -0
  631. package/templates/saas/public/screenshots/members.png +0 -0
  632. package/templates/saas/public/screenshots/overview.png +0 -0
  633. package/templates/saas/public/screenshots/projects.png +0 -0
  634. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  635. package/templates/saas/tests/names.test.ts +35 -0
  636. package/templates/saas/tests/plans.test.ts +12 -0
  637. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  638. package/templates/saas/tests/tasks.test.ts +81 -0
  639. package/templates/shop/AGENTS.md +1 -1
  640. package/templates/shop/README.md +25 -13
  641. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  642. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  643. package/templates/shop/app/(marketing)/page.tsx +120 -59
  644. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  645. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  646. package/templates/shop/app/globals.css +94 -1
  647. package/templates/shop/app/layout.tsx +4 -4
  648. package/templates/shop/app/login/page.tsx +4 -4
  649. package/templates/shop/app.ts +18 -6
  650. package/templates/shop/components/cart-button.tsx +33 -0
  651. package/templates/shop/components/marketing.tsx +17 -87
  652. package/templates/shop/components/shelf-count.tsx +36 -0
  653. package/templates/shop/functions/checkout.ts +17 -1
  654. package/templates/shop/lib/cart-store.ts +57 -0
  655. package/templates/shop/lib/shelf-store.ts +35 -0
  656. package/templates/shop/lib/shop.ts +27 -0
  657. package/templates/shop/lib/site.config.ts +108 -98
  658. package/templates/shop/public/images/hero.jpg +0 -0
  659. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  660. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  661. package/templates/shop/public/images/products/linen.jpg +0 -0
  662. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  663. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  664. package/templates/shop/tests/shop.test.ts +64 -0
  665. package/templates/todo/AGENTS.md +1 -1
  666. package/templates/waitlist/.env.example +7 -0
  667. package/templates/waitlist/AGENTS.md +1 -1
  668. package/templates/waitlist/README.md +29 -13
  669. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  670. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  671. package/templates/waitlist/app/auth-form.tsx +8 -8
  672. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  673. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  674. package/templates/waitlist/app/icon.svg +2 -0
  675. package/templates/waitlist/app/login/page.tsx +14 -14
  676. package/templates/waitlist/app.ts +6 -6
  677. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  678. package/templates/waitlist/functions/seedDemo.ts +32 -0
  679. package/templates/waitlist/lib/demo.ts +94 -0
  680. package/templates/waitlist/lib/launch.ts +19 -0
  681. package/templates/waitlist/lib/site.config.ts +14 -9
  682. package/templates/waitlist/lib/stats.ts +35 -0
  683. package/templates/waitlist/public/favicon.ico +0 -0
  684. package/templates/waitlist/tests/counter.test.ts +107 -0
  685. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  686. package/templates/ai-chat/tests/example.test.ts +0 -12
  687. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  688. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  689. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  690. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  691. package/templates/chat/functions/_keep.ts +0 -13
  692. package/templates/chat/tests/example.test.ts +0 -12
  693. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  694. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  695. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  696. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -3,13 +3,27 @@
3
3
  import React, { useEffect, useState } from "react";
4
4
  import { db, callFn } from "@pylonsync/react";
5
5
  import { useAuth } from "@pylonsync/client";
6
- import { parseTags, type SubmissionRow, type OwnerSubmissionsResult } from "@/lib/directory";
6
+ import { ArrowUpRight, Check, LogOut, X } from "lucide-react";
7
+ import {
8
+ monogram,
9
+ parseTags,
10
+ type SubmissionRow,
11
+ type OwnerSubmissionsResult,
12
+ } from "@/lib/directory";
7
13
 
8
14
  // The curator's moderation queue. Liveness rides the public Listing table:
9
15
  // `db.useQuery("Listing")` re-renders when listings change (approving a
10
16
  // submission creates one), and that same signal re-fetches the owner-gated
11
- // `submissionsForOwner` — so the queue stays current without a reload, while
17
+ // `submissionsForOwner`, so the queue stays current without a reload, while
12
18
  // submitter PII only ever travels through the gated call.
19
+ type Filter = "new" | "approved" | "rejected";
20
+
21
+ const FILTERS: { id: Filter; label: string }[] = [
22
+ { id: "new", label: "Pending" },
23
+ { id: "approved", label: "Approved" },
24
+ { id: "rejected", label: "Rejected" },
25
+ ];
26
+
13
27
  export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
14
28
  const { data: listings } = db.useQuery<{ id: string }>("Listing");
15
29
  const liveCount = listings.length;
@@ -18,6 +32,8 @@ export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
18
32
  const [denied, setDenied] = useState(false);
19
33
  const [error, setError] = useState<string | null>(null);
20
34
  const [busyId, setBusyId] = useState<string | null>(null);
35
+ const [filter, setFilter] = useState<Filter>("new");
36
+ const [seedDone, setSeedDone] = useState(false);
21
37
 
22
38
  async function load() {
23
39
  try {
@@ -33,10 +49,20 @@ export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
33
49
  }
34
50
  }
35
51
 
52
+ // In `pylon dev` an empty queue gets demo submissions (functions/seedDemo.ts)
53
+ // before the first load. On a deploy, or for a non-owner, the call writes
54
+ // nothing.
36
55
  useEffect(() => {
56
+ void callFn("seedDemo", {})
57
+ .catch(() => {})
58
+ .finally(() => setSeedDone(true));
59
+ }, []);
60
+
61
+ useEffect(() => {
62
+ if (!seedDone) return;
37
63
  void load();
38
64
  // eslint-disable-next-line react-hooks/exhaustive-deps
39
- }, [liveCount]);
65
+ }, [liveCount, seedDone]);
40
66
 
41
67
  async function act(id: string, fn: "approveSubmission" | "rejectSubmission") {
42
68
  setBusyId(id);
@@ -50,113 +76,170 @@ export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
50
76
 
51
77
  if (denied) return <OwnerOnly email={userEmail} />;
52
78
  if (error) {
53
- return <div className="rounded-xl border border-red-200 bg-red-50 px-5 py-4 text-sm text-red-700">{error}</div>;
79
+ return <div className="border border-red-200 bg-red-50 px-5 py-4 text-sm text-red-700">{error}</div>;
54
80
  }
55
81
  if (!submissions) return <Skeleton />;
56
82
 
57
- const pending = submissions.filter((s) => s.status === "new");
58
- const approved = submissions.filter((s) => s.status === "approved").length;
83
+ const counts: Record<Filter, number> = { new: 0, approved: 0, rejected: 0 };
84
+ for (const s of submissions) {
85
+ if (s.status === "new" || s.status === "approved" || s.status === "rejected") counts[s.status]++;
86
+ }
87
+ const shown = submissions.filter((s) => s.status === filter);
59
88
 
60
89
  return (
61
90
  <div className="space-y-8">
62
91
  <div>
63
- <h1 className="text-xl font-semibold tracking-tight">Submissions</h1>
64
- <p className="mt-1 text-sm text-zinc-500">
65
- Live — new submissions land here; approving one publishes it to the directory instantly.
92
+ <h1 className="text-[1.75rem] font-semibold leading-tight tracking-[-0.02em] text-zinc-900 sm:text-[2rem]">
93
+ Review queue
94
+ </h1>
95
+ <p className="mt-2 max-w-xl text-[14px] leading-relaxed text-zinc-600">
96
+ New submissions land here. Approving one publishes it to the directory right away.
66
97
  </p>
67
98
  </div>
68
99
 
69
- <div className="grid gap-4 sm:grid-cols-3">
70
- <Stat label="Pending" value={String(pending.length)} />
71
- <Stat label="Approved" value={String(approved)} />
72
- <Stat label="Live listings" value={String(liveCount)} />
73
- </div>
100
+ <dl className="grid grid-cols-3 divide-x divide-zinc-200 border-y border-zinc-200">
101
+ <Stat label="Pending" value={counts.new} accent={counts.new > 0} />
102
+ <Stat label="Approved" value={counts.approved} />
103
+ <Stat label="Live listings" value={liveCount} />
104
+ </dl>
74
105
 
75
- <div className="rounded-xl border border-zinc-200 bg-white">
76
- <div className="border-b border-zinc-100 px-4 py-3 text-sm font-semibold text-zinc-900">
77
- Queue <span className="font-normal text-zinc-400">({submissions.length})</span>
106
+ <section>
107
+ <div className="flex items-center gap-4 border-b border-zinc-200">
108
+ {FILTERS.map((f) => (
109
+ <button
110
+ key={f.id}
111
+ type="button"
112
+ onClick={() => setFilter(f.id)}
113
+ aria-pressed={filter === f.id}
114
+ className={
115
+ "-mb-px border-b-2 pb-2 text-[14px] transition-colors " +
116
+ (filter === f.id
117
+ ? "border-zinc-900 font-medium text-zinc-900"
118
+ : "border-transparent text-zinc-500 hover:text-zinc-900")
119
+ }
120
+ >
121
+ {f.label} <span className="font-mono text-[12px] text-zinc-400">{counts[f.id]}</span>
122
+ </button>
123
+ ))}
78
124
  </div>
79
- {submissions.length === 0 ? (
80
- <p className="p-8 text-center text-sm text-zinc-500">No submissions yet.</p>
125
+
126
+ {shown.length === 0 ? (
127
+ <p className="py-12 text-center text-[14px] text-zinc-500">
128
+ {filter === "new"
129
+ ? "Nothing to review. New submissions appear here as they arrive."
130
+ : `No ${filter} submissions.`}
131
+ </p>
81
132
  ) : (
82
- <ul className="divide-y divide-zinc-100">
83
- {submissions.map((s) => (
84
- <li key={s.id} className="px-4 py-3.5">
85
- <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2">
86
- <div className="min-w-0 flex-1">
87
- <div className="flex flex-wrap items-center gap-2">
88
- <a href={s.url} target="_blank" rel="noopener noreferrer" className="text-[14px] font-medium text-zinc-900 hover:text-brand">
89
- {s.name}
90
- </a>
91
- <span className="rounded-full bg-zinc-100 px-2 py-0.5 text-[11px] font-medium text-zinc-500">{s.category}</span>
92
- <StatusBadge status={s.status} />
93
- </div>
94
- {s.tagline ? <p className="mt-1 text-[13.5px] text-zinc-600">{s.tagline}</p> : null}
95
- <div className="mt-1 truncate text-[12px] text-zinc-400">
96
- {s.submitterName} · {s.submitterEmail}
97
- {parseTags(s.tags).length ? ` · ${parseTags(s.tags).map((t) => "#" + t).join(" ")}` : ""}
98
- </div>
99
- </div>
100
- {s.status === "new" ? (
101
- <div className="flex items-center gap-2">
102
- <button
103
- type="button"
104
- disabled={busyId === s.id}
105
- onClick={() => act(s.id, "approveSubmission")}
106
- className="rounded-md bg-brand px-3 py-1.5 text-[12.5px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-50"
107
- >
108
- {busyId === s.id ? "…" : "Approve"}
109
- </button>
110
- <button
111
- type="button"
112
- disabled={busyId === s.id}
113
- onClick={() => act(s.id, "rejectSubmission")}
114
- className="rounded-md border border-zinc-300 px-3 py-1.5 text-[12.5px] font-medium text-zinc-600 transition-colors hover:border-red-300 hover:text-red-600 disabled:opacity-50"
115
- >
116
- Reject
117
- </button>
118
- </div>
119
- ) : null}
120
- </div>
121
- </li>
133
+ <ul>
134
+ {shown.map((s) => (
135
+ <QueueItem key={s.id} s={s} busy={busyId === s.id} onAct={act} />
122
136
  ))}
123
137
  </ul>
124
138
  )}
125
- </div>
139
+ </section>
126
140
  </div>
127
141
  );
128
142
  }
129
143
 
130
- function Stat({ label, value }: { label: string; value: string }) {
144
+ function QueueItem({
145
+ s,
146
+ busy,
147
+ onAct,
148
+ }: {
149
+ s: SubmissionRow;
150
+ busy: boolean;
151
+ onAct: (id: string, fn: "approveSubmission" | "rejectSubmission") => void;
152
+ }) {
153
+ const m = monogram(s.name);
154
+ const tags = parseTags(s.tags);
131
155
  return (
132
- <div className="rounded-xl border border-zinc-200 bg-white p-4">
133
- <div className="text-[11px] font-medium uppercase tracking-wide text-zinc-400">{label}</div>
134
- <div className="mt-1 text-2xl font-semibold tabular-nums text-zinc-900">{value}</div>
135
- </div>
156
+ <li className="grid grid-cols-[36px_minmax(0,1fr)] gap-x-3 gap-y-3 border-b border-zinc-200 py-4 sm:grid-cols-[36px_minmax(0,1fr)_auto]">
157
+ <span
158
+ aria-hidden
159
+ className="flex size-9 items-center justify-center rounded-md font-mono text-[13px] font-semibold"
160
+ style={{ backgroundColor: m.bg, color: m.fg }}
161
+ >
162
+ {m.letters}
163
+ </span>
164
+ <div className="min-w-0">
165
+ <div className="flex flex-wrap items-baseline gap-x-2">
166
+ <a
167
+ href={s.url}
168
+ target="_blank"
169
+ rel="noopener noreferrer"
170
+ className="inline-flex items-center gap-1 text-[15px] font-semibold text-zinc-900 hover:text-brand"
171
+ >
172
+ {s.name}
173
+ <ArrowUpRight aria-hidden className="size-3.5 text-zinc-400" />
174
+ </a>
175
+ <span className="font-mono text-[12px] text-zinc-400">{s.category}</span>
176
+ </div>
177
+ <p className="mt-0.5 text-[14px] leading-snug text-zinc-700">{s.tagline}</p>
178
+ {s.description ? (
179
+ <p className="mt-1 max-w-2xl text-[13px] leading-relaxed text-zinc-500">{s.description}</p>
180
+ ) : null}
181
+ <p className="mt-1.5 truncate font-mono text-[12px] text-zinc-400">
182
+ {s.submitterName} · {s.submitterEmail} · {ago(s.createdAt)}
183
+ {tags.length ? ` · ${tags.join(", ")}` : ""}
184
+ </p>
185
+ </div>
186
+ {s.status === "new" ? (
187
+ <div className="col-start-2 flex items-center gap-2 sm:col-start-3 sm:self-start">
188
+ <button
189
+ type="button"
190
+ disabled={busy}
191
+ onClick={() => onAct(s.id, "approveSubmission")}
192
+ className="inline-flex h-8 items-center gap-1.5 bg-brand px-3 text-[13px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-50"
193
+ >
194
+ <Check aria-hidden className="size-3.5" />
195
+ Approve
196
+ </button>
197
+ <button
198
+ type="button"
199
+ disabled={busy}
200
+ onClick={() => onAct(s.id, "rejectSubmission")}
201
+ className="inline-flex h-8 items-center gap-1.5 border border-zinc-300 px-3 text-[13px] font-medium text-zinc-600 transition-colors hover:border-zinc-500 hover:text-zinc-900 disabled:opacity-50"
202
+ >
203
+ <X aria-hidden className="size-3.5" />
204
+ Reject
205
+ </button>
206
+ </div>
207
+ ) : null}
208
+ </li>
136
209
  );
137
210
  }
138
211
 
139
- function StatusBadge({ status }: { status: string }) {
140
- const tone =
141
- status === "approved"
142
- ? "bg-green-50 text-green-700"
143
- : status === "rejected"
144
- ? "bg-zinc-100 text-zinc-400"
145
- : "bg-amber-50 text-amber-700"; // new
146
- const label = status === "new" ? "pending" : status;
147
- return <span className={"rounded-full px-2 py-0.5 text-[10px] font-medium capitalize " + tone}>{label}</span>;
212
+ function ago(iso: string): string {
213
+ const mins = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 60_000));
214
+ if (mins < 60) return `${mins}m ago`;
215
+ const hours = Math.round(mins / 60);
216
+ if (hours < 48) return `${hours}h ago`;
217
+ return `${Math.round(hours / 24)}d ago`;
218
+ }
219
+
220
+ function Stat({ label, value, accent }: { label: string; value: number; accent?: boolean }) {
221
+ return (
222
+ <div className="px-4 py-4 first:pl-0">
223
+ <dt className="text-[12px] font-medium uppercase tracking-wide text-zinc-500">{label}</dt>
224
+ <dd className={"mt-1 font-mono text-[1.75rem] leading-none tabular-nums " + (accent ? "text-brand" : "text-zinc-900")}>
225
+ {value}
226
+ </dd>
227
+ </div>
228
+ );
148
229
  }
149
230
 
150
231
  function OwnerOnly({ email }: { email: string }) {
151
232
  return (
152
- <div className="rounded-xl border border-dashed border-zinc-300 px-6 py-12 text-center">
233
+ <div className="border border-dashed border-zinc-300 px-6 py-12 text-center">
153
234
  <h1 className="text-lg font-semibold">This dashboard is owner-only</h1>
154
235
  <p className="mx-auto mt-2 max-w-md text-sm text-zinc-500">
155
- You&apos;re signed in as <span className="font-medium text-zinc-700">{email || "this account"}</span>.
156
- Only the curator can review submissions. Set{" "}
157
- <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-[12px]">PYLON_OWNER_EMAIL={email || "you@yourdirectory.com"}</code>{" "}
158
- in your <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-[12px]">.env</code>, restart, and reload —
159
- or sign in with the owner account.
236
+ You are signed in as <span className="font-medium text-zinc-700">{email || "this account"}</span>.
237
+ Only the directory owner can review submissions. Set{" "}
238
+ <code className="bg-zinc-100 px-1.5 py-0.5 font-mono text-[12px]">
239
+ PYLON_OWNER_EMAIL={email || "you@yourdirectory.com"}
240
+ </code>{" "}
241
+ in <code className="bg-zinc-100 px-1.5 py-0.5 font-mono text-[12px]">.env</code>, restart, and
242
+ reload. Or sign in with the owner account.
160
243
  </p>
161
244
  </div>
162
245
  );
@@ -171,18 +254,22 @@ export function UserMenu({ email }: { email: string }) {
171
254
  }
172
255
  return (
173
256
  <details className="group relative">
174
- <summary className="flex size-8 cursor-pointer select-none list-none items-center justify-center rounded-full bg-zinc-900 text-[12px] font-semibold text-white marker:hidden [&::-webkit-details-marker]:hidden">
257
+ <summary
258
+ aria-label="Account"
259
+ className="flex size-7 cursor-pointer select-none list-none items-center justify-center bg-zinc-900 font-mono text-[12px] font-semibold text-white marker:hidden [&::-webkit-details-marker]:hidden"
260
+ >
175
261
  {initial}
176
262
  </summary>
177
- <div className="absolute right-0 top-full z-40 mt-2 w-56 overflow-hidden rounded-xl border border-zinc-200 bg-white py-1 shadow-[0_16px_48px_-16px_rgba(0,0,0,0.25)]">
178
- <div className="border-b border-zinc-100 px-3 py-2">
179
- <div className="truncate text-[13px] font-medium text-zinc-900">{email || "Signed in"}</div>
263
+ <div className="absolute right-0 top-full z-40 mt-2 w-60 border border-zinc-200 bg-white py-1 shadow-[0_16px_40px_-20px_rgba(0,0,0,0.3)]">
264
+ <div className="truncate border-b border-zinc-100 px-3 py-2 font-mono text-[12px] text-zinc-700">
265
+ {email || "Signed in"}
180
266
  </div>
181
267
  <button
182
268
  type="button"
183
269
  onClick={onSignOut}
184
- className="flex w-full items-center px-3 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
270
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
185
271
  >
272
+ <LogOut aria-hidden className="size-3.5" />
186
273
  Sign out
187
274
  </button>
188
275
  </div>
@@ -193,13 +280,13 @@ export function UserMenu({ email }: { email: string }) {
193
280
  function Skeleton() {
194
281
  return (
195
282
  <div className="space-y-8">
196
- <div className="h-6 w-32 animate-pulse rounded bg-zinc-100" />
197
- <div className="grid gap-4 sm:grid-cols-3">
198
- {[0, 1, 2].map((i) => (
199
- <div key={i} className="h-20 animate-pulse rounded-xl bg-zinc-100" />
283
+ <div className="h-8 w-48 animate-pulse bg-zinc-100" />
284
+ <div className="h-20 animate-pulse bg-zinc-50" />
285
+ <div className="space-y-3">
286
+ {[0, 1, 2, 3].map((i) => (
287
+ <div key={i} className="h-16 animate-pulse bg-zinc-50" />
200
288
  ))}
201
289
  </div>
202
- <div className="h-48 animate-pulse rounded-xl bg-zinc-100" />
203
290
  </div>
204
291
  );
205
292
  }
@@ -1,6 +1,8 @@
1
1
  import React, { use } from "react";
2
2
  import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
+ import { ArrowUpRight } from "lucide-react";
3
4
  import { siteConfig } from "@/lib/site.config";
5
+ import { WRAP } from "@/components/marketing";
4
6
  import { UserMenu, DirectoryDashboard } from "./dashboard-client";
5
7
 
6
8
  export const metadata: Metadata = {
@@ -35,35 +37,35 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
35
37
  );
36
38
  }
37
39
 
38
- // Dashboard chrome: a slim top bar with the logo, a link back to the public
39
- // site, and the account menu (a client island for sign-out).
40
+ // Dashboard chrome in the site's own look: the same slim white nav with the
41
+ // directory name, a link back to the site, and the account menu (a client
42
+ // island for sign-out).
40
43
  function Shell({ email, children }: { email: string; children: React.ReactNode }) {
41
44
  const { brand } = siteConfig;
42
45
  return (
43
46
  <div className="flex min-h-screen flex-col bg-white text-zinc-900">
44
47
  <header className="border-b border-zinc-200">
45
- <div className="mx-auto flex h-14 max-w-4xl items-center justify-between px-6">
46
- <div className="flex items-center gap-2">
47
- <span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
48
- {brand.letter}
49
- </span>
50
- <span className="text-[15px] font-semibold tracking-tight">
51
- {brand.name} <span className="text-zinc-400">/ curator</span>
52
- </span>
48
+ <div className={`${WRAP} flex h-12 items-center justify-between`}>
49
+ <div className="flex items-baseline gap-2.5">
50
+ <Link href="/" className="text-[15px] font-semibold text-zinc-900">
51
+ {brand.name}
52
+ </Link>
53
+ <span className="font-mono text-[12px] text-zinc-400">curator</span>
53
54
  </div>
54
- <div className="flex items-center gap-4">
55
+ <div className="flex items-center gap-5">
55
56
  <Link
56
57
  href="/"
57
- className="text-[13px] text-zinc-500 transition-colors hover:text-zinc-900"
58
+ className="hidden items-center gap-1 text-[14px] text-zinc-600 hover:text-zinc-900 sm:inline-flex"
58
59
  >
59
- View site ↗
60
+ View site
61
+ <ArrowUpRight aria-hidden className="size-3.5" />
60
62
  </Link>
61
63
  <UserMenu email={email} />
62
64
  </div>
63
65
  </div>
64
66
  </header>
65
67
  <main className="flex-1">
66
- <div className="mx-auto max-w-4xl px-6 py-8">{children}</div>
68
+ <div className={`${WRAP} py-10`}>{children}</div>
67
69
  </main>
68
70
  </div>
69
71
  );
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#1d4ed8"/>
2
+ <rect x="14" y="14" width="36" height="9" rx="2" fill="#ffffff"/>
3
+ <rect x="14" y="27.5" width="28" height="9" rx="2" fill="#ffffff" opacity="0.8"/>
4
+ <rect x="14" y="41" width="20" height="9" rx="2" fill="#ffffff" opacity="0.6"/></svg>
@@ -2,6 +2,7 @@ import React from "react";
2
2
  import { Link, type Metadata, type PageProps } from "@pylonsync/react";
3
3
  import { AuthForm } from "../auth-form";
4
4
  import { siteConfig } from "@/lib/site.config";
5
+ import { WRAP } from "@/components/marketing";
5
6
 
6
7
  export const metadata: Metadata = {
7
8
  title: `Sign in — ${siteConfig.brand.name}`,
@@ -18,21 +19,21 @@ export default function LoginPage({ auth, response }: PageProps) {
18
19
  if (auth.user_id && !auth.user_id.startsWith("guest_")) response.redirect("/dashboard");
19
20
  const { brand } = siteConfig;
20
21
  return (
21
- <div className="flex min-h-screen items-center justify-center bg-white px-6 py-12">
22
- <div className="w-full max-w-[400px] rounded-2xl border border-zinc-200/70 p-8">
23
- <Link href="/" className="inline-flex">
24
- <span className="flex size-9 items-center justify-center rounded-xl bg-zinc-900 text-base font-bold text-white">
25
- {brand.letter}
26
- </span>
27
- </Link>
28
- <h1 className="mt-5 text-[22px] font-semibold tracking-tight text-zinc-900">
29
- {brand.name} dashboard
30
- </h1>
31
- <p className="mt-1 text-[13px] text-zinc-500">
32
- Sign in to manage your inquiries.
33
- </p>
34
- <div className="mt-6">
35
- <AuthForm />
22
+ <div className="flex min-h-screen flex-col bg-white text-zinc-900">
23
+ <div className="border-b border-zinc-200">
24
+ <div className={`${WRAP} flex h-12 items-center`}>
25
+ <Link href="/" className="text-[15px] font-semibold">
26
+ {brand.name}
27
+ </Link>
28
+ </div>
29
+ </div>
30
+ <div className="flex flex-1 items-center justify-center px-5 py-16">
31
+ <div className="w-full max-w-[380px]">
32
+ <h1 className="text-[1.75rem] font-semibold tracking-[-0.02em]">Curator sign in</h1>
33
+ <p className="mt-2 text-[14px] text-zinc-600">Review submissions and publish them to {brand.name}.</p>
34
+ <div className="mt-8 border border-zinc-200 p-6">
35
+ <AuthForm />
36
+ </div>
36
37
  </div>
37
38
  </div>
38
39
  </div>
@@ -0,0 +1,43 @@
1
+ import { mutation } from "@pylonsync/functions";
2
+ import { emailMatchesOwner } from "../lib/owner";
3
+ import { DEMO_SUBMISSIONS, demoDataEnabled } from "../lib/demo";
4
+
5
+ // seedDemo — owner-only, development-only. Fills an empty review queue with the
6
+ // fictional submissions in lib/demo.ts, so the curator dashboard has entries.
7
+ //
8
+ // It writes nothing unless demo data is on (lib/demo.ts: PYLON_DEMO_DATA, else
9
+ // a `pylon dev` process), so `pylon start`, Docker, and Pylon Cloud deploys do
10
+ // not get demo rows. It also writes nothing once any Submission exists.
11
+ // Submissions carry submitter emails (PII), so only the signed-in owner can run
12
+ // it, like the rest of the curator surface.
13
+ export default mutation<Record<string, never>, { seeded: boolean }>({
14
+ auth: "user",
15
+ async handler(ctx) {
16
+ const me = await ctx.db.get("User", ctx.auth.userId);
17
+ if (!emailMatchesOwner(me?.email as string | undefined, ctx.env.PYLON_OWNER_EMAIL)) {
18
+ throw ctx.error("POLICY_DENIED", "Only the owner can seed the review queue.");
19
+ }
20
+ if (!demoDataEnabled(ctx.env)) return { seeded: false };
21
+
22
+ await ctx.db.advisoryLock("directory_seed_demo");
23
+ const existing = await ctx.db.unsafe.list("Submission");
24
+ if (existing.length > 0) return { seeded: false };
25
+
26
+ const nowMs = Date.now();
27
+ for (const s of DEMO_SUBMISSIONS) {
28
+ await ctx.db.unsafe.insert("Submission", {
29
+ submitterName: s.submitterName,
30
+ submitterEmail: s.submitterEmail,
31
+ name: s.name,
32
+ tagline: s.tagline,
33
+ url: s.url,
34
+ category: s.category,
35
+ tags: s.tags,
36
+ description: s.description || null,
37
+ status: s.status,
38
+ createdAt: new Date(nowMs - s.hoursAgo * 3_600_000).toISOString(),
39
+ });
40
+ }
41
+ return { seeded: true };
42
+ },
43
+ });
@@ -0,0 +1,145 @@
1
+ // Demo review queue for local development.
2
+ //
3
+ // The curator's first dashboard load in `pylon dev` fills an empty Submission
4
+ // table with fictional tool submissions (functions/seedDemo.ts), so the queue
5
+ // has entries to approve or reject. A deploy does not seed: `demoDataEnabled`
6
+ // is true only when PYLON_DEMO_DATA is on, or when it is unset and the process
7
+ // runs under `pylon dev`. Set PYLON_DEMO_DATA=0 to turn it off in development too.
8
+ //
9
+ // The starter listings (functions/seedListings.ts) are different: they are the
10
+ // directory's own content from lib/site.config.ts, so they seed everywhere.
11
+
12
+ const ON = new Set(["1", "true", "yes", "on"]);
13
+ const OFF = new Set(["0", "false", "no", "off"]);
14
+
15
+ /**
16
+ * True when demo rows may be written. An explicit PYLON_DEMO_DATA wins.
17
+ * Otherwise it needs both PYLON_DEV_MODE on and PYLON_DEV_WATCH_DIR set. Only
18
+ * `pylon dev` sets PYLON_DEV_WATCH_DIR; `pylon start` and the Docker image do
19
+ * not, and the image turns PYLON_DEV_MODE on by default, so dev mode alone is
20
+ * not proof of a local machine.
21
+ */
22
+ export function demoDataEnabled(env: Record<string, string | undefined>): boolean {
23
+ const explicit = env.PYLON_DEMO_DATA?.trim().toLowerCase();
24
+ if (explicit && ON.has(explicit)) return true;
25
+ if (explicit && OFF.has(explicit)) return false;
26
+ const dev = env.PYLON_DEV_MODE?.trim().toLowerCase();
27
+ const devMode = dev === "1" || dev === "true";
28
+ return devMode && Boolean(env.PYLON_DEV_WATCH_DIR?.trim());
29
+ }
30
+
31
+ export interface DemoSubmission {
32
+ submitterName: string;
33
+ submitterEmail: string;
34
+ name: string;
35
+ tagline: string;
36
+ url: string;
37
+ category: string;
38
+ tags: string;
39
+ description: string;
40
+ status: "new" | "approved" | "rejected";
41
+ /** Hours before the seed runs. */
42
+ hoursAgo: number;
43
+ }
44
+
45
+ // Fictional tools and submitters. The two "approved" rows name starter
46
+ // listings from lib/site.config.ts, so every approved submission is live.
47
+ // Starter listings added from config have no submission row.
48
+ export const DEMO_SUBMISSIONS: DemoSubmission[] = [
49
+ {
50
+ submitterName: "Nadia Haddad",
51
+ submitterEmail: "nadia@example.com",
52
+ name: "Querybird",
53
+ tagline: "Explain plans for Postgres, drawn as a tree you can click through.",
54
+ url: "https://example.com/querybird",
55
+ category: "Database",
56
+ tags: "postgres, performance",
57
+ description: "Paste a query, get the plan with the slow node marked. Runs in the browser; nothing leaves your machine.",
58
+ status: "new",
59
+ hoursAgo: 2,
60
+ },
61
+ {
62
+ submitterName: "Owen Reilly",
63
+ submitterEmail: "owen@example.com",
64
+ name: "Flagpost",
65
+ tagline: "Feature flags stored in your own database, with a small admin UI.",
66
+ url: "https://example.com/flagpost",
67
+ category: "DevOps",
68
+ tags: "feature flags, self-hosted",
69
+ description: "One table, one SDK call, and an admin page you mount in your app.",
70
+ status: "new",
71
+ hoursAgo: 9,
72
+ },
73
+ {
74
+ submitterName: "Hana Sato",
75
+ submitterEmail: "hana@example.com",
76
+ name: "Palettier",
77
+ tagline: "Generate accessible color scales from one brand color.",
78
+ url: "https://example.com/palettier",
79
+ category: "Design",
80
+ tags: "color, accessibility, tokens",
81
+ description: "Every step is checked for WCAG contrast against white and black. Exports CSS variables and a Figma file.",
82
+ status: "new",
83
+ hoursAgo: 26,
84
+ },
85
+ {
86
+ submitterName: "Felix Wagner",
87
+ submitterEmail: "felix@example.com",
88
+ name: "Logline",
89
+ tagline: "Structured logs you can search from the terminal.",
90
+ url: "https://example.com/logline",
91
+ category: "DevOps",
92
+ tags: "logging, cli",
93
+ description: "Pipe JSON logs in, then filter with a short query language. Keeps a week of history on disk.",
94
+ status: "new",
95
+ hoursAgo: 40,
96
+ },
97
+ {
98
+ submitterName: "Isabel Cruz",
99
+ submitterEmail: "isabel@example.com",
100
+ name: "Promptdeck",
101
+ tagline: "Version and diff the prompts your app sends to a model.",
102
+ url: "https://example.com/promptdeck",
103
+ category: "AI",
104
+ tags: "llm, prompts, versioning",
105
+ description: "Each prompt change gets a version and a side-by-side diff of outputs on your saved test inputs.",
106
+ status: "new",
107
+ hoursAgo: 55,
108
+ },
109
+ {
110
+ submitterName: "Ruth Castillo",
111
+ submitterEmail: "ruth@example.com",
112
+ name: "Brineway",
113
+ tagline: "S3-compatible object storage at the edge.",
114
+ url: "https://example.com/brineway",
115
+ category: "Hosting",
116
+ tags: "storage, edge, s3",
117
+ description: "",
118
+ status: "approved",
119
+ hoursAgo: 150,
120
+ },
121
+ {
122
+ submitterName: "Daniel Park",
123
+ submitterEmail: "daniel@example.com",
124
+ name: "Countwell",
125
+ tagline: "Privacy-first product analytics you can self-host.",
126
+ url: "https://example.com/countwell",
127
+ category: "Analytics",
128
+ tags: "analytics, privacy",
129
+ description: "",
130
+ status: "approved",
131
+ hoursAgo: 210,
132
+ },
133
+ {
134
+ submitterName: "Luca Romano",
135
+ submitterEmail: "luca@example.com",
136
+ name: "Best AI Tools 2026",
137
+ tagline: "A list of 500 AI tools.",
138
+ url: "https://example.com/best-ai-tools",
139
+ category: "AI",
140
+ tags: "list",
141
+ description: "",
142
+ status: "rejected",
143
+ hoursAgo: 96,
144
+ },
145
+ ];