@pylonsync/create-pylon 0.19.0 → 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
@@ -1,111 +1,117 @@
1
1
  import React from "react";
2
2
  import { Link } from "@pylonsync/react";
3
- import { Command, FileText, LogOut, Users } from "lucide-react";
3
+ import { LogOut, Search } from "lucide-react";
4
4
  import { cn } from "@/lib/utils";
5
+ import { BRAND } from "@/lib/brand";
6
+ import { NAV } from "@/components/nav";
5
7
  import { Avatar } from "@/components/avatar";
8
+ import { BrandMark } from "@/components/brand-mark";
6
9
  import { Kbd } from "@/components/kbd";
7
10
 
8
- export interface NavItem {
9
- href: string;
10
- label: string;
11
- icon: React.ReactNode;
12
- count?: number;
11
+ /** True when `href` is the section `pathname` belongs to. */
12
+ export function isActive(href: string, pathname: string): boolean {
13
+ if (href === "/") return pathname === "/";
14
+ return pathname === href || pathname.startsWith(`${href}/`);
13
15
  }
14
16
 
15
- export const NAV: Array<Omit<NavItem, "count">> = [
16
- { href: "/", label: "Invoices", icon: <FileText /> },
17
- { href: "/clients", label: "Clients", icon: <Users /> },
18
- ];
19
-
20
17
  /**
21
- * The fixed left rail. Presentational: the active path, the counts, and the
22
- * sign-out handler all arrive as props, so it renders identically in a test.
18
+ * The navigation rail: brand, search, links with counts, and the signed-in
19
+ * person. The same component renders as the fixed rail on wide screens and
20
+ * inside the drawer on phones (see components/app-shell.tsx).
21
+ *
22
+ * Presentational: the path, counts, and handlers arrive as props, so it
23
+ * renders the same in a test.
23
24
  */
24
25
  export function Sidebar({
25
- workspace,
26
- email,
26
+ user,
27
27
  pathname,
28
28
  counts,
29
29
  onOpenCommand,
30
+ onNavigate,
30
31
  onSignOut,
32
+ className,
31
33
  }: {
32
- workspace: string;
33
- email: string;
34
+ user: { name: string; email: string };
34
35
  pathname: string;
36
+ /** Badge per nav href. Omit a key to show no number. */
35
37
  counts?: Record<string, number>;
36
38
  onOpenCommand: () => void;
39
+ /** Called after a link is chosen, so the phone drawer can close. */
40
+ onNavigate?: () => void;
37
41
  onSignOut: () => void;
42
+ className?: string;
38
43
  }) {
39
44
  return (
40
- <aside className="flex w-[224px] shrink-0 flex-col border-r border-border bg-surface-1">
41
- <div className="flex h-12 items-center gap-2 px-3">
42
- <span className="flex size-6 shrink-0 items-center justify-center rounded-md bg-primary text-[11px] font-bold text-primary-foreground">
43
- {workspace.slice(0, 1).toUpperCase()}
45
+ <div className={cn("flex h-full w-full flex-col bg-surface-1", className)}>
46
+ <div className="flex h-13 shrink-0 items-center gap-2.5 px-4">
47
+ <BrandMark />
48
+ <span className="truncate text-[14px] font-semibold tracking-[-0.01em]">
49
+ {BRAND.name}
44
50
  </span>
45
- <span className="truncate text-[13px] font-semibold">{workspace}</span>
46
51
  </div>
47
52
 
48
- <div className="px-2 pb-2">
53
+ <div className="px-3 pb-3">
49
54
  <button
50
55
  type="button"
51
56
  onClick={onOpenCommand}
52
57
  className={cn(
53
- "flex w-full items-center gap-2 rounded-md border border-border bg-background px-2 py-1.5",
54
- "text-[12px] text-muted-foreground transition-colors hover:border-ring/40 hover:text-foreground",
58
+ "flex h-8 w-full items-center gap-2 rounded-md border border-border bg-background px-2.5",
59
+ "text-[12.5px] text-muted-foreground shadow-[0_1px_1px_rgb(0_0_0/0.03)] transition-colors",
60
+ "hover:border-foreground/15 hover:text-foreground",
55
61
  )}
56
62
  >
57
- <Command className="size-3.5" />
58
- <span className="flex-1 text-left">Search…</span>
59
- <Kbd>⌘K</Kbd>
63
+ <Search className="size-3.5" />
64
+ <span className="flex-1 text-left">Search</span>
65
+ <Kbd className="max-md:hidden">⌘K</Kbd>
60
66
  </button>
61
67
  </div>
62
68
 
63
- <nav className="flex-1 space-y-0.5 px-2" aria-label="Main">
69
+ <nav className="flex-1 space-y-px overflow-y-auto px-3" aria-label="Main">
64
70
  {NAV.map((item) => {
65
- const active =
66
- item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
71
+ const active = isActive(item.href, pathname);
72
+ const count = counts?.[item.href];
67
73
  return (
68
74
  <Link
69
75
  key={item.href}
70
76
  href={item.href}
77
+ onClick={onNavigate}
71
78
  aria-current={active ? "page" : undefined}
72
79
  className={cn(
73
- "flex h-7 items-center gap-2 rounded-md px-2 text-[13px] transition-colors",
74
- "[&_svg]:size-3.5 [&_svg]:shrink-0",
80
+ "flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] transition-colors md:h-7.5",
81
+ "[&_svg]:size-4 [&_svg]:shrink-0",
75
82
  active
76
- ? "bg-surface-2 font-medium text-foreground"
77
- : "text-muted-foreground hover:bg-surface-2/60 hover:text-foreground",
83
+ ? "bg-foreground/[0.06] font-medium text-foreground [&_svg]:text-primary"
84
+ : "text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground",
78
85
  )}
79
86
  >
80
87
  {item.icon}
81
88
  <span className="flex-1 truncate">{item.label}</span>
82
- {counts?.[item.href] ? (
83
- <span className="tabular text-[11px] text-muted-foreground">
84
- {counts[item.href]}
85
- </span>
89
+ {typeof count === "number" ? (
90
+ <span className="tabular text-[11.5px] text-muted-foreground">{count}</span>
86
91
  ) : null}
87
92
  </Link>
88
93
  );
89
94
  })}
90
95
  </nav>
91
96
 
92
- <div className="border-t border-border p-2">
93
- <div className="flex items-center gap-2 rounded-md px-1.5 py-1">
94
- <Avatar name={email} size="sm" />
95
- <span className="min-w-0 flex-1 truncate text-[12px] text-muted-foreground">
96
- {email}
97
- </span>
97
+ <div className="border-t border-border p-3">
98
+ <div className="flex items-center gap-2.5">
99
+ <Avatar name={user.name || user.email} />
100
+ <div className="min-w-0 flex-1 leading-tight">
101
+ <p className="truncate text-[12.5px] font-medium">{user.name || "Signed in"}</p>
102
+ <p className="truncate text-[11.5px] text-muted-foreground">{user.email}</p>
103
+ </div>
98
104
  <button
99
105
  type="button"
100
106
  onClick={onSignOut}
101
107
  title="Sign out"
102
108
  aria-label="Sign out"
103
- className="rounded p-1 text-muted-foreground transition-colors hover:bg-surface-2 hover:text-foreground"
109
+ className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-foreground/[0.06] hover:text-foreground"
104
110
  >
105
- <LogOut className="size-3.5" />
111
+ <LogOut className="size-4" />
106
112
  </button>
107
113
  </div>
108
114
  </div>
109
- </aside>
115
+ </div>
110
116
  );
111
117
  }
@@ -5,11 +5,11 @@ import { statusById } from "@/lib/billing";
5
5
  // "overdue" isn't a stored status — it's derived in lib/billing.ts — but it's
6
6
  // the one the eye needs to find first, so it gets the only alarming colour.
7
7
  const STYLE: Record<string, string> = {
8
- draft: "border-border text-muted-foreground",
9
- sent: "border-stage-qualified/40 text-stage-qualified",
10
- overdue: "border-destructive/50 text-destructive",
11
- paid: "border-stage-won/40 text-stage-won",
12
- void: "border-border text-muted-foreground line-through",
8
+ draft: "bg-foreground/[0.06] text-muted-foreground",
9
+ sent: "bg-stage-qualified/10 text-stage-qualified",
10
+ overdue: "bg-destructive/10 text-destructive",
11
+ paid: "bg-stage-won/10 text-stage-won",
12
+ void: "bg-foreground/[0.06] text-muted-foreground line-through",
13
13
  };
14
14
 
15
15
  const LABEL: Record<string, string> = { overdue: "Overdue" };
@@ -24,8 +24,8 @@ export function StatusBadge({
24
24
  return (
25
25
  <span
26
26
  className={cn(
27
- "inline-flex h-5 items-center rounded border px-1.5 text-[11px] font-medium whitespace-nowrap",
28
- STYLE[status] ?? "border-border text-muted-foreground",
27
+ "inline-flex h-5 items-center rounded px-1.5 text-[11.5px] font-medium whitespace-nowrap",
28
+ STYLE[status] ?? "bg-foreground/[0.06] text-muted-foreground",
29
29
  className,
30
30
  )}
31
31
  >
@@ -19,7 +19,7 @@ export function SummaryBar({
19
19
  }) {
20
20
  const s = summarize(invoices, items, payments, now);
21
21
  return (
22
- <dl className="grid grid-cols-2 gap-px border-b border-border bg-border sm:grid-cols-4">
22
+ <dl className="grid shrink-0 grid-cols-2 gap-px border-b border-border bg-border lg:grid-cols-4">
23
23
  <Metric label="Outstanding" value={money(s.outstandingCents)} hint="sent, unpaid" />
24
24
  <Metric
25
25
  label="Overdue"
@@ -49,17 +49,17 @@ function Metric({
49
49
  alarming?: boolean;
50
50
  }) {
51
51
  return (
52
- <div className="bg-background px-4 py-3">
53
- <dt className="text-[11px] text-muted-foreground">{label}</dt>
52
+ <div className="min-w-0 bg-background px-4 py-2.5 md:px-5 md:py-3.5">
53
+ <dt className="truncate text-[12px] text-muted-foreground">{label}</dt>
54
54
  <dd
55
55
  className={
56
- "tabular mt-0.5 text-[18px] font-semibold tracking-tight" +
56
+ "tabular mt-1 text-[18px] font-semibold leading-none tracking-[-0.02em] md:text-[22px]" +
57
57
  (alarming ? " text-destructive" : "")
58
58
  }
59
59
  >
60
60
  {value}
61
61
  </dd>
62
- <p className="mt-0.5 text-[11px] text-muted-foreground">{hint}</p>
62
+ <p className="mt-1 truncate text-[11.5px] text-muted-foreground md:mt-1.5">{hint}</p>
63
63
  </div>
64
64
  );
65
65
  }
@@ -5,15 +5,16 @@ import { cva, type VariantProps } from "class-variance-authority";
5
5
  import { cn } from "@/lib/utils";
6
6
 
7
7
  const buttonVariants = cva(
8
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-[13px] font-medium transition-[color,background-color,box-shadow,transform] duration-150 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
9
  {
10
10
  variants: {
11
11
  variant: {
12
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
+ default:
13
+ "bg-primary text-primary-foreground shadow-[inset_0_1px_0_rgb(255_255_255/0.15),0_1px_2px_rgb(0_0_0/0.1)] hover:bg-primary/90",
13
14
  destructive:
14
15
  "bg-destructive text-white hover:bg-destructive/90",
15
16
  outline:
16
- "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ "border border-input bg-background shadow-[0_1px_1px_rgb(0_0_0/0.03)] hover:bg-accent hover:text-accent-foreground",
17
18
  secondary:
18
19
  "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
20
  ghost: "hover:bg-accent hover:text-accent-foreground",
@@ -21,7 +22,7 @@ const buttonVariants = cva(
21
22
  },
22
23
  size: {
23
24
  default: "h-9 px-4 py-2",
24
- sm: "h-8 rounded-md px-3 text-xs",
25
+ sm: "h-8 rounded-md px-3 text-[12.5px] md:h-7.5 [&_svg]:size-3.5",
25
26
  lg: "h-10 rounded-md px-8",
26
27
  icon: "h-9 w-9",
27
28
  },
@@ -35,7 +35,7 @@ const DialogContent = React.forwardRef<
35
35
  <DialogPrimitive.Content
36
36
  ref={ref}
37
37
  className={cn(
38
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border bg-background p-6 shadow-lg duration-200",
38
+ "fixed left-[50%] top-[50%] z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-xl border bg-background p-5 shadow-[0_24px_48px_-12px_rgb(0_0_0/0.25)] duration-200 sm:p-6",
39
39
  "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
40
40
  className,
41
41
  )}
@@ -12,7 +12,7 @@ function Input({
12
12
  type={type}
13
13
  data-slot="input"
14
14
  className={cn(
15
- "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors",
15
+ "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm md:text-[13px] transition-colors",
16
16
  "placeholder:text-muted-foreground",
17
17
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
18
18
  "disabled:cursor-not-allowed disabled:opacity-50",
@@ -22,7 +22,7 @@ function Select({
22
22
  <select
23
23
  data-slot="select"
24
24
  className={cn(
25
- "flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-sm shadow-sm transition-colors",
25
+ "flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-base shadow-sm md:text-[13px] transition-colors",
26
26
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
27
27
  "disabled:cursor-not-allowed disabled:opacity-50",
28
28
  className,
@@ -10,7 +10,7 @@ function Textarea({
10
10
  <textarea
11
11
  data-slot="textarea"
12
12
  className={cn(
13
- "flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors",
13
+ "flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm md:text-[13px] transition-colors",
14
14
  "placeholder:text-muted-foreground",
15
15
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
16
16
  "disabled:cursor-not-allowed disabled:opacity-50",
@@ -35,6 +35,9 @@ export default mutation<
35
35
  if (invoice.status === "void") {
36
36
  throw ctx.error("INVALID_ARGS", "That invoice is void.");
37
37
  }
38
+ if (invoice.status === "draft") {
39
+ throw ctx.error("INVALID_ARGS", "Send the invoice before recording a payment.");
40
+ }
38
41
 
39
42
  const items = (await ctx.db.query("LineItem", {
40
43
  invoiceId: args.invoiceId,
@@ -141,6 +141,46 @@ export function displayStatus(
141
141
  return invoice.status;
142
142
  }
143
143
 
144
+ export interface InvoiceTab {
145
+ id: string;
146
+ label: string;
147
+ }
148
+
149
+ /** The invoice list's tabs, left to right. Each matches a `displayStatus`. */
150
+ export const INVOICE_TABS: InvoiceTab[] = [
151
+ { id: "all", label: "All" },
152
+ { id: "draft", label: "Draft" },
153
+ { id: "sent", label: "Sent" },
154
+ { id: "overdue", label: "Overdue" },
155
+ { id: "paid", label: "Paid" },
156
+ ];
157
+
158
+ /** Whether an invoice showing `status` (from `displayStatus`) belongs on a tab. */
159
+ export function inInvoiceTab(tabId: string, status: string): boolean {
160
+ return tabId === "all" || tabId === status;
161
+ }
162
+
163
+ /**
164
+ * How many invoices each tab lists. The list, the tab counts, and the sidebar
165
+ * badge all go through `displayStatus`, so an invoice that just went overdue
166
+ * moves between tabs and every count follows.
167
+ */
168
+ export function invoiceTabCounts(
169
+ invoices: Invoice[],
170
+ items: LineItem[],
171
+ payments: Payment[],
172
+ now: number = Date.now(),
173
+ ): Record<string, number> {
174
+ const counts: Record<string, number> = Object.fromEntries(INVOICE_TABS.map((t) => [t.id, 0]));
175
+ for (const invoice of invoices) {
176
+ const status = displayStatus(invoice, totals(invoice, items, payments).balanceCents, now);
177
+ for (const tab of INVOICE_TABS) {
178
+ if (inInvoiceTab(tab.id, status)) counts[tab.id] += 1;
179
+ }
180
+ }
181
+ return counts;
182
+ }
183
+
144
184
  export function daysOverdue(
145
185
  invoice: Invoice,
146
186
  now: number = Date.now(),
@@ -0,0 +1,9 @@
1
+ // The product name, in one place. The sidebar, the sign-in screen, the page
2
+ // title, and the drawer on phones all read it. Rename the product here; the
3
+ // mark is in components/brand-mark.tsx and the favicon is app/icon.svg.
4
+
5
+ export const BRAND = {
6
+ name: "Billfold",
7
+ /** One line on the sign-in screen: what the app holds and who sees it. */
8
+ description: "Your team's invoices, clients, and payments. Everyone works from the same books.",
9
+ } as const;
@@ -1,15 +1,16 @@
1
1
  // Client-side search powering the ⌘K palette.
2
2
  //
3
- // A small team's CRM fits in the synced replica, so search runs locally against
3
+ // A small team's workspace fits in the synced replica, so search runs locally against
4
4
  // data already in memory: results appear as you type, with no request per
5
5
  // keystroke. Swap in `db.useSearch` (Pylon's FTS) when a workspace outgrows
6
6
  // that — the component boundary doesn't change, only this file.
7
7
 
8
8
  export interface SearchItem {
9
9
  id: string;
10
- type: "deal" | "company" | "contact";
10
+ /** The record kind, such as "deal". The palette picks its icon from this. */
11
+ type: string;
11
12
  title: string;
12
- /** Second line: the company for a contact, the domain for a company. */
13
+ /** Shown on the right of the row: the company for a contact, say. */
13
14
  subtitle?: string;
14
15
  href: string;
15
16
  /** Extra text matched but not displayed — an email, say. */
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import {
3
+ INVOICE_TABS,
3
4
  daysOverdue,
5
+ inInvoiceTab,
6
+ invoiceTabCounts,
4
7
  displayStatus,
5
8
  isOverdue,
6
9
  isValidStatus,
@@ -281,3 +284,27 @@ describe("statuses", () => {
281
284
  expect(isValidStatus("void")).toBe(true);
282
285
  });
283
286
  });
287
+
288
+ describe("invoice tabs", () => {
289
+ const invoices = [
290
+ invoice({ id: "a", status: "draft" }),
291
+ invoice({ id: "b", status: "sent", dueDate: at(10) }),
292
+ invoice({ id: "c", status: "sent", dueDate: at(-5) }),
293
+ invoice({ id: "d", status: "paid" }),
294
+ invoice({ id: "e", status: "sent", dueDate: at(-5) }),
295
+ ];
296
+ const items = invoices.map((inv, i) => line({ id: `l${i}`, invoiceId: inv.id }));
297
+ // "e" is past due but fully paid, so it reads as paid, not overdue.
298
+ const payments: Payment[] = [{ id: "p1", invoiceId: "e", amountCents: 10_000 }];
299
+
300
+ test("each tab's count is exactly the rows it lists", () => {
301
+ const counts = invoiceTabCounts(invoices, items, payments, NOW);
302
+ for (const tab of INVOICE_TABS) {
303
+ const listed = invoices.filter((inv) =>
304
+ inInvoiceTab(tab.id, displayStatus(inv, totals(inv, items, payments).balanceCents, NOW)),
305
+ );
306
+ expect(counts[tab.id]).toBe(listed.length);
307
+ }
308
+ expect(counts).toEqual({ all: 5, draft: 1, sent: 1, overdue: 1, paid: 2 });
309
+ });
310
+ });
@@ -1,14 +1,15 @@
1
1
  // swift-tools-version:5.9
2
2
  import PackageDescription
3
3
 
4
+ // SwiftPM manifest for the app sources. Xcode builds use project.yml
5
+ // (xcodegen); this file lets `swift build` resolve the Pylon packages.
4
6
  let package = Package(
5
7
  name: "__APP_NAME_PASCAL__",
6
8
  platforms: [
7
- .iOS(.v16),
8
- .macOS(.v13),
9
+ .iOS(.v17),
9
10
  ],
10
11
  dependencies: [
11
- .package(url: "https://github.com/pylonsync/pylon.git", from: "0.3.0"),
12
+ .package(url: "https://github.com/pylonsync/pylon.git", from: "__PYLON_VERSION__"),
12
13
  ],
13
14
  targets: [
14
15
  .executableTarget(
@@ -0,0 +1,107 @@
1
+ import SwiftUI
2
+
3
+ /// Likes and comments on the user's posts, and new followers. Live.
4
+ struct ActivityView: View {
5
+ @EnvironmentObject private var social: SocialStore
6
+
7
+ var body: some View {
8
+ let items = social.activity
9
+ ScrollView {
10
+ if items.isEmpty {
11
+ ContentUnavailableView(
12
+ "No activity yet",
13
+ systemImage: "heart",
14
+ description: Text("When people like or comment on your posts, or follow you, it shows up here.")
15
+ )
16
+ .padding(.top, 80)
17
+ } else {
18
+ LazyVStack(alignment: .leading, spacing: 0, pinnedViews: []) {
19
+ ForEach(sections(items), id: \.title) { section in
20
+ Text(section.title)
21
+ .font(.headline)
22
+ .padding(.horizontal, 16)
23
+ .padding(.top, 18)
24
+ .padding(.bottom, 6)
25
+ ForEach(section.items) { item in
26
+ ActivityRow(item: item)
27
+ }
28
+ }
29
+ }
30
+ .animation(.snappy, value: items.map(\.id))
31
+ }
32
+ }
33
+ .navigationTitle("Activity")
34
+ .appRoutes()
35
+ }
36
+
37
+ private struct Section {
38
+ let title: String
39
+ let items: [ActivityItem]
40
+ }
41
+
42
+ private func sections(_ items: [ActivityItem]) -> [Section] {
43
+ let calendar = Calendar.current
44
+ let weekAgo = Date.now.addingTimeInterval(-7 * 86_400)
45
+ let today = items.filter { calendar.isDateInToday($0.date) }
46
+ let week = items.filter { !calendar.isDateInToday($0.date) && $0.date > weekAgo }
47
+ let earlier = items.filter { $0.date <= weekAgo }
48
+ return [
49
+ Section(title: "Today", items: today),
50
+ Section(title: "This week", items: week),
51
+ Section(title: "Earlier", items: earlier),
52
+ ].filter { !$0.items.isEmpty }
53
+ }
54
+ }
55
+
56
+ private struct ActivityRow: View {
57
+ @EnvironmentObject private var social: SocialStore
58
+ let item: ActivityItem
59
+
60
+ private var actor: Profile? { social.profile(item.actorId) }
61
+
62
+ var body: some View {
63
+ NavigationLink(value: item.postId.map { Route.post($0) } ?? Route.profile(item.actorId)) {
64
+ HStack(spacing: 12) {
65
+ StoryAvatar(profile: actor, size: 44, ringed: social.hasRecentPost(item.actorId))
66
+ sentence
67
+ .font(.subheadline)
68
+ .lineLimit(3)
69
+ .frame(maxWidth: .infinity, alignment: .leading)
70
+ trailing
71
+ }
72
+ .padding(.horizontal, 16)
73
+ .padding(.vertical, 8)
74
+ .contentShape(Rectangle())
75
+ }
76
+ .buttonStyle(.plain)
77
+ }
78
+
79
+ private var sentence: Text {
80
+ let name = Text(actor?.handle ?? "Someone").fontWeight(.semibold)
81
+ let time = Text(" " + Timestamp.short(item.date)).foregroundColor(.secondary)
82
+ switch item.kind {
83
+ case .like:
84
+ return name + Text(" liked your photo.") + time
85
+ case .comment:
86
+ return name + Text(" commented: \(item.text ?? "")") + time
87
+ case .follow:
88
+ return name + Text(" started following you.") + time
89
+ }
90
+ }
91
+
92
+ @ViewBuilder
93
+ private var trailing: some View {
94
+ if let postId = item.postId, let post = social.post(postId) {
95
+ RemoteImage(url: social.media.thumbnailURL(for: post.imageUrl, width: 128))
96
+ .frame(width: 44, height: 44)
97
+ .clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
98
+ } else if item.kind == .follow {
99
+ let following = social.isFollowing(item.actorId)
100
+ Button(following ? "Following" : "Follow back") {
101
+ social.toggleFollow(item.actorId)
102
+ }
103
+ .buttonStyle(ProfileButtonStyle(prominent: !following))
104
+ .frame(width: 112)
105
+ }
106
+ }
107
+ }