@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
@@ -6,7 +6,7 @@ import { EnsureGuest } from "@pylonsync/client";
6
6
  import { ArrowUpRight, ChevronUp } from "lucide-react";
7
7
  import { siteConfig } from "@/lib/site.config";
8
8
  import { LINK } from "@/components/marketing";
9
- import { parseTags, type ListingRow } from "@/lib/directory";
9
+ import { monogram, parseTags, type ListingRow } from "@/lib/directory";
10
10
 
11
11
  // The browse island: search box, category rail, and the list.
12
12
  // `db.useSearch("Listing", …)` is a LIVE faceted full-text search: it re-runs
@@ -89,18 +89,18 @@ function BrowseInner() {
89
89
  className="h-10 w-full border border-zinc-300 bg-white px-3 font-mono text-[13px] text-zinc-900 outline-none placeholder:text-zinc-400 focus:border-brand"
90
90
  />
91
91
 
92
- <div className="mt-6 grid gap-8 lg:grid-cols-[180px_1fr]">
92
+ <div className="mt-6 grid grid-cols-[minmax(0,1fr)] gap-6 lg:grid-cols-[180px_minmax(0,1fr)] lg:gap-8">
93
93
  {/* Category rail. Live counts per category come from the search. On
94
- narrow screens the same links run in a horizontal row. */}
95
- <aside>
94
+ narrow screens the same links wrap as a row of chips. */}
95
+ <aside className="min-w-0">
96
96
  <p className="border-b border-zinc-200 pb-2 text-[12px] font-medium uppercase tracking-wide text-zinc-500">
97
97
  Category
98
98
  </p>
99
- <ul className="mt-2 flex gap-x-4 gap-y-1 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible">{facetLinks}</ul>
99
+ <ul className="mt-3 flex flex-wrap gap-2 lg:mt-2 lg:flex-col lg:gap-0">{facetLinks}</ul>
100
100
  </aside>
101
101
 
102
102
  {/* Results */}
103
- <div>
103
+ <div className="min-w-0">
104
104
  <div className="flex items-center justify-between border-b border-zinc-200 pb-2 text-[13px] text-zinc-500">
105
105
  <span className={MONO_SMALL}>
106
106
  {search.total} {search.total === 1 ? "tool" : "tools"}
@@ -163,14 +163,16 @@ function FacetLink({
163
163
  onClick: () => void;
164
164
  }) {
165
165
  return (
166
- <li className="shrink-0">
166
+ <li>
167
167
  <button
168
168
  type="button"
169
169
  onClick={onClick}
170
170
  aria-pressed={active}
171
171
  className={
172
- "flex w-full items-baseline gap-2 py-0.5 text-left text-[14px] lg:justify-between " +
173
- (active ? "font-medium text-zinc-900" : "text-brand hover:underline")
172
+ "flex w-full items-baseline gap-2 border px-2.5 py-1 text-left text-[13px] lg:justify-between lg:border-0 lg:px-0 lg:py-0.5 lg:text-[14px] " +
173
+ (active
174
+ ? "border-zinc-900 font-medium text-zinc-900"
175
+ : "border-zinc-200 text-brand hover:border-zinc-300 lg:hover:underline")
174
176
  }
175
177
  >
176
178
  <span>{label}</span>
@@ -183,8 +185,9 @@ function FacetLink({
183
185
  function ListingRowItem({ listing }: { listing: ListingRow }) {
184
186
  const tags = parseTags(listing.tags);
185
187
  return (
186
- <li className="grid grid-cols-[44px_1fr_20px] items-start gap-3 border-b border-zinc-200 py-3">
188
+ <li className="grid grid-cols-[44px_36px_minmax(0,1fr)_20px] items-start gap-3 border-b border-zinc-200 py-3">
187
189
  <VoteButton listingId={listing.id} votes={listing.votes} />
190
+ <ToolMark name={listing.name} />
188
191
  <div className="min-w-0">
189
192
  <div className="flex flex-wrap items-baseline gap-x-2">
190
193
  <a href={listing.url} target="_blank" rel="noopener noreferrer" className="text-[14px] font-semibold text-zinc-900 hover:text-brand">
@@ -211,6 +214,20 @@ function ListingRowItem({ listing }: { listing: ListingRow }) {
211
214
  );
212
215
  }
213
216
 
217
+ // A tool's monogram mark (see monogram in lib/directory.ts).
218
+ function ToolMark({ name }: { name: string }) {
219
+ const m = monogram(name);
220
+ return (
221
+ <span
222
+ aria-hidden
223
+ className="flex size-9 items-center justify-center rounded-md font-mono text-[13px] font-semibold tracking-tight"
224
+ style={{ backgroundColor: m.bg, color: m.fg }}
225
+ >
226
+ {m.letters}
227
+ </span>
228
+ );
229
+ }
230
+
214
231
  // The upvote control. Clicking calls the public `upvote` mutation; the count
215
232
  // you see comes from the LIVE search, so it ticks up here and in every other
216
233
  // open tab. localStorage remembers your votes so the arrow stays "voted".
@@ -267,8 +284,9 @@ function BrowseSkeleton({ bare }: { bare?: boolean }) {
267
284
  const list = (
268
285
  <ul>
269
286
  {Array.from({ length: 6 }).map((_, i) => (
270
- <li key={i} className="grid grid-cols-[44px_1fr_20px] gap-3 border-b border-zinc-200 py-3">
287
+ <li key={i} className="grid grid-cols-[44px_36px_minmax(0,1fr)_20px] gap-3 border-b border-zinc-200 py-3">
271
288
  <div className="mx-auto h-8 w-6 animate-pulse bg-zinc-100" />
289
+ <div className="size-9 animate-pulse rounded-md bg-zinc-100" />
272
290
  <div className="space-y-2 py-0.5">
273
291
  <div className="h-3.5 w-1/3 animate-pulse bg-zinc-100" />
274
292
  <div className="h-3 w-2/3 animate-pulse bg-zinc-100" />
@@ -281,7 +299,7 @@ function BrowseSkeleton({ bare }: { bare?: boolean }) {
281
299
  return (
282
300
  <div>
283
301
  <div className="h-10 w-full animate-pulse border border-zinc-200 bg-zinc-50" />
284
- <div className="mt-6 grid gap-8 lg:grid-cols-[180px_1fr]">
302
+ <div className="mt-6 grid grid-cols-[minmax(0,1fr)] gap-8 lg:grid-cols-[180px_minmax(0,1fr)]">
285
303
  <div className="hidden h-40 animate-pulse bg-zinc-50 lg:block" />
286
304
  {list}
287
305
  </div>
@@ -16,11 +16,13 @@ export const metadata: Metadata = {
16
16
  // Listing table. Copy comes from siteConfig; the listings seed on first visit.
17
17
  // Doesn't read `auth`, so the public page stays cacheable.
18
18
  export default function LandingPage() {
19
- const { brand, intro } = siteConfig;
19
+ const { intro } = siteConfig;
20
20
  return (
21
- <div className={`${WRAP} pb-20 pt-8`}>
22
- <h1 className="font-display text-[22px] leading-tight text-zinc-900">{brand.name}</h1>
23
- <p className="mt-1 max-w-2xl text-[14px] leading-relaxed text-zinc-600">{intro.description}</p>
21
+ <div className={`${WRAP} pb-20 pt-10 sm:pt-14`}>
22
+ <h1 className="text-balance text-[1.75rem] font-semibold leading-tight tracking-[-0.02em] text-zinc-900 sm:text-[2.25rem]">
23
+ {intro.headline}
24
+ </h1>
25
+ <p className="mt-2 max-w-2xl text-[15px] leading-relaxed text-zinc-600">{intro.description}</p>
24
26
  <div id="browse" className="mt-5">
25
27
  <DirectoryBrowse />
26
28
  </div>
@@ -59,7 +59,7 @@ export function AuthForm() {
59
59
  required
60
60
  autoComplete="email"
61
61
  placeholder="you@yourbusiness.com"
62
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
62
+ className="h-10 w-full border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand"
63
63
  />
64
64
  </label>
65
65
  <label className="block">
@@ -71,18 +71,18 @@ export function AuthForm() {
71
71
  required
72
72
  autoComplete={mode === "login" ? "current-password" : "new-password"}
73
73
  placeholder={mode === "login" ? "Your password" : "At least 10 characters"}
74
- className="h-10 w-full rounded-lg border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand focus:ring-2 focus:ring-brand/20"
74
+ className="h-10 w-full border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-brand"
75
75
  />
76
76
  </label>
77
77
  {error ? (
78
- <p className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-[13px] leading-snug text-red-700">
78
+ <p className="border border-red-200 bg-red-50 px-3 py-2 text-[13px] leading-snug text-red-700">
79
79
  {error}
80
80
  </p>
81
81
  ) : null}
82
82
  <button
83
83
  type="submit"
84
84
  disabled={pending}
85
- className="inline-flex h-10 w-full items-center justify-center rounded-lg bg-zinc-900 text-sm font-medium text-white transition-colors hover:bg-zinc-700 disabled:opacity-60"
85
+ className="inline-flex h-10 w-full items-center justify-center bg-zinc-900 text-sm font-medium text-white transition-colors hover:bg-zinc-700 disabled:opacity-60"
86
86
  >
87
87
  {pending ? "…" : mode === "login" ? "Sign in" : "Create account"}
88
88
  </button>
@@ -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>