@pylonsync/create-pylon 0.19.1 → 0.20.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 (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -3,37 +3,15 @@
3
3
  import React, { useState } from "react";
4
4
  import { db, useRouter } from "@pylonsync/react";
5
5
  import { ImagePlus } from "lucide-react";
6
- import { Alert, AlertDescription } from "@/components/ui/alert";
7
- import { Button } from "@/components/ui/button";
8
- import {
9
- Card,
10
- CardContent,
11
- CardDescription,
12
- CardHeader,
13
- CardTitle,
14
- } from "@/components/ui/card";
15
- import {
16
- Field,
17
- FieldDescription,
18
- FieldGroup,
19
- FieldLabel,
20
- FieldSeparator,
21
- } from "@/components/ui/field";
22
- import { Input } from "@/components/ui/input";
23
- import {
24
- NativeSelect,
25
- NativeSelectOption,
26
- } from "@/components/ui/native-select";
6
+ import { cn } from "@/lib/utils";
27
7
  import { Spinner } from "@/components/ui/spinner";
28
- import { Textarea } from "@/components/ui/textarea";
8
+ import { CATEGORIES, CONDITIONS } from "../lib/catalog";
9
+ import { ListingCard } from "./ListingCard";
29
10
  import { AuthGate, MarketProvider, useIdentity } from "./MarketProvider";
30
- import { conditionLabel, makeSlug } from "./market";
11
+ import { conditionLabel, makeSlug, type Listing } from "./market";
31
12
 
32
- const CATEGORIES = [
33
- "furniture", "electronics", "cameras", "bikes", "audio", "kitchen",
34
- "instruments", "outdoor", "apparel", "other",
35
- ];
36
- const CONDITIONS = ["new", "like-new", "good", "fair"];
13
+ const INPUT =
14
+ "h-11 w-full rounded-xl bg-card px-3.5 text-[15px] shadow-[var(--shadow-border)] outline-none placeholder:text-muted-foreground focus:ring-2 focus:ring-ring focus-visible:outline-none";
37
15
 
38
16
  async function uploadListingPhoto(file: File) {
39
17
  const initResponse = await fetch("/api/files/init", {
@@ -48,7 +26,7 @@ async function uploadListingPhoto(file: File) {
48
26
  }),
49
27
  });
50
28
  if (!initResponse.ok) throw new Error("Could not prepare that upload.");
51
- const init = await initResponse.json() as { assetId: string; uploadUrl: string };
29
+ const init = (await initResponse.json()) as { assetId: string; uploadUrl: string };
52
30
 
53
31
  const uploadResponse = await fetch(init.uploadUrl, {
54
32
  method: "PUT",
@@ -66,18 +44,39 @@ async function uploadListingPhoto(file: File) {
66
44
  return confirmResponse.json() as Promise<{ id: string; url: string; size: number }>;
67
45
  }
68
46
 
47
+ function isPhotoUrl(value: string): boolean {
48
+ if (value.startsWith("/")) return true;
49
+ try {
50
+ return ["http:", "https:"].includes(new URL(value).protocol);
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ function Label({ htmlFor, children }: { htmlFor?: string; children: React.ReactNode }) {
57
+ return (
58
+ <label htmlFor={htmlFor} className="text-sm font-medium">
59
+ {children}
60
+ </label>
61
+ );
62
+ }
63
+
69
64
  function Form() {
70
- // Rendered inside <AuthGate>, so identity is guaranteed non-null here.
65
+ // Rendered inside <AuthGate>, so identity is non-null here.
71
66
  const identity = useIdentity();
72
67
  const userId = identity?.userId ?? "";
73
68
  const name = identity?.name ?? "you";
74
69
  const router = useRouter();
75
70
  const [title, setTitle] = useState("");
76
71
  const [description, setDescription] = useState("");
72
+ // The photo is either an upload (a same-origin /api/files path) or a link
73
+ // the seller pastes. An upload clears the link field.
77
74
  const [imageUrl, setImageUrl] = useState("");
75
+ const uploaded = imageUrl.startsWith("/");
78
76
  const [price, setPrice] = useState("");
79
- const [category, setCategory] = useState(CATEGORIES[0]);
77
+ const [category, setCategory] = useState(CATEGORIES[0]!.id);
80
78
  const [condition, setCondition] = useState("good");
79
+ const [location, setLocation] = useState("");
81
80
  const [photoBusy, setPhotoBusy] = useState(false);
82
81
  const [busy, setBusy] = useState(false);
83
82
  const [err, setErr] = useState<string | null>(null);
@@ -110,30 +109,21 @@ function Form() {
110
109
  e.preventDefault();
111
110
  const value = Number.parseFloat(price);
112
111
  if (!title.trim()) return setErr("Give your item a title.");
113
- if (!imageUrl.startsWith("/")) {
114
- try {
115
- const photo = new URL(imageUrl);
116
- if (!["http:", "https:"].includes(photo.protocol)) throw new Error();
117
- } catch {
118
- return setErr("Upload a photo or add a valid photo URL.");
119
- }
120
- }
112
+ if (!isPhotoUrl(imageUrl)) return setErr("Upload a photo or add a photo link.");
121
113
  if (!Number.isFinite(value) || value < 0) return setErr("Set a price.");
122
114
  setBusy(true);
123
115
  setErr(null);
124
- // Local-first by default — no createListing function, no opt-in
125
- // optimism flag. `db.insert` paints the listing into the local store
126
- // synchronously (it's in the "just listed" ticker before the network
127
- // call even leaves the tab) and pushes in the background. `sellerId`
128
- // is declared `field.owner()` in app.ts, so the server stamps and
129
- // verifies it from the session — we send our own id only so the
130
- // optimistic row is complete; a forged seller id would be rejected.
116
+ // db.insert writes the listing to the local store at once (it shows in
117
+ // every open browse grid and activity feed) and pushes to the server in
118
+ // the background. `sellerId` is `field.owner()` in app.ts, so the server
119
+ // stamps and verifies it from the session; a forged id is rejected.
131
120
  const seed = Math.random().toString(36).slice(2, 8);
132
121
  const slug = makeSlug(title.trim(), seed);
133
122
  try {
134
123
  await db.insert("Listing", {
135
124
  sellerId: userId,
136
125
  sellerName: name,
126
+ location: location.trim(),
137
127
  title: title.trim(),
138
128
  slug,
139
129
  description: description.trim(),
@@ -152,147 +142,246 @@ function Form() {
152
142
  }
153
143
  }
154
144
 
145
+ const draft: Listing = {
146
+ id: "preview",
147
+ sellerId: userId,
148
+ sellerName: name,
149
+ location: location.trim() || undefined,
150
+ title: title.trim() || "Your item title",
151
+ slug: "",
152
+ description,
153
+ price: Number.parseFloat(price) || 0,
154
+ category,
155
+ condition,
156
+ status: "active",
157
+ imageUrl: isPhotoUrl(imageUrl) ? imageUrl : undefined,
158
+ seed: "preview",
159
+ createdAt: new Date().toISOString(),
160
+ };
161
+
155
162
  return (
156
- <form onSubmit={submit}>
157
- <FieldGroup className="gap-5">
158
- <Field>
159
- <FieldLabel htmlFor="title">Title</FieldLabel>
160
- <Input
161
- id="title"
162
- name="title"
163
- autoComplete="off"
164
- value={title}
165
- onChange={(e) => setTitle(e.target.value)}
166
- placeholder="e.g. Herman Miller Aeron, size B"
167
- />
168
- </Field>
169
- <Field>
170
- <FieldLabel htmlFor="description">Description</FieldLabel>
171
- <Textarea
172
- id="description"
173
- name="description"
174
- autoComplete="off"
175
- value={description}
176
- onChange={(e) => setDescription(e.target.value)}
177
- placeholder="Condition details, dimensions, why you're selling…"
178
- rows={4}
179
- />
180
- </Field>
181
- <Field>
182
- <FieldLabel htmlFor="listing-photo">Photo</FieldLabel>
183
- <label
184
- htmlFor="listing-photo"
185
- className="flex min-h-28 cursor-pointer flex-col items-center justify-center gap-2 rounded-xl border border-dashed border-border bg-muted/40 px-5 py-6 text-center transition-colors hover:bg-muted/70 focus-within:ring-2 focus-within:ring-ring"
186
- >
187
- <ImagePlus aria-hidden="true" className="size-5 text-muted-foreground" />
188
- <span className="text-sm font-medium">
189
- {photoBusy ? "Uploading photo…" : imageUrl ? "Replace photo" : "Upload a photo"}
190
- </span>
191
- <span className="text-xs text-muted-foreground">JPG, PNG, or WebP up to 8 MB</span>
192
- <Input
193
- id="listing-photo"
194
- name="photo"
195
- type="file"
196
- accept="image/jpeg,image/png,image/webp"
197
- onChange={selectPhoto}
198
- disabled={photoBusy}
199
- className="sr-only"
200
- />
201
- </label>
202
- <FieldSeparator>or use a link</FieldSeparator>
203
- <FieldLabel htmlFor="imageUrl" className="sr-only">
204
- Photo URL
205
- </FieldLabel>
206
- <Input
207
- id="imageUrl"
208
- name="imageUrl"
209
- type="url"
210
- autoComplete="off"
211
- spellCheck={false}
212
- value={imageUrl}
213
- onChange={(e) => setImageUrl(e.target.value)}
214
- placeholder="https://example.com/item.jpg"
215
- aria-describedby="image-help"
216
- />
217
- <FieldDescription id="image-help">
218
- Clear, well-lit photos get more interest.
219
- </FieldDescription>
220
- {imageUrl ? (
221
- <div className="mt-3 aspect-[16/10] overflow-hidden rounded-xl bg-muted shadow-[var(--shadow-border)]">
222
- <img
223
- src={imageUrl}
224
- alt="Listing photo preview"
225
- width="1200"
226
- height="750"
227
- className="h-full w-full object-cover outline outline-1 -outline-offset-1 outline-border"
163
+ <div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_300px] xl:gap-16">
164
+ <form
165
+ onSubmit={submit}
166
+ // Fields are checked in submit(). Native validation would reject the
167
+ // same-origin path of an uploaded photo in the type="url" field.
168
+ noValidate
169
+ className="flex min-w-0 flex-col gap-7"
170
+ >
171
+ <div className="flex flex-col gap-2">
172
+ <Label htmlFor="listing-photo">Photo</Label>
173
+ <label
174
+ htmlFor="listing-photo"
175
+ className="group flex min-h-44 cursor-pointer flex-col items-center justify-center gap-2 rounded-2xl border border-dashed border-input bg-card/60 px-5 py-8 text-center transition-colors hover:bg-card focus-within:ring-2 focus-within:ring-ring"
176
+ >
177
+ {isPhotoUrl(imageUrl) && !photoBusy ? (
178
+ <img
179
+ src={imageUrl}
180
+ alt="Listing photo"
181
+ width="96"
182
+ height="120"
183
+ className="h-[120px] w-24 rounded-lg object-cover shadow-[var(--shadow-float)]"
184
+ />
185
+ ) : (
186
+ <span className="grid size-11 place-items-center rounded-full bg-muted transition-transform duration-300 group-hover:scale-105">
187
+ {photoBusy ? (
188
+ <Spinner />
189
+ ) : (
190
+ <ImagePlus aria-hidden="true" strokeWidth={1.5} className="size-5" />
191
+ )}
192
+ </span>
193
+ )}
194
+ <span className="text-sm font-medium">
195
+ {photoBusy
196
+ ? "Uploading photo"
197
+ : imageUrl
198
+ ? "Replace photo"
199
+ : "Upload a photo"}
200
+ </span>
201
+ <span className="text-xs text-muted-foreground">
202
+ JPG, PNG, or WebP up to 8 MB. Natural light and a plain background work best.
203
+ </span>
204
+ <input
205
+ id="listing-photo"
206
+ name="photo"
207
+ type="file"
208
+ accept="image/jpeg,image/png,image/webp"
209
+ onChange={selectPhoto}
210
+ disabled={photoBusy}
211
+ className="sr-only"
228
212
  />
229
- </div>
230
- ) : null}
231
- </Field>
232
- <div className="grid grid-cols-2 gap-4">
233
- <Field>
234
- <FieldLabel htmlFor="price">Price ($)</FieldLabel>
235
- <Input
236
- id="price"
237
- name="price"
238
- type="number"
239
- inputMode="decimal"
213
+ </label>
214
+ <label htmlFor="imageUrl" className="sr-only">
215
+ Photo link
216
+ </label>
217
+ <input
218
+ id="imageUrl"
219
+ name="imageUrl"
220
+ type="url"
240
221
  autoComplete="off"
241
- min="0"
242
- step="1"
243
- value={price}
244
- onChange={(e) => setPrice(e.target.value)}
245
- placeholder="0"
222
+ spellCheck={false}
223
+ value={uploaded ? "" : imageUrl}
224
+ onChange={(e) => setImageUrl(e.target.value)}
225
+ placeholder="Or paste a photo link: https://…"
226
+ className={INPUT}
246
227
  />
247
- </Field>
248
- <Field>
249
- <FieldLabel htmlFor="condition">Condition</FieldLabel>
250
- <NativeSelect
251
- id="condition"
252
- name="condition"
253
- value={condition}
254
- onChange={(e) => setCondition(e.target.value)}
255
- >
228
+ </div>
229
+
230
+ <div className="flex flex-col gap-2">
231
+ <Label htmlFor="title">Title</Label>
232
+ <input
233
+ id="title"
234
+ name="title"
235
+ autoComplete="off"
236
+ value={title}
237
+ onChange={(e) => setTitle(e.target.value)}
238
+ placeholder="Walnut desk lamp, 1970s"
239
+ maxLength={80}
240
+ className={INPUT}
241
+ />
242
+ </div>
243
+
244
+ <fieldset className="flex flex-col gap-2">
245
+ <legend className="mb-2 text-sm font-medium">Category</legend>
246
+ <div className="flex flex-wrap gap-1.5">
247
+ {CATEGORIES.map((c) => (
248
+ <label
249
+ key={c.id}
250
+ className={cn(
251
+ "inline-flex min-h-9 cursor-pointer items-center rounded-full px-3.5 text-[13px] font-medium transition-[background-color,color,box-shadow] duration-200 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring",
252
+ category === c.id
253
+ ? "bg-foreground text-background"
254
+ : "bg-card shadow-[var(--shadow-border)] hover:shadow-[var(--shadow-border-hover)]",
255
+ )}
256
+ >
257
+ <input
258
+ type="radio"
259
+ name="category"
260
+ value={c.id}
261
+ checked={category === c.id}
262
+ onChange={() => setCategory(c.id)}
263
+ className="sr-only"
264
+ />
265
+ {c.label}
266
+ </label>
267
+ ))}
268
+ </div>
269
+ </fieldset>
270
+
271
+ <fieldset className="flex flex-col gap-2">
272
+ <legend className="mb-2 text-sm font-medium">Condition</legend>
273
+ <div className="grid grid-cols-4 gap-1 rounded-full bg-muted p-1">
256
274
  {CONDITIONS.map((c) => (
257
- <NativeSelectOption key={c} value={c}>
275
+ <label
276
+ key={c}
277
+ className={cn(
278
+ "flex min-h-9 cursor-pointer items-center justify-center rounded-full text-[13px] font-medium transition-[background-color,color,box-shadow] duration-200 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring",
279
+ condition === c
280
+ ? "bg-card text-foreground shadow-[var(--shadow-border)]"
281
+ : "text-muted-foreground hover:text-foreground",
282
+ )}
283
+ >
284
+ <input
285
+ type="radio"
286
+ name="condition"
287
+ value={c}
288
+ checked={condition === c}
289
+ onChange={() => setCondition(c)}
290
+ className="sr-only"
291
+ />
258
292
  {conditionLabel(c)}
259
- </NativeSelectOption>
293
+ </label>
260
294
  ))}
261
- </NativeSelect>
262
- </Field>
263
- </div>
264
- <Field>
265
- <FieldLabel htmlFor="category">Category</FieldLabel>
266
- <NativeSelect
267
- id="category"
268
- name="category"
269
- value={category}
270
- onChange={(e) => setCategory(e.target.value)}
271
- >
272
- {CATEGORIES.map((c) => (
273
- <NativeSelectOption key={c} value={c}>
274
- {c[0]?.toUpperCase()}{c.slice(1)}
275
- </NativeSelectOption>
276
- ))}
277
- </NativeSelect>
278
- </Field>
279
- <div aria-live="polite">
280
- {err ? (
281
- <Alert variant="destructive">
282
- <AlertDescription>{err}</AlertDescription>
283
- </Alert>
284
- ) : null}
285
- </div>
286
- <Button type="submit" disabled={busy || photoBusy} className="w-full">
287
- {busy ? <Spinner data-icon="inline-start" /> : null}
288
- {busy ? "Posting…" : "Post listing"}
289
- </Button>
290
- <p className="text-center text-xs text-muted-foreground">
291
- Posting as <span className="font-medium">{name}</span>. Buyers'
292
- offers land in <a href="/me" className="underline">Dashboard</a>.
293
- </p>
294
- </FieldGroup>
295
- </form>
295
+ </div>
296
+ </fieldset>
297
+
298
+ <div className="grid gap-5 sm:grid-cols-2">
299
+ <div className="flex flex-col gap-2">
300
+ <Label htmlFor="price">Price</Label>
301
+ <div className="flex h-11 items-center rounded-xl bg-card px-3.5 shadow-[var(--shadow-border)] focus-within:ring-2 focus-within:ring-ring">
302
+ <span className="text-[15px] text-muted-foreground">$</span>
303
+ <input
304
+ id="price"
305
+ name="price"
306
+ type="number"
307
+ inputMode="decimal"
308
+ autoComplete="off"
309
+ min="0"
310
+ step="1"
311
+ value={price}
312
+ onChange={(e) => setPrice(e.target.value)}
313
+ placeholder="0"
314
+ className="h-full w-full bg-transparent pl-1 text-[15px] tabular-nums outline-none focus-visible:outline-none"
315
+ />
316
+ </div>
317
+ </div>
318
+ <div className="flex flex-col gap-2">
319
+ <Label htmlFor="location">Location</Label>
320
+ <input
321
+ id="location"
322
+ name="location"
323
+ autoComplete="address-level2"
324
+ value={location}
325
+ onChange={(e) => setLocation(e.target.value)}
326
+ placeholder="Denver, CO"
327
+ className={INPUT}
328
+ />
329
+ </div>
330
+ </div>
331
+
332
+ <div className="flex flex-col gap-2">
333
+ <Label htmlFor="description">Description</Label>
334
+ <textarea
335
+ id="description"
336
+ name="description"
337
+ autoComplete="off"
338
+ value={description}
339
+ onChange={(e) => setDescription(e.target.value)}
340
+ placeholder="Condition, measurements, what is included, pickup or shipping"
341
+ rows={5}
342
+ className="w-full resize-y rounded-xl bg-card px-3.5 py-3 text-[15px] leading-6 shadow-[var(--shadow-border)] outline-none placeholder:text-muted-foreground focus:ring-2 focus:ring-ring focus-visible:outline-none"
343
+ />
344
+ </div>
345
+
346
+ <div aria-live="polite">
347
+ {err ? (
348
+ <p className="rounded-xl bg-destructive/10 px-3 py-2 text-sm text-destructive">
349
+ {err}
350
+ </p>
351
+ ) : null}
352
+ </div>
353
+
354
+ <div className="flex flex-col gap-3 border-t border-border/70 pt-6 sm:flex-row sm:items-center sm:justify-between">
355
+ <p className="text-sm text-muted-foreground">
356
+ Posting as <span className="font-medium text-foreground">{name}</span>.
357
+ Offers arrive on your{" "}
358
+ <a href="/me" className="underline underline-offset-4">
359
+ dashboard
360
+ </a>
361
+ .
362
+ </p>
363
+ <button
364
+ type="submit"
365
+ disabled={busy || photoBusy}
366
+ className="inline-flex min-h-12 items-center justify-center gap-2 rounded-full bg-primary px-7 text-[15px] font-medium text-primary-foreground transition-[scale] active:scale-[0.98] disabled:opacity-60"
367
+ >
368
+ {busy ? <Spinner /> : null}
369
+ {busy ? "Posting" : "Post listing"}
370
+ </button>
371
+ </div>
372
+ </form>
373
+
374
+ <aside className="hidden lg:block">
375
+ <div className="sticky top-24">
376
+ <h2 className="mb-3 text-sm font-medium text-muted-foreground">
377
+ Preview in the grid
378
+ </h2>
379
+ <div className="pointer-events-none" inert>
380
+ <ListingCard listing={draft} sizes="300px" />
381
+ </div>
382
+ </div>
383
+ </aside>
384
+ </div>
296
385
  );
297
386
  }
298
387
 
@@ -301,19 +390,9 @@ export function SellForm() {
301
390
  <MarketProvider>
302
391
  <AuthGate
303
392
  title="Sign in to list an item"
304
- blurb="Selling needs an account so your listings stay tied to you. The demo account is ready; just select Log in."
393
+ blurb="Listings belong to an account so offers reach you. The demo account is filled in."
305
394
  >
306
- <Card>
307
- <CardHeader>
308
- <CardTitle>Item details</CardTitle>
309
- <CardDescription>
310
- Add a clear photo and enough detail for buyers to decide quickly.
311
- </CardDescription>
312
- </CardHeader>
313
- <CardContent>
314
- <Form />
315
- </CardContent>
316
- </Card>
395
+ <Form />
317
396
  </AuthGate>
318
397
  </MarketProvider>
319
398
  );
@@ -48,6 +48,7 @@ export function ThemeToggle() {
48
48
  type="button"
49
49
  variant="ghost"
50
50
  size="icon"
51
+ className="rounded-full text-muted-foreground hover:text-foreground"
51
52
  aria-label={isDark ? "Use light theme" : "Use dark theme"}
52
53
  title={isDark ? "Use light theme" : "Use dark theme"}
53
54
  onClick={toggleTheme}
@@ -3,15 +3,14 @@
3
3
  import React, { useEffect, useState } from "react";
4
4
  import { db } from "@pylonsync/react";
5
5
  import { Heart } from "lucide-react";
6
- import { Button } from "@/components/ui/button";
7
6
  import { cn } from "@/lib/utils";
8
7
  import { bootClient, readIdentity, type Watch } from "./market";
9
8
 
10
9
  // Heart toggle that saves a listing to your private watchlist. Self-contained
11
10
  // (no provider needed): boots the client, reads identity, and toggles the
12
- // Watch row with optimistic db.insert / db.delete — the live query below flips
13
- // the fill instantly. Hidden for signed-out visitors (watchlists are a
14
- // logged-in feature). The mounted gate keeps db.useQuery off the SSR pass.
11
+ // Watch row with optimistic db.insert / db.delete; the live query below flips
12
+ // the fill instantly. Signed-out visitors are sent to the sign-in card on
13
+ // /me. The mounted gate keeps db.useQuery off the SSR pass.
15
14
  export function WatchButton(props: {
16
15
  listingId: string;
17
16
  listingTitle: string;
@@ -57,43 +56,44 @@ function Inner({
57
56
  : undefined;
58
57
  const watched = !!mine;
59
58
 
60
- // No heart for signed-out visitors.
61
- if (!identity) return null;
62
-
63
59
  function toggle(e: React.MouseEvent) {
64
60
  // Stop the click from bubbling into the surrounding card link.
65
61
  e.preventDefault();
66
62
  e.stopPropagation();
63
+ if (!identity) {
64
+ // Watchlists belong to an account; the dashboard shows the sign-in card.
65
+ window.location.assign("/me");
66
+ return;
67
+ }
67
68
  if (mine) {
68
69
  void db.delete("Watch", mine.id);
69
70
  } else {
70
- void db.insert("Watch", { userId: identity!.userId, listingId, listingTitle });
71
+ void db.insert("Watch", { userId: identity.userId, listingId, listingTitle });
71
72
  }
72
73
  }
73
74
 
74
75
  return (
75
- <Button
76
+ <button
76
77
  type="button"
77
- variant="outline"
78
- size="icon"
79
78
  onClick={toggle}
80
79
  aria-pressed={watched}
81
80
  aria-label={watched ? "Remove from watchlist" : "Save to watchlist"}
82
81
  title={watched ? "Saved" : "Save to watchlist"}
83
82
  className={cn(
84
- "rounded-full bg-background/85 backdrop-blur",
83
+ "grid size-9 place-items-center rounded-full bg-white/92 text-neutral-900 shadow-[var(--shadow-float)] backdrop-blur transition-[scale,background-color] duration-200 hover:bg-white active:scale-90",
85
84
  className,
86
85
  )}
87
86
  >
88
87
  <Heart
89
88
  aria-hidden="true"
89
+ strokeWidth={1.75}
90
90
  className={cn(
91
- "transition-[color,fill,scale] duration-200",
91
+ "size-[17px] transition-[color,fill,scale] duration-300 ease-[var(--ease-out-soft)]",
92
92
  watched
93
- ? "scale-100 fill-foreground text-foreground"
94
- : "scale-95 fill-transparent text-foreground/70",
93
+ ? "scale-100 fill-signal text-signal"
94
+ : "scale-95 fill-transparent text-neutral-900",
95
95
  )}
96
96
  />
97
- </Button>
97
+ </button>
98
98
  );
99
99
  }