@pylonsync/create-pylon 0.19.1 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (675) hide show
  1. package/bin/create-pylon.js +30 -7
  2. package/package.json +1 -1
  3. package/templates/agency/.env.example +7 -0
  4. package/templates/agency/README.md +28 -3
  5. package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
  6. package/templates/agency/app/(marketing)/page.tsx +3 -3
  7. package/templates/agency/app/(marketing)/work/page.tsx +3 -3
  8. package/templates/agency/app/auth-form.tsx +4 -4
  9. package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
  10. package/templates/agency/app/dashboard/page.tsx +14 -16
  11. package/templates/agency/app/icon.svg +3 -0
  12. package/templates/agency/app/login/page.tsx +12 -14
  13. package/templates/agency/components/marketing.tsx +12 -5
  14. package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
  15. package/templates/agency/lib/agency.ts +22 -0
  16. package/templates/agency/lib/demo.ts +118 -0
  17. package/templates/agency/lib/site.config.ts +19 -13
  18. package/templates/agency/public/favicon.ico +0 -0
  19. package/templates/agency/tests/agency.test.ts +57 -0
  20. package/templates/ai-chat/.env.example +13 -27
  21. package/templates/ai-chat/README.md +53 -60
  22. package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
  23. package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
  24. package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
  25. package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
  26. package/templates/ai-chat/app/(chat)/page.tsx +7 -11
  27. package/templates/ai-chat/app/auth-form.tsx +45 -38
  28. package/templates/ai-chat/app/error.tsx +9 -12
  29. package/templates/ai-chat/app/globals.css +464 -102
  30. package/templates/ai-chat/app/layout.tsx +11 -19
  31. package/templates/ai-chat/app/login/page.tsx +39 -24
  32. package/templates/ai-chat/app.ts +118 -43
  33. package/templates/ai-chat/components/chat/composer.tsx +193 -0
  34. package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
  35. package/templates/ai-chat/components/chat/icons.tsx +198 -0
  36. package/templates/ai-chat/components/chat/markdown.tsx +164 -0
  37. package/templates/ai-chat/components/chat/message.tsx +183 -0
  38. package/templates/ai-chat/components/chat/setup.tsx +71 -0
  39. package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
  40. package/templates/ai-chat/functions/_beginTurn.ts +145 -0
  41. package/templates/ai-chat/functions/_finishTurn.ts +55 -0
  42. package/templates/ai-chat/functions/assistantStatus.ts +13 -0
  43. package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
  44. package/templates/ai-chat/functions/createConversation.ts +28 -0
  45. package/templates/ai-chat/functions/deleteConversation.ts +15 -20
  46. package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
  47. package/templates/ai-chat/functions/renameConversation.ts +19 -0
  48. package/templates/ai-chat/functions/respond.ts +88 -0
  49. package/templates/ai-chat/functions/seedExamples.ts +46 -0
  50. package/templates/ai-chat/functions/stopResponse.ts +30 -0
  51. package/templates/ai-chat/lib/brand-icon.ts +7 -0
  52. package/templates/ai-chat/lib/chat.ts +277 -0
  53. package/templates/ai-chat/lib/claim.ts +15 -0
  54. package/templates/ai-chat/lib/examples.ts +179 -0
  55. package/templates/ai-chat/lib/highlight.ts +107 -0
  56. package/templates/ai-chat/lib/markdown.ts +317 -0
  57. package/templates/ai-chat/lib/provider.ts +38 -0
  58. package/templates/ai-chat/lib/site.config.ts +55 -39
  59. package/templates/ai-chat/tests/chat.test.ts +174 -0
  60. package/templates/ai-chat/tests/components.test.tsx +57 -0
  61. package/templates/ai-chat/tests/markdown.test.ts +88 -0
  62. package/templates/ai-chat/tests/provider.test.ts +32 -0
  63. package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
  64. package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
  65. package/templates/backend/consumer/apps/api/app.ts +166 -0
  66. package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
  67. package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
  68. package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
  69. package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
  70. package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
  71. package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
  72. package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
  73. package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
  74. package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
  75. package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
  76. package/templates/backend/consumer/apps/api/package.json +8 -2
  77. package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
  78. package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
  79. package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
  80. package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
  81. package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
  82. package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
  83. package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
  84. package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
  85. package/templates/chat/README.md +43 -21
  86. package/templates/chat/app/chat-client.tsx +495 -85
  87. package/templates/chat/app/error.tsx +1 -1
  88. package/templates/chat/app/globals.css +170 -54
  89. package/templates/chat/app/layout.tsx +12 -9
  90. package/templates/chat/app/not-found.tsx +4 -2
  91. package/templates/chat/app/page.tsx +9 -19
  92. package/templates/chat/app.ts +96 -17
  93. package/templates/chat/components/chat/avatar.tsx +45 -0
  94. package/templates/chat/components/chat/composer.tsx +110 -0
  95. package/templates/chat/components/chat/message-list.tsx +285 -0
  96. package/templates/chat/components/chat/sidebar.tsx +190 -0
  97. package/templates/chat/functions/joinChat.ts +83 -0
  98. package/templates/chat/functions/renameMember.ts +25 -0
  99. package/templates/chat/lib/chat.ts +188 -0
  100. package/templates/chat/lib/seed.ts +78 -0
  101. package/templates/chat/tests/chat.test.ts +159 -0
  102. package/templates/chat/tests/composer.test.tsx +54 -0
  103. package/templates/consumer/README.md +3 -3
  104. package/templates/consumer/app/globals.css +10 -3
  105. package/templates/consumer/app/icon.svg +1 -0
  106. package/templates/consumer/components/social/avatar.tsx +25 -11
  107. package/templates/consumer/functions/seedFeed.ts +3 -3
  108. package/templates/consumer/lib/seed.ts +43 -85
  109. package/templates/consumer/public/favicon.ico +0 -0
  110. package/templates/consumer/public/images/posts/arch.jpg +0 -0
  111. package/templates/consumer/public/images/posts/coffee.jpg +0 -0
  112. package/templates/consumer/public/images/posts/dog.jpg +0 -0
  113. package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
  114. package/templates/consumer/public/images/posts/ramen.jpg +0 -0
  115. package/templates/consumer/public/images/posts/run.jpg +0 -0
  116. package/templates/consumer/public/images/posts/sunset.jpg +0 -0
  117. package/templates/consumer/tests/seed.test.ts +39 -0
  118. package/templates/creator/.env.example +7 -0
  119. package/templates/creator/README.md +24 -5
  120. package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
  121. package/templates/creator/app/auth-form.tsx +7 -7
  122. package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
  123. package/templates/creator/app/dashboard/page.tsx +18 -18
  124. package/templates/creator/app/icon.svg +2 -0
  125. package/templates/creator/app/login/page.tsx +14 -14
  126. package/templates/creator/app.ts +4 -4
  127. package/templates/creator/functions/seedDemo.ts +32 -0
  128. package/templates/creator/functions/subscribe.ts +4 -15
  129. package/templates/creator/lib/demo.ts +94 -0
  130. package/templates/creator/lib/site.config.ts +15 -10
  131. package/templates/creator/lib/stats.ts +35 -0
  132. package/templates/creator/public/favicon.ico +0 -0
  133. package/templates/creator/tests/counter.test.ts +92 -0
  134. package/templates/creator/tests/live-count.test.tsx +45 -0
  135. package/templates/crm/README.md +22 -1
  136. package/templates/crm/app/apple-icon.png +0 -0
  137. package/templates/crm/app/companies/companies-view.tsx +32 -14
  138. package/templates/crm/app/companies/page.tsx +2 -1
  139. package/templates/crm/app/contacts/contacts-view.tsx +34 -10
  140. package/templates/crm/app/contacts/page.tsx +2 -1
  141. package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
  142. package/templates/crm/app/deals/[id]/page.tsx +2 -1
  143. package/templates/crm/app/globals.css +29 -14
  144. package/templates/crm/app/icon.svg +1 -0
  145. package/templates/crm/app/layout.tsx +7 -5
  146. package/templates/crm/app/page.tsx +2 -1
  147. package/templates/crm/app/pipeline-view.tsx +5 -5
  148. package/templates/crm/app/workspace.tsx +29 -17
  149. package/templates/crm/app.ts +5 -5
  150. package/templates/crm/components/activity-timeline.tsx +14 -4
  151. package/templates/crm/components/app-shell.tsx +94 -0
  152. package/templates/crm/components/auth-form.tsx +35 -5
  153. package/templates/crm/components/avatar.tsx +5 -1
  154. package/templates/crm/components/board-skeleton.tsx +5 -5
  155. package/templates/crm/components/brand-mark.tsx +25 -0
  156. package/templates/crm/components/command-palette.tsx +17 -17
  157. package/templates/crm/components/data-table.tsx +97 -36
  158. package/templates/crm/components/deal-card.tsx +23 -27
  159. package/templates/crm/components/filter-tabs.tsx +101 -0
  160. package/templates/crm/components/metrics-bar.tsx +6 -6
  161. package/templates/crm/components/nav.tsx +15 -0
  162. package/templates/crm/components/page-header.tsx +43 -7
  163. package/templates/crm/components/pipeline-board.tsx +16 -10
  164. package/templates/crm/components/require-auth.tsx +24 -26
  165. package/templates/crm/components/sidebar.tsx +54 -49
  166. package/templates/crm/components/stage-badge.tsx +4 -2
  167. package/templates/crm/components/ui/button.tsx +5 -4
  168. package/templates/crm/components/ui/dialog.tsx +1 -1
  169. package/templates/crm/components/ui/input.tsx +1 -1
  170. package/templates/crm/components/ui/select.tsx +1 -1
  171. package/templates/crm/components/ui/textarea.tsx +1 -1
  172. package/templates/crm/functions/seedWorkspace.ts +32 -17
  173. package/templates/crm/lib/brand.ts +9 -0
  174. package/templates/crm/lib/demo-team.ts +51 -0
  175. package/templates/crm/lib/pipeline.ts +37 -0
  176. package/templates/crm/lib/search.ts +4 -3
  177. package/templates/crm/lib/seed.ts +53 -8
  178. package/templates/crm/public/favicon.ico +0 -0
  179. package/templates/crm/tests/components.test.tsx +5 -5
  180. package/templates/crm/tests/demo-team.test.ts +18 -0
  181. package/templates/crm/tests/pipeline.test.ts +59 -3
  182. package/templates/directory/.env.example +7 -0
  183. package/templates/directory/README.md +22 -3
  184. package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
  185. package/templates/directory/app/(marketing)/page.tsx +6 -4
  186. package/templates/directory/app/auth-form.tsx +4 -4
  187. package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
  188. package/templates/directory/app/dashboard/page.tsx +16 -14
  189. package/templates/directory/app/icon.svg +4 -0
  190. package/templates/directory/app/login/page.tsx +16 -15
  191. package/templates/directory/functions/seedDemo.ts +43 -0
  192. package/templates/directory/lib/demo.ts +145 -0
  193. package/templates/directory/lib/directory.ts +27 -0
  194. package/templates/directory/lib/site.config.ts +21 -16
  195. package/templates/directory/public/favicon.ico +0 -0
  196. package/templates/directory/tests/directory.test.ts +46 -0
  197. package/templates/expo/consumer/apps/expo/App.tsx +360 -288
  198. package/templates/expo/consumer/apps/expo/app.json +9 -1
  199. package/templates/expo/consumer/apps/expo/package.json +2 -1
  200. package/templates/helpdesk/README.md +21 -0
  201. package/templates/helpdesk/app/apple-icon.png +0 -0
  202. package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
  203. package/templates/helpdesk/app/customers/page.tsx +2 -1
  204. package/templates/helpdesk/app/globals.css +29 -14
  205. package/templates/helpdesk/app/icon.svg +1 -0
  206. package/templates/helpdesk/app/inbox-view.tsx +66 -47
  207. package/templates/helpdesk/app/layout.tsx +7 -5
  208. package/templates/helpdesk/app/page.tsx +4 -7
  209. package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
  210. package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
  211. package/templates/helpdesk/app/workspace.tsx +33 -22
  212. package/templates/helpdesk/app.ts +2 -2
  213. package/templates/helpdesk/components/app-shell.tsx +94 -0
  214. package/templates/helpdesk/components/auth-form.tsx +35 -5
  215. package/templates/helpdesk/components/avatar.tsx +5 -1
  216. package/templates/helpdesk/components/brand-mark.tsx +28 -0
  217. package/templates/helpdesk/components/command-palette.tsx +17 -17
  218. package/templates/helpdesk/components/data-table.tsx +97 -36
  219. package/templates/helpdesk/components/filter-tabs.tsx +101 -0
  220. package/templates/helpdesk/components/nav.tsx +14 -0
  221. package/templates/helpdesk/components/page-header.tsx +43 -7
  222. package/templates/helpdesk/components/priority-badge.tsx +49 -6
  223. package/templates/helpdesk/components/require-auth.tsx +24 -26
  224. package/templates/helpdesk/components/sidebar.tsx +54 -48
  225. package/templates/helpdesk/components/sla-indicator.tsx +5 -8
  226. package/templates/helpdesk/components/thread.tsx +7 -7
  227. package/templates/helpdesk/components/ticket-list.tsx +79 -19
  228. package/templates/helpdesk/components/ui/button.tsx +5 -4
  229. package/templates/helpdesk/components/ui/dialog.tsx +1 -1
  230. package/templates/helpdesk/components/ui/input.tsx +1 -1
  231. package/templates/helpdesk/components/ui/select.tsx +1 -1
  232. package/templates/helpdesk/components/ui/textarea.tsx +1 -1
  233. package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
  234. package/templates/helpdesk/lib/brand.ts +9 -0
  235. package/templates/helpdesk/lib/demo-team.ts +51 -0
  236. package/templates/helpdesk/lib/search.ts +4 -3
  237. package/templates/helpdesk/lib/seed.ts +43 -2
  238. package/templates/helpdesk/lib/tickets.ts +68 -20
  239. package/templates/helpdesk/public/favicon.ico +0 -0
  240. package/templates/helpdesk/tests/components.test.tsx +10 -0
  241. package/templates/helpdesk/tests/demo-team.test.ts +18 -0
  242. package/templates/helpdesk/tests/tickets.test.ts +70 -0
  243. package/templates/imessage/.env.example +49 -0
  244. package/templates/imessage/AGENTS.md +153 -0
  245. package/templates/imessage/CLAUDE.md +7 -0
  246. package/templates/imessage/README.md +187 -0
  247. package/templates/imessage/app/console.tsx +382 -0
  248. package/templates/imessage/app/contacts/page.tsx +13 -0
  249. package/templates/imessage/app/error.tsx +16 -0
  250. package/templates/imessage/app/gate.tsx +31 -0
  251. package/templates/imessage/app/globals.css +183 -0
  252. package/templates/imessage/app/layout.tsx +17 -0
  253. package/templates/imessage/app/login/login-form.tsx +115 -0
  254. package/templates/imessage/app/login/page.tsx +30 -0
  255. package/templates/imessage/app/not-found.tsx +13 -0
  256. package/templates/imessage/app/page.tsx +14 -0
  257. package/templates/imessage/app/robots.ts +6 -0
  258. package/templates/imessage/app/setup/page.tsx +13 -0
  259. package/templates/imessage/app/sign-out.tsx +20 -0
  260. package/templates/imessage/app.ts +299 -0
  261. package/templates/imessage/bunfig +4 -0
  262. package/templates/imessage/components/avatar.tsx +46 -0
  263. package/templates/imessage/components/contact-panel.tsx +263 -0
  264. package/templates/imessage/components/contacts-view.tsx +172 -0
  265. package/templates/imessage/components/conversation-list.tsx +129 -0
  266. package/templates/imessage/components/nav-rail.tsx +67 -0
  267. package/templates/imessage/components/setup-view.tsx +408 -0
  268. package/templates/imessage/components/thread.tsx +229 -0
  269. package/templates/imessage/components/ui/badge.tsx +37 -0
  270. package/templates/imessage/components/ui/button.tsx +56 -0
  271. package/templates/imessage/components/ui/card.tsx +90 -0
  272. package/templates/imessage/components/ui/dialog.tsx +110 -0
  273. package/templates/imessage/components/ui/input.tsx +30 -0
  274. package/templates/imessage/components/ui/label.tsx +21 -0
  275. package/templates/imessage/components/ui/select.tsx +39 -0
  276. package/templates/imessage/components/ui/table.tsx +124 -0
  277. package/templates/imessage/components/ui/textarea.tsx +24 -0
  278. package/templates/imessage/components.json +20 -0
  279. package/templates/imessage/functions/answerNow.ts +34 -0
  280. package/templates/imessage/functions/assistant.ts +96 -0
  281. package/templates/imessage/functions/beginTurn.ts +101 -0
  282. package/templates/imessage/functions/cancelReminder.ts +13 -0
  283. package/templates/imessage/functions/claimOutbound.ts +44 -0
  284. package/templates/imessage/functions/clearDemo.ts +18 -0
  285. package/templates/imessage/functions/deleteNote.ts +11 -0
  286. package/templates/imessage/functions/deliverOutbound.ts +55 -0
  287. package/templates/imessage/functions/finishTurn.ts +84 -0
  288. package/templates/imessage/functions/fireReminder.ts +42 -0
  289. package/templates/imessage/functions/ingestInbound.ts +22 -0
  290. package/templates/imessage/functions/ownerAccount.ts +13 -0
  291. package/templates/imessage/functions/processTurn.ts +74 -0
  292. package/templates/imessage/functions/relayApply.ts +102 -0
  293. package/templates/imessage/functions/relaySync.ts +63 -0
  294. package/templates/imessage/functions/releaseOutbound.ts +18 -0
  295. package/templates/imessage/functions/resolveTurn.ts +25 -0
  296. package/templates/imessage/functions/saveContact.ts +27 -0
  297. package/templates/imessage/functions/seedDemo.ts +70 -0
  298. package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
  299. package/templates/imessage/functions/sendblueWebhook.ts +49 -0
  300. package/templates/imessage/functions/setContactStatus.ts +17 -0
  301. package/templates/imessage/functions/settleOutbound.ts +35 -0
  302. package/templates/imessage/functions/setupStatus.ts +33 -0
  303. package/templates/imessage/functions/sweep.ts +58 -0
  304. package/templates/imessage/functions/toolCancelReminder.ts +17 -0
  305. package/templates/imessage/functions/toolForgetNote.ts +15 -0
  306. package/templates/imessage/functions/toolListNotes.ts +16 -0
  307. package/templates/imessage/functions/toolListReminders.ts +17 -0
  308. package/templates/imessage/functions/toolSaveNote.ts +22 -0
  309. package/templates/imessage/functions/toolSetReminder.ts +55 -0
  310. package/templates/imessage/functions/updateSettings.ts +48 -0
  311. package/templates/imessage/gitignore +11 -0
  312. package/templates/imessage/lib/agent-call.ts +251 -0
  313. package/templates/imessage/lib/assistant.ts +74 -0
  314. package/templates/imessage/lib/chunk.ts +135 -0
  315. package/templates/imessage/lib/demo.ts +68 -0
  316. package/templates/imessage/lib/format.ts +170 -0
  317. package/templates/imessage/lib/gate.ts +104 -0
  318. package/templates/imessage/lib/handles.ts +67 -0
  319. package/templates/imessage/lib/provider.ts +38 -0
  320. package/templates/imessage/lib/relay-protocol.ts +95 -0
  321. package/templates/imessage/lib/reminders.ts +41 -0
  322. package/templates/imessage/lib/secrets.ts +59 -0
  323. package/templates/imessage/lib/sendblue.ts +173 -0
  324. package/templates/imessage/lib/store.ts +407 -0
  325. package/templates/imessage/lib/transport.ts +135 -0
  326. package/templates/imessage/lib/utils.ts +10 -0
  327. package/templates/imessage/package.json +40 -0
  328. package/templates/imessage/relay/.env.example +14 -0
  329. package/templates/imessage/relay/applescript.ts +70 -0
  330. package/templates/imessage/relay/chatdb.ts +165 -0
  331. package/templates/imessage/relay/client.ts +41 -0
  332. package/templates/imessage/relay/main.ts +137 -0
  333. package/templates/imessage/relay/state.ts +35 -0
  334. package/templates/imessage/tests/agent-call.test.ts +112 -0
  335. package/templates/imessage/tests/applescript.test.ts +88 -0
  336. package/templates/imessage/tests/chatdb.test.ts +139 -0
  337. package/templates/imessage/tests/chunk.test.ts +61 -0
  338. package/templates/imessage/tests/fake-ctx.ts +88 -0
  339. package/templates/imessage/tests/ingest.test.ts +141 -0
  340. package/templates/imessage/tests/lib.test.ts +99 -0
  341. package/templates/imessage/tests/review-fixes.test.ts +138 -0
  342. package/templates/imessage/tests/secrets.test.ts +36 -0
  343. package/templates/imessage/tests/sendblue.test.ts +143 -0
  344. package/templates/imessage/tests/setup.ts +5 -0
  345. package/templates/imessage/tsconfig.json +18 -0
  346. package/templates/inventory/README.md +21 -0
  347. package/templates/inventory/app/apple-icon.png +0 -0
  348. package/templates/inventory/app/globals.css +29 -14
  349. package/templates/inventory/app/icon.svg +1 -0
  350. package/templates/inventory/app/layout.tsx +7 -5
  351. package/templates/inventory/app/movements/movements-view.tsx +32 -10
  352. package/templates/inventory/app/movements/page.tsx +2 -1
  353. package/templates/inventory/app/page.tsx +4 -6
  354. package/templates/inventory/app/products/[id]/page.tsx +2 -1
  355. package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
  356. package/templates/inventory/app/products-view.tsx +74 -33
  357. package/templates/inventory/app/workspace.tsx +34 -16
  358. package/templates/inventory/app.ts +10 -2
  359. package/templates/inventory/components/app-shell.tsx +94 -0
  360. package/templates/inventory/components/auth-form.tsx +35 -5
  361. package/templates/inventory/components/avatar.tsx +5 -1
  362. package/templates/inventory/components/brand-mark.tsx +25 -0
  363. package/templates/inventory/components/category-tile.tsx +49 -0
  364. package/templates/inventory/components/command-palette.tsx +17 -17
  365. package/templates/inventory/components/data-table.tsx +97 -36
  366. package/templates/inventory/components/filter-tabs.tsx +101 -0
  367. package/templates/inventory/components/nav.tsx +14 -0
  368. package/templates/inventory/components/page-header.tsx +43 -7
  369. package/templates/inventory/components/require-auth.tsx +24 -26
  370. package/templates/inventory/components/sidebar.tsx +54 -48
  371. package/templates/inventory/components/stock-level.tsx +30 -14
  372. package/templates/inventory/components/summary-bar.tsx +8 -15
  373. package/templates/inventory/components/ui/button.tsx +5 -4
  374. package/templates/inventory/components/ui/dialog.tsx +1 -1
  375. package/templates/inventory/components/ui/input.tsx +1 -1
  376. package/templates/inventory/components/ui/select.tsx +1 -1
  377. package/templates/inventory/components/ui/textarea.tsx +1 -1
  378. package/templates/inventory/functions/seedWorkspace.ts +18 -8
  379. package/templates/inventory/lib/brand.ts +9 -0
  380. package/templates/inventory/lib/demo-team.ts +51 -0
  381. package/templates/inventory/lib/search.ts +4 -3
  382. package/templates/inventory/lib/seed.ts +29 -2
  383. package/templates/inventory/lib/stock.ts +32 -0
  384. package/templates/inventory/public/favicon.ico +0 -0
  385. package/templates/inventory/tests/demo-team.test.ts +18 -0
  386. package/templates/inventory/tests/stock.test.ts +29 -0
  387. package/templates/invoices/README.md +16 -0
  388. package/templates/invoices/app/apple-icon.png +0 -0
  389. package/templates/invoices/app/clients/clients-view.tsx +18 -6
  390. package/templates/invoices/app/clients/page.tsx +2 -1
  391. package/templates/invoices/app/globals.css +29 -14
  392. package/templates/invoices/app/icon.svg +1 -0
  393. package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
  394. package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
  395. package/templates/invoices/app/invoices-view.tsx +81 -32
  396. package/templates/invoices/app/layout.tsx +7 -5
  397. package/templates/invoices/app/page.tsx +4 -6
  398. package/templates/invoices/app/workspace.tsx +26 -16
  399. package/templates/invoices/app.ts +2 -2
  400. package/templates/invoices/components/app-shell.tsx +94 -0
  401. package/templates/invoices/components/auth-form.tsx +35 -5
  402. package/templates/invoices/components/avatar.tsx +5 -1
  403. package/templates/invoices/components/brand-mark.tsx +26 -0
  404. package/templates/invoices/components/command-palette.tsx +17 -17
  405. package/templates/invoices/components/data-table.tsx +97 -36
  406. package/templates/invoices/components/filter-tabs.tsx +101 -0
  407. package/templates/invoices/components/line-items.tsx +19 -14
  408. package/templates/invoices/components/nav.tsx +14 -0
  409. package/templates/invoices/components/page-header.tsx +43 -7
  410. package/templates/invoices/components/require-auth.tsx +24 -26
  411. package/templates/invoices/components/sidebar.tsx +54 -48
  412. package/templates/invoices/components/status-badge.tsx +7 -7
  413. package/templates/invoices/components/summary-bar.tsx +5 -5
  414. package/templates/invoices/components/ui/button.tsx +5 -4
  415. package/templates/invoices/components/ui/dialog.tsx +1 -1
  416. package/templates/invoices/components/ui/input.tsx +1 -1
  417. package/templates/invoices/components/ui/select.tsx +1 -1
  418. package/templates/invoices/components/ui/textarea.tsx +1 -1
  419. package/templates/invoices/functions/recordPayment.ts +3 -0
  420. package/templates/invoices/lib/billing.ts +40 -0
  421. package/templates/invoices/lib/brand.ts +9 -0
  422. package/templates/invoices/lib/search.ts +4 -3
  423. package/templates/invoices/public/favicon.ico +0 -0
  424. package/templates/invoices/tests/billing.test.ts +27 -0
  425. package/templates/ios/consumer/apps/ios/Package.swift +4 -3
  426. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
  427. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
  428. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
  429. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
  430. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
  431. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
  432. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
  433. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
  434. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
  435. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
  436. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
  437. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
  438. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
  439. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
  440. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
  441. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
  442. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
  443. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
  444. package/templates/ios/consumer/apps/ios/project.yml +13 -9
  445. package/templates/local-service/.env.example +7 -0
  446. package/templates/local-service/README.md +26 -2
  447. package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
  448. package/templates/local-service/app/(marketing)/page.tsx +66 -42
  449. package/templates/local-service/app/auth-form.tsx +8 -8
  450. package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
  451. package/templates/local-service/app/dashboard/page.tsx +14 -14
  452. package/templates/local-service/app/icon.svg +2 -0
  453. package/templates/local-service/app/login/page.tsx +15 -15
  454. package/templates/local-service/functions/seedDemo.ts +49 -0
  455. package/templates/local-service/lib/demo.ts +135 -0
  456. package/templates/local-service/lib/hours.ts +44 -0
  457. package/templates/local-service/lib/site.config.ts +8 -3
  458. package/templates/local-service/lib/slots.ts +3 -0
  459. package/templates/local-service/public/favicon.ico +0 -0
  460. package/templates/local-service/tests/booking.test.ts +104 -0
  461. package/templates/marketplace/README.md +23 -15
  462. package/templates/marketplace/app/globals.css +172 -114
  463. package/templates/marketplace/app/layout.tsx +75 -30
  464. package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
  465. package/templates/marketplace/app/me/page.tsx +4 -4
  466. package/templates/marketplace/app/page.tsx +221 -298
  467. package/templates/marketplace/app/sell/page.tsx +8 -11
  468. package/templates/marketplace/app.ts +32 -7
  469. package/templates/marketplace/client/ActivityFeed.tsx +311 -0
  470. package/templates/marketplace/client/AuthNav.tsx +24 -16
  471. package/templates/marketplace/client/ListingCard.tsx +118 -0
  472. package/templates/marketplace/client/LiveGrid.tsx +92 -0
  473. package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
  474. package/templates/marketplace/client/LoginCard.tsx +114 -136
  475. package/templates/marketplace/client/MarketProvider.tsx +3 -5
  476. package/templates/marketplace/client/MyMarket.tsx +371 -185
  477. package/templates/marketplace/client/OfferPanel.tsx +421 -287
  478. package/templates/marketplace/client/SellForm.tsx +271 -192
  479. package/templates/marketplace/client/ThemeToggle.tsx +1 -0
  480. package/templates/marketplace/client/WatchButton.tsx +16 -16
  481. package/templates/marketplace/client/market.ts +61 -14
  482. package/templates/marketplace/functions/seedMarket.ts +143 -36
  483. package/templates/marketplace/functions/seedOffers.ts +101 -0
  484. package/templates/marketplace/lib/catalog.ts +72 -1
  485. package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
  486. package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
  487. package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
  488. package/templates/marketplace/public/images/listings/duffel.webp +0 -0
  489. package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
  490. package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
  491. package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
  492. package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
  493. package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
  494. package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
  495. package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
  496. package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
  497. package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
  498. package/templates/marketplace/public/images/listings/turntable.webp +0 -0
  499. package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
  500. package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
  501. package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
  502. package/templates/marketplace/public/images/listings/watch.webp +0 -0
  503. package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
  504. package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
  505. package/templates/marketplace/tests/example.test.ts +64 -2
  506. package/templates/marketplace/tests/listing-card.test.tsx +61 -0
  507. package/templates/projects/README.md +24 -3
  508. package/templates/projects/app/apple-icon.png +0 -0
  509. package/templates/projects/app/clients/clients-view.tsx +20 -6
  510. package/templates/projects/app/clients/page.tsx +2 -1
  511. package/templates/projects/app/globals.css +29 -14
  512. package/templates/projects/app/icon.svg +1 -0
  513. package/templates/projects/app/layout.tsx +7 -5
  514. package/templates/projects/app/page.tsx +3 -5
  515. package/templates/projects/app/projects/[id]/page.tsx +2 -1
  516. package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
  517. package/templates/projects/app/projects-view.tsx +105 -52
  518. package/templates/projects/app/workspace.tsx +41 -20
  519. package/templates/projects/app.ts +2 -2
  520. package/templates/projects/components/app-shell.tsx +94 -0
  521. package/templates/projects/components/auth-form.tsx +35 -5
  522. package/templates/projects/components/avatar.tsx +5 -1
  523. package/templates/projects/components/board-skeleton.tsx +3 -3
  524. package/templates/projects/components/brand-mark.tsx +25 -0
  525. package/templates/projects/components/command-palette.tsx +17 -17
  526. package/templates/projects/components/data-table.tsx +97 -36
  527. package/templates/projects/components/filter-tabs.tsx +101 -0
  528. package/templates/projects/components/meter.tsx +48 -0
  529. package/templates/projects/components/nav.tsx +14 -0
  530. package/templates/projects/components/page-header.tsx +43 -7
  531. package/templates/projects/components/require-auth.tsx +24 -26
  532. package/templates/projects/components/sidebar.tsx +54 -48
  533. package/templates/projects/components/summary-bar.tsx +49 -0
  534. package/templates/projects/components/task-board.tsx +10 -9
  535. package/templates/projects/components/time-dialog.tsx +29 -5
  536. package/templates/projects/components/ui/button.tsx +5 -4
  537. package/templates/projects/components/ui/dialog.tsx +1 -1
  538. package/templates/projects/components/ui/input.tsx +1 -1
  539. package/templates/projects/components/ui/select.tsx +1 -1
  540. package/templates/projects/components/ui/textarea.tsx +1 -1
  541. package/templates/projects/functions/seedWorkspace.ts +26 -12
  542. package/templates/projects/lib/brand.ts +9 -0
  543. package/templates/projects/lib/demo-team.ts +51 -0
  544. package/templates/projects/lib/search.ts +4 -3
  545. package/templates/projects/lib/seed.ts +35 -1
  546. package/templates/projects/lib/work.ts +63 -0
  547. package/templates/projects/public/favicon.ico +0 -0
  548. package/templates/projects/tests/demo-team.test.ts +18 -0
  549. package/templates/projects/tests/work.test.ts +65 -0
  550. package/templates/restaurant/.env.example +7 -0
  551. package/templates/restaurant/README.md +29 -3
  552. package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
  553. package/templates/restaurant/app/(marketing)/page.tsx +47 -23
  554. package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
  555. package/templates/restaurant/app/auth-form.tsx +8 -8
  556. package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
  557. package/templates/restaurant/app/dashboard/page.tsx +14 -14
  558. package/templates/restaurant/app/icon.svg +4 -0
  559. package/templates/restaurant/app/login/page.tsx +15 -15
  560. package/templates/restaurant/functions/seedDemo.ts +44 -0
  561. package/templates/restaurant/lib/demo.ts +144 -0
  562. package/templates/restaurant/lib/hours.ts +45 -0
  563. package/templates/restaurant/lib/site.config.ts +3 -3
  564. package/templates/restaurant/public/favicon.ico +0 -0
  565. package/templates/restaurant/tests/reservations.test.ts +67 -0
  566. package/templates/saas/README.md +54 -35
  567. package/templates/saas/app/(auth)/auth-form.tsx +25 -23
  568. package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
  569. package/templates/saas/app/(auth)/layout.tsx +27 -33
  570. package/templates/saas/app/(auth)/login/page.tsx +1 -1
  571. package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
  572. package/templates/saas/app/(auth)/signup/page.tsx +1 -1
  573. package/templates/saas/app/(marketing)/layout.tsx +127 -253
  574. package/templates/saas/app/(marketing)/page.tsx +133 -372
  575. package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
  576. package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
  577. package/templates/saas/app/dashboard/board-client.tsx +416 -0
  578. package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
  579. package/templates/saas/app/dashboard/layout.tsx +27 -13
  580. package/templates/saas/app/dashboard/overview-client.tsx +332 -0
  581. package/templates/saas/app/dashboard/page.tsx +29 -35
  582. package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
  583. package/templates/saas/app/dashboard/projects/page.tsx +11 -7
  584. package/templates/saas/app/dashboard/projects-client.tsx +398 -0
  585. package/templates/saas/app/globals.css +5 -4
  586. package/templates/saas/app/icon.svg +1 -0
  587. package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
  588. package/templates/saas/app/onboarding/page.tsx +6 -2
  589. package/templates/saas/app.ts +68 -13
  590. package/templates/saas/components/dashboard-shell.tsx +212 -132
  591. package/templates/saas/components/marketing.tsx +137 -274
  592. package/templates/saas/components/pricing-table.tsx +12 -11
  593. package/templates/saas/components/setup-checklist.tsx +31 -29
  594. package/templates/saas/components/task-ui.tsx +199 -0
  595. package/templates/saas/functions/createProject.ts +7 -13
  596. package/templates/saas/functions/createTask.ts +67 -0
  597. package/templates/saas/functions/deleteProject.ts +17 -0
  598. package/templates/saas/functions/deleteTask.ts +14 -0
  599. package/templates/saas/functions/seedWorkspace.ts +46 -0
  600. package/templates/saas/functions/setProjectStatus.ts +41 -0
  601. package/templates/saas/functions/updateProfile.ts +13 -0
  602. package/templates/saas/functions/updateProject.ts +4 -5
  603. package/templates/saas/functions/updateTask.ts +88 -0
  604. package/templates/saas/lib/members.ts +53 -0
  605. package/templates/saas/lib/names.ts +53 -0
  606. package/templates/saas/lib/plans.ts +11 -0
  607. package/templates/saas/lib/sample-workspace.ts +117 -0
  608. package/templates/saas/lib/site.config.ts +200 -463
  609. package/templates/saas/lib/tasks.ts +101 -0
  610. package/templates/saas/lib/use-live.ts +25 -0
  611. package/templates/saas/public/favicon.ico +0 -0
  612. package/templates/saas/public/screenshots/board.png +0 -0
  613. package/templates/saas/public/screenshots/members.png +0 -0
  614. package/templates/saas/public/screenshots/overview.png +0 -0
  615. package/templates/saas/public/screenshots/projects.png +0 -0
  616. package/templates/saas/scripts/capture-screenshots.mjs +156 -0
  617. package/templates/saas/tests/names.test.ts +35 -0
  618. package/templates/saas/tests/plans.test.ts +12 -0
  619. package/templates/saas/tests/sample-workspace.test.ts +46 -0
  620. package/templates/saas/tests/tasks.test.ts +81 -0
  621. package/templates/shop/README.md +25 -13
  622. package/templates/shop/app/(marketing)/layout.tsx +94 -67
  623. package/templates/shop/app/(marketing)/not-found.tsx +13 -12
  624. package/templates/shop/app/(marketing)/page.tsx +120 -59
  625. package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
  626. package/templates/shop/app/(marketing)/success/page.tsx +75 -22
  627. package/templates/shop/app/globals.css +94 -1
  628. package/templates/shop/app/layout.tsx +4 -4
  629. package/templates/shop/app/login/page.tsx +4 -4
  630. package/templates/shop/app.ts +18 -6
  631. package/templates/shop/components/cart-button.tsx +33 -0
  632. package/templates/shop/components/marketing.tsx +17 -87
  633. package/templates/shop/components/shelf-count.tsx +36 -0
  634. package/templates/shop/functions/checkout.ts +17 -1
  635. package/templates/shop/lib/cart-store.ts +57 -0
  636. package/templates/shop/lib/shelf-store.ts +35 -0
  637. package/templates/shop/lib/shop.ts +27 -0
  638. package/templates/shop/lib/site.config.ts +108 -98
  639. package/templates/shop/public/images/hero.jpg +0 -0
  640. package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
  641. package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
  642. package/templates/shop/public/images/products/linen.jpg +0 -0
  643. package/templates/shop/public/images/products/sea-salt.jpg +0 -0
  644. package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
  645. package/templates/shop/tests/shop.test.ts +64 -0
  646. package/templates/waitlist/.env.example +7 -0
  647. package/templates/waitlist/README.md +29 -13
  648. package/templates/waitlist/app/(marketing)/page.tsx +9 -7
  649. package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
  650. package/templates/waitlist/app/auth-form.tsx +8 -8
  651. package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
  652. package/templates/waitlist/app/dashboard/page.tsx +17 -16
  653. package/templates/waitlist/app/icon.svg +2 -0
  654. package/templates/waitlist/app/login/page.tsx +14 -14
  655. package/templates/waitlist/app.ts +6 -6
  656. package/templates/waitlist/functions/joinWaitlist.ts +6 -18
  657. package/templates/waitlist/functions/seedDemo.ts +32 -0
  658. package/templates/waitlist/lib/demo.ts +94 -0
  659. package/templates/waitlist/lib/launch.ts +19 -0
  660. package/templates/waitlist/lib/site.config.ts +14 -9
  661. package/templates/waitlist/lib/stats.ts +35 -0
  662. package/templates/waitlist/public/favicon.ico +0 -0
  663. package/templates/waitlist/tests/counter.test.ts +107 -0
  664. package/templates/waitlist/tests/live-count.test.tsx +48 -0
  665. package/templates/ai-chat/tests/example.test.ts +0 -12
  666. package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
  667. package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
  668. package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
  669. package/templates/backend/consumer/apps/api/schema.ts +0 -143
  670. package/templates/chat/functions/_keep.ts +0 -13
  671. package/templates/chat/tests/example.test.ts +0 -12
  672. package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
  673. package/templates/marketplace/client/LiveTicker.tsx +0 -116
  674. package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
  675. package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
@@ -3,6 +3,9 @@
3
3
  import React, { useEffect, useMemo, useState } from "react";
4
4
  import { db, callFn } from "@pylonsync/react";
5
5
  import { useAuth } from "@pylonsync/client";
6
+ import { Check, LogOut, MapPin, X } from "lucide-react";
7
+ import { siteConfig } from "@/lib/site.config";
8
+ import { localDateKey } from "@/lib/slots";
6
9
  import type { ReservationRow, OwnerReservationsResult } from "@/lib/reservation";
7
10
 
8
11
  // The owner's live reservations dashboard. Liveness rides the SAME public
@@ -18,6 +21,7 @@ export function ReservationsDashboard({ userEmail }: { userEmail: string }) {
18
21
  const [denied, setDenied] = useState(false);
19
22
  const [error, setError] = useState<string | null>(null);
20
23
  const [busyId, setBusyId] = useState<string | null>(null);
24
+ const [day, setDay] = useState<string | null>(null);
21
25
 
22
26
  async function load() {
23
27
  try {
@@ -33,6 +37,12 @@ export function ReservationsDashboard({ userEmail }: { userEmail: string }) {
33
37
  }
34
38
  }
35
39
 
40
+ // In `pylon dev` an empty book gets demo reservations (functions/seedDemo.ts).
41
+ // On a deploy the call returns without writing.
42
+ useEffect(() => {
43
+ void callFn("seedDemo", {}).catch(() => {});
44
+ }, []);
45
+
36
46
  useEffect(() => {
37
47
  void load();
38
48
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -48,178 +58,297 @@ export function ReservationsDashboard({ userEmail }: { userEmail: string }) {
48
58
  }
49
59
  }
50
60
 
61
+ const nowMs = Date.now();
62
+ // The next 14 open days, each with its reservations.
63
+ const days = useMemo(() => {
64
+ const cfg = siteConfig.reservations;
65
+ const byDay = new Map<string, ReservationRow[]>();
66
+ for (const r of reservations ?? []) {
67
+ const key = dayKey(r.startsAt);
68
+ const list = byDay.get(key) ?? [];
69
+ list.push(r);
70
+ byDay.set(key, list);
71
+ }
72
+ const out: { key: string; rows: ReservationRow[] }[] = [];
73
+ for (let i = 0; i < 21 && out.length < 14; i++) {
74
+ const key = localDateKey(i, nowMs);
75
+ const [y, m, d] = key.split("-").map(Number);
76
+ if (!cfg.hours[new Date(y, m - 1, d).getDay()]) continue;
77
+ const rows = (byDay.get(key) ?? []).sort((a, b) => (a.startsAt < b.startsAt ? -1 : 1));
78
+ out.push({ key, rows });
79
+ }
80
+ return out;
81
+ // eslint-disable-next-line react-hooks/exhaustive-deps
82
+ }, [reservations]);
83
+
51
84
  if (denied) return <OwnerOnly email={userEmail} />;
52
85
  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>;
86
+ return <div className="rounded-lg border border-brand/40 bg-brand/10 px-5 py-4 text-sm text-[var(--cream)]">{error}</div>;
54
87
  }
55
88
  if (!reservations) return <Skeleton />;
56
89
 
57
- const nowMs = Date.now();
58
90
  const active = reservations.filter((r) => r.status !== "cancelled");
59
91
  const upcoming = active.filter((r) => Date.parse(r.startsAt) >= nowMs - 3_600_000);
60
- const todayKey = new Date().toLocaleDateString();
61
- const todayCount = upcoming.filter((r) => new Date(r.startsAt).toLocaleDateString() === todayKey).length;
62
- const covers = upcoming.reduce((sum, r) => sum + (r.partySize || 0), 0);
92
+ const todayKey = localDateKey(0, nowMs);
93
+ // Every active table today, seated or not, so the stat matches the day chip.
94
+ const tonight = active.filter((r) => dayKey(r.startsAt) === todayKey);
95
+ const weekEnd = nowMs + 7 * 86_400_000;
96
+ const weekCovers = upcoming
97
+ .filter((r) => Date.parse(r.startsAt) < weekEnd)
98
+ .reduce((sum, r) => sum + (r.partySize || 0), 0);
63
99
  const pendingCount = upcoming.filter((r) => r.status === "pending").length;
64
100
 
101
+ const selected = days.find((d) => d.key === day) ?? days[0];
102
+
65
103
  return (
66
- <div className="space-y-8">
67
- <div>
68
- <h1 className="text-xl font-semibold tracking-tight">Reservations</h1>
69
- <p className="mt-1 text-sm text-zinc-500">
70
- Live — new reservations appear the moment they happen; cancelling frees the table on the
71
- site instantly.
104
+ <div className="space-y-10">
105
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
106
+ <div>
107
+ <h1 className="font-display text-[2.75rem] font-medium leading-none tracking-[-0.01em] sm:text-[3.5rem]">
108
+ Reservations
109
+ </h1>
110
+ <p className="mt-3 max-w-lg text-[15px] leading-relaxed text-[var(--cream-2)]">
111
+ New reservations appear here as guests book. Cancelling one frees the table on the
112
+ site right away.
113
+ </p>
114
+ </div>
115
+ <p className="flex items-center gap-2 text-[13px] text-[var(--cream-2)]">
116
+ <span className="relative flex size-2">
117
+ <span className="absolute inline-flex size-2 animate-ping rounded-full bg-brand/60" />
118
+ <span className="relative inline-flex size-2 rounded-full bg-brand" />
119
+ </span>
120
+ Live
72
121
  </p>
73
122
  </div>
74
123
 
75
- <div className="grid gap-4 sm:grid-cols-4">
76
- <Stat label="Upcoming" value={upcoming.length} />
77
- <Stat label="Today" value={todayCount} />
78
- <Stat label="Covers" value={covers} />
79
- <Stat label="Awaiting confirm" value={pendingCount} />
80
- </div>
124
+ <dl className="grid grid-cols-2 gap-px overflow-hidden rounded-[6px] border border-white/10 bg-white/10 sm:grid-cols-4">
125
+ <Stat label="Tonight" value={`${tonight.length}`} note={`${tonight.reduce((n, r) => n + r.partySize, 0)} covers`} />
126
+ <Stat label="Covers, next 7 days" value={weekCovers.toLocaleString("en-US")} />
127
+ <Stat label="Upcoming" value={upcoming.length.toLocaleString("en-US")} />
128
+ <Stat label="To confirm" value={`${pendingCount}`} accent={pendingCount > 0} />
129
+ </dl>
130
+
131
+ <section>
132
+ <div className="flex gap-2 overflow-x-auto pb-2">
133
+ {days.map((d) => {
134
+ const isOn = d.key === selected?.key;
135
+ const covers = d.rows.filter((r) => r.status !== "cancelled").reduce((n, r) => n + r.partySize, 0);
136
+ return (
137
+ <button
138
+ key={d.key}
139
+ type="button"
140
+ onClick={() => setDay(d.key)}
141
+ aria-pressed={isOn}
142
+ className={
143
+ "flex shrink-0 flex-col items-start whitespace-nowrap rounded-[6px] border px-3.5 py-2.5 text-left transition-colors " +
144
+ (isOn
145
+ ? "border-brand bg-brand text-white"
146
+ : "border-white/10 bg-[var(--ink-2)] text-[var(--cream)] hover:border-white/30")
147
+ }
148
+ >
149
+ <span className={"text-[12px] " + (isOn ? "text-white/80" : "text-[var(--cream-2)]")}>
150
+ {d.key === todayKey ? "Tonight" : weekday(d.key)}
151
+ </span>
152
+ <span className="font-display text-[20px] leading-tight">{monthDay(d.key)}</span>
153
+ <span className={"mt-1 text-[12px] tabular-nums " + (isOn ? "text-white/80" : "text-[var(--cream-2)]")}>
154
+ {covers} covers
155
+ </span>
156
+ </button>
157
+ );
158
+ })}
159
+ </div>
81
160
 
82
- <UpcomingList reservations={upcoming} busyId={busyId} onAct={act} />
161
+ {selected ? (
162
+ <ServiceSheet rows={selected.rows} busyId={busyId} onAct={act} />
163
+ ) : null}
164
+ </section>
165
+
166
+ {!siteConfig.location.mapEmbedUrl ? <MapHint /> : null}
83
167
  </div>
84
168
  );
85
169
  }
86
170
 
87
- function Stat({ label, value }: { label: string; value: number }) {
171
+ function dayKey(iso: string): string {
172
+ const d = new Date(iso);
173
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
174
+ }
175
+ function keyDate(key: string): Date {
176
+ const [y, m, d] = key.split("-").map(Number);
177
+ return new Date(y, m - 1, d);
178
+ }
179
+ function weekday(key: string) {
180
+ return keyDate(key).toLocaleDateString("en-US", { weekday: "short" });
181
+ }
182
+ function monthDay(key: string) {
183
+ return keyDate(key).toLocaleDateString("en-US", { month: "short", day: "numeric" });
184
+ }
185
+ function timeOf(iso: string) {
186
+ return new Date(iso).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
187
+ }
188
+
189
+ function Stat({ label, value, note, accent }: { label: string; value: string; note?: string; accent?: boolean }) {
88
190
  return (
89
- <div className="rounded-xl border border-zinc-200 bg-white p-4">
90
- <div className="text-[11px] font-medium uppercase tracking-wide text-zinc-400">{label}</div>
91
- <div className="mt-1 text-2xl font-semibold tabular-nums text-zinc-900">{value}</div>
191
+ <div className="bg-[var(--ink)] px-4 py-4 sm:px-5">
192
+ <dt className="text-[12px] text-[var(--cream-2)]">{label}</dt>
193
+ <dd className="mt-1.5 flex items-baseline gap-2">
194
+ <span className={"font-display text-[2.25rem] leading-none tabular-nums " + (accent ? "text-brand" : "")}>
195
+ {value}
196
+ </span>
197
+ {note ? <span className="text-[12px] text-[var(--cream-2)]">{note}</span> : null}
198
+ </dd>
92
199
  </div>
93
200
  );
94
201
  }
95
202
 
96
- function UpcomingList({
97
- reservations,
203
+ // One day's book, grouped by seating time.
204
+ function ServiceSheet({
205
+ rows,
98
206
  busyId,
99
207
  onAct,
100
208
  }: {
101
- reservations: ReservationRow[];
209
+ rows: ReservationRow[];
102
210
  busyId: string | null;
103
211
  onAct: (id: string, fn: "confirmReservation" | "cancelReservation") => void;
104
212
  }) {
105
- const groups = useMemo(() => {
213
+ const seatings = useMemo(() => {
106
214
  const m = new Map<string, ReservationRow[]>();
107
- for (const r of reservations) {
108
- const key = new Date(r.startsAt).toLocaleDateString(undefined, {
109
- weekday: "long",
110
- month: "short",
111
- day: "numeric",
112
- });
113
- const arr = m.get(key) ?? [];
114
- arr.push(r);
115
- m.set(key, arr);
215
+ for (const r of rows) {
216
+ const list = m.get(r.startsAt) ?? [];
217
+ list.push(r);
218
+ m.set(r.startsAt, list);
116
219
  }
117
220
  return Array.from(m.entries());
118
- }, [reservations]);
221
+ }, [rows]);
119
222
 
120
- if (reservations.length === 0) {
223
+ if (rows.length === 0) {
121
224
  return (
122
- <div className="rounded-xl border border-dashed border-zinc-300 p-10 text-center text-sm text-zinc-500">
123
- No upcoming reservations yet. Share your site — new reservations land here live.
225
+ <div className="mt-4 rounded-[6px] border border-dashed border-white/15 p-10 text-center text-[15px] text-[var(--cream-2)]">
226
+ No reservations for this day yet.
124
227
  </div>
125
228
  );
126
229
  }
127
230
 
128
231
  return (
129
- <div className="space-y-6">
130
- {groups.map(([day, items]) => (
131
- <div key={day}>
132
- <h2 className="mb-2 text-[13px] font-semibold text-zinc-900">{day}</h2>
133
- <div className="overflow-hidden rounded-xl border border-zinc-200">
134
- {items.map((r, i) => (
135
- <Item key={r.id} r={r} busy={busyId === r.id} onAct={onAct} last={i === items.length - 1} />
136
- ))}
232
+ <div className="mt-4 overflow-hidden rounded-[6px] border border-white/10">
233
+ {seatings.map(([startsAt, list]) => (
234
+ <div key={startsAt} className="grid border-b border-white/10 last:border-b-0 sm:grid-cols-[7rem_1fr]">
235
+ <div className="border-white/10 bg-[var(--ink-2)] px-4 py-3 sm:border-r">
236
+ <div className="font-display text-[20px] leading-none">{timeOf(startsAt)}</div>
237
+ <div className="mt-1 text-[12px] text-[var(--cream-2)]">
238
+ {list.filter((r) => r.status !== "cancelled").length}/{siteConfig.reservations.tablesPerSlot} tables
239
+ </div>
137
240
  </div>
241
+ <ul className="divide-y divide-white/10">
242
+ {list.map((r) => (
243
+ <Row key={r.id} r={r} busy={busyId === r.id} onAct={onAct} />
244
+ ))}
245
+ </ul>
138
246
  </div>
139
247
  ))}
140
248
  </div>
141
249
  );
142
250
  }
143
251
 
144
- function Item({
252
+ function Row({
145
253
  r,
146
254
  busy,
147
255
  onAct,
148
- last,
149
256
  }: {
150
257
  r: ReservationRow;
151
258
  busy: boolean;
152
259
  onAct: (id: string, fn: "confirmReservation" | "cancelReservation") => void;
153
- last: boolean;
154
260
  }) {
261
+ const cancelled = r.status === "cancelled";
262
+ const past = Date.parse(r.startsAt) < Date.now() - 3_600_000;
155
263
  return (
156
- <div className={"flex flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3 " + (last ? "" : "border-b border-zinc-100")}>
157
- <div className="w-20 shrink-0 text-[14px] font-semibold tabular-nums text-zinc-900">
158
- {new Date(r.startsAt).toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" })}
159
- </div>
264
+ <li className={"flex flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3 " + (cancelled ? "opacity-50" : "")}>
265
+ <span
266
+ aria-label={`Party of ${r.partySize}`}
267
+ className="flex size-9 shrink-0 items-center justify-center rounded-full border border-white/15 font-display text-[17px] tabular-nums"
268
+ >
269
+ {r.partySize}
270
+ </span>
160
271
  <div className="min-w-0 flex-1">
161
- <div className="flex items-center gap-2">
162
- <span className="text-[14px] font-medium text-zinc-900">{r.customerName}</span>
163
- <span className="rounded-full bg-zinc-100 px-2 py-0.5 text-[11px] font-medium text-zinc-600">
164
- party of {r.partySize}
165
- </span>
166
- <StatusBadge status={r.status} />
272
+ <div className="flex flex-wrap items-center gap-2">
273
+ <span className={"text-[15px] font-medium " + (cancelled ? "line-through" : "")}>{r.customerName}</span>
274
+ <StatusTag status={r.status} />
167
275
  </div>
168
- <div className="truncate text-[12.5px] text-zinc-500">
169
- {r.customerEmail}
170
- {r.customerPhone ? ` · ${r.customerPhone}` : ""}
171
- {r.notes ? ` · “${r.notes}”` : ""}
276
+ <div className="mt-0.5 truncate text-[13px] text-[var(--cream-2)]">
277
+ {r.notes ? <span className="italic text-[var(--cream)]">{r.notes} · </span> : null}
278
+ {r.customerPhone ?? r.customerEmail}
172
279
  </div>
173
280
  </div>
174
- <div className="flex items-center gap-2">
175
- {r.status === "pending" ? (
281
+ {!cancelled && !past ? (
282
+ <div className="flex items-center gap-2">
283
+ {r.status === "pending" ? (
284
+ <button
285
+ type="button"
286
+ disabled={busy}
287
+ onClick={() => onAct(r.id, "confirmReservation")}
288
+ className="inline-flex h-8 items-center gap-1.5 rounded-full bg-brand px-3.5 text-[13px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-50"
289
+ >
290
+ <Check aria-hidden className="size-3.5" />
291
+ Confirm
292
+ </button>
293
+ ) : null}
176
294
  <button
177
295
  type="button"
178
296
  disabled={busy}
179
- onClick={() => onAct(r.id, "confirmReservation")}
180
- className="rounded-md bg-zinc-900 px-3 py-1.5 text-[12.5px] font-medium text-white transition-colors hover:bg-zinc-700 disabled:opacity-50"
297
+ onClick={() => onAct(r.id, "cancelReservation")}
298
+ className="inline-flex h-8 items-center gap-1.5 rounded-full border border-white/20 px-3.5 text-[13px] font-medium text-[var(--cream-2)] transition-colors hover:border-white/40 hover:text-[var(--cream)] disabled:opacity-50"
181
299
  >
182
- {busy ? "…" : "Confirm"}
300
+ <X aria-hidden className="size-3.5" />
301
+ Cancel
183
302
  </button>
184
- ) : null}
185
- <button
186
- type="button"
187
- disabled={busy}
188
- onClick={() => onAct(r.id, "cancelReservation")}
189
- 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"
190
- >
191
- Cancel
192
- </button>
193
- </div>
194
- </div>
303
+ </div>
304
+ ) : null}
305
+ </li>
195
306
  );
196
307
  }
197
308
 
198
- function StatusBadge({ status }: { status: string }) {
199
- const tone =
200
- status === "confirmed"
201
- ? "bg-green-50 text-green-700"
202
- : status === "cancelled"
203
- ? "bg-zinc-100 text-zinc-500"
204
- : "bg-amber-50 text-amber-700";
205
- return <span className={"rounded-full px-2 py-0.5 text-[10px] font-medium capitalize " + tone}>{status}</span>;
309
+ function StatusTag({ status }: { status: string }) {
310
+ if (status === "confirmed") return null;
311
+ const label = status === "pending" ? "To confirm" : "Cancelled";
312
+ const tone = status === "pending" ? "bg-brand/20 text-[#f3b58c]" : "bg-white/10 text-[var(--cream-2)]";
313
+ return <span className={"rounded-full px-2 py-0.5 text-[11px] font-medium " + tone}>{label}</span>;
206
314
  }
207
315
 
316
+ // Setup notes for the owner live here, not on the public site.
317
+ function MapHint() {
318
+ return (
319
+ <aside className="flex gap-3 rounded-[6px] border border-white/10 bg-[var(--ink-2)] p-4 text-[13.5px] leading-relaxed text-[var(--cream-2)]">
320
+ <MapPin aria-hidden className="mt-0.5 size-4 shrink-0 text-[var(--cream)]" />
321
+ <p>
322
+ Your site shows the week&apos;s hours next to the address. To show a map there instead,
323
+ paste a Google Maps embed URL into{" "}
324
+ <code className="rounded bg-black/30 px-1 text-[12.5px] text-[var(--cream)]">location.mapEmbedUrl</code> in{" "}
325
+ <code className="rounded bg-black/30 px-1 text-[12.5px] text-[var(--cream)]">lib/site.config.ts</code>.
326
+ </p>
327
+ </aside>
328
+ );
329
+ }
330
+
331
+ /* --------------------------- owner-only gate -------------------------- */
332
+
208
333
  function OwnerOnly({ email }: { email: string }) {
209
334
  return (
210
- <div className="rounded-xl border border-dashed border-zinc-300 px-6 py-12 text-center">
211
- <h1 className="text-lg font-semibold">This dashboard is owner-only</h1>
212
- <p className="mx-auto mt-2 max-w-md text-sm text-zinc-500">
213
- You&apos;re signed in as <span className="font-medium text-zinc-700">{email || "this account"}</span>.
214
- Only the owner can see reservations. Set{" "}
215
- <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-[12px]">PYLON_OWNER_EMAIL={email || "you@restaurant.com"}</code>{" "}
216
- in your <code className="rounded bg-zinc-100 px-1.5 py-0.5 text-[12px]">.env</code>, restart, and reload —
217
- or sign in with the owner account.
335
+ <div className="rounded-[6px] border border-dashed border-white/15 px-6 py-12 text-center">
336
+ <h1 className="font-display text-[2rem] leading-tight">This dashboard is owner-only</h1>
337
+ <p className="mx-auto mt-3 max-w-md text-[14px] leading-relaxed text-[var(--cream-2)]">
338
+ You are signed in as <span className="text-[var(--cream)]">{email || "this account"}</span>.
339
+ Only the restaurant owner can see reservations. Set{" "}
340
+ <code className="rounded bg-black/30 px-1.5 py-0.5 text-[12px] text-[var(--cream)]">
341
+ PYLON_OWNER_EMAIL={email || "you@yourrestaurant.com"}
342
+ </code>{" "}
343
+ in <code className="rounded bg-black/30 px-1.5 py-0.5 text-[12px] text-[var(--cream)]">.env</code>,
344
+ restart, and reload. Or sign in with the owner account.
218
345
  </p>
219
346
  </div>
220
347
  );
221
348
  }
222
349
 
350
+ /* ----------------------------- user menu ------------------------------ */
351
+
223
352
  export function UserMenu({ email }: { email: string }) {
224
353
  const { signOut } = useAuth();
225
354
  const initial = (email.trim()[0] || "?").toUpperCase();
@@ -229,18 +358,22 @@ export function UserMenu({ email }: { email: string }) {
229
358
  }
230
359
  return (
231
360
  <details className="group relative">
232
- <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">
361
+ <summary
362
+ aria-label="Account"
363
+ className="flex size-8 cursor-pointer select-none list-none items-center justify-center rounded-full bg-brand text-[12px] font-semibold text-white marker:hidden [&::-webkit-details-marker]:hidden"
364
+ >
233
365
  {initial}
234
366
  </summary>
235
- <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)]">
236
- <div className="border-b border-zinc-100 px-3 py-2">
237
- <div className="truncate text-[13px] font-medium text-zinc-900">{email || "Signed in"}</div>
367
+ <div className="absolute right-0 top-full z-40 mt-2 w-60 overflow-hidden rounded-[6px] border border-white/10 bg-[var(--ink-2)] py-1 shadow-[0_24px_48px_-24px_rgba(0,0,0,0.8)]">
368
+ <div className="truncate border-b border-white/10 px-3 py-2.5 text-[13px] text-[var(--cream)]">
369
+ {email || "Signed in"}
238
370
  </div>
239
371
  <button
240
372
  type="button"
241
373
  onClick={onSignOut}
242
- className="flex w-full items-center px-3 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
374
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-[13px] text-[var(--cream-2)] transition-colors hover:bg-white/5 hover:text-[var(--cream)]"
243
375
  >
376
+ <LogOut aria-hidden className="size-3.5" />
244
377
  Sign out
245
378
  </button>
246
379
  </div>
@@ -248,16 +381,15 @@ export function UserMenu({ email }: { email: string }) {
248
381
  );
249
382
  }
250
383
 
384
+ /* ------------------------------ skeleton ------------------------------ */
385
+
251
386
  function Skeleton() {
252
387
  return (
253
- <div className="space-y-8">
254
- <div className="h-6 w-36 animate-pulse rounded bg-zinc-100" />
255
- <div className="grid gap-4 sm:grid-cols-4">
256
- {[0, 1, 2, 3].map((i) => (
257
- <div key={i} className="h-20 animate-pulse rounded-xl bg-zinc-100" />
258
- ))}
259
- </div>
260
- <div className="h-48 animate-pulse rounded-xl bg-zinc-100" />
388
+ <div className="space-y-10">
389
+ <div className="h-14 w-72 animate-pulse rounded bg-white/5" />
390
+ <div className="h-24 animate-pulse rounded-[6px] bg-white/5" />
391
+ <div className="h-20 animate-pulse rounded-[6px] bg-white/5" />
392
+ <div className="h-72 animate-pulse rounded-[6px] bg-white/5" />
261
393
  </div>
262
394
  );
263
395
  }
@@ -1,5 +1,6 @@
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";
4
5
  import { UserMenu, ReservationsDashboard } from "./dashboard-client";
5
6
 
@@ -32,27 +33,26 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
32
33
  function Shell({ email, children }: { email: string; children: React.ReactNode }) {
33
34
  const { brand } = siteConfig;
34
35
  return (
35
- <div className="flex min-h-screen flex-col bg-white text-zinc-900">
36
- <header className="border-b border-zinc-200">
37
- <div className="mx-auto flex h-14 max-w-4xl items-center justify-between px-6">
38
- <div className="flex items-center gap-2">
39
- <span className="flex size-6 items-center justify-center rounded-[7px] bg-zinc-900 text-[13px] font-bold text-white">
40
- {brand.letter}
41
- </span>
42
- <span className="text-[15px] font-semibold tracking-tight">
43
- {brand.name} <span className="text-zinc-400">/ reservations</span>
44
- </span>
45
- </div>
36
+ <div className="flex min-h-screen flex-col bg-[var(--ink)] text-[var(--cream)]">
37
+ <header className="sticky top-0 z-30 border-b border-white/10 bg-[var(--ink)]/90 backdrop-blur">
38
+ <div className="mx-auto flex h-16 w-full max-w-5xl items-center justify-between px-6">
39
+ <Link href="/" className="font-display text-[22px] tracking-tight">
40
+ {brand.name}
41
+ </Link>
46
42
  <div className="flex items-center gap-4">
47
- <Link href="/" className="text-[13px] text-zinc-500 transition-colors hover:text-zinc-900">
48
- View site ↗
43
+ <Link
44
+ href="/"
45
+ className="hidden items-center gap-1 text-[13px] font-medium text-[var(--cream-2)] transition-colors hover:text-[var(--cream)] sm:inline-flex"
46
+ >
47
+ View site
48
+ <ArrowUpRight aria-hidden className="size-3.5" />
49
49
  </Link>
50
50
  <UserMenu email={email} />
51
51
  </div>
52
52
  </div>
53
53
  </header>
54
54
  <main className="flex-1">
55
- <div className="mx-auto max-w-4xl px-6 py-8">{children}</div>
55
+ <div className="mx-auto w-full max-w-5xl px-6 py-10 sm:py-12">{children}</div>
56
56
  </main>
57
57
  </div>
58
58
  );
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#151311"/>
2
+ <path d="M32 10c9 10 14 18 14 26a14 14 0 0 1-28 0c0-8 5-16 14-26z" fill="#c2571a"/>
3
+ <path d="M32 24v28" stroke="#151311" stroke-width="3.2" stroke-linecap="round"/>
4
+ <path d="M32 38l-7-6M32 32l6-5" stroke="#151311" stroke-width="3" stroke-linecap="round"/></svg>
@@ -18,21 +18,21 @@ export default function LoginPage({ auth, response }: PageProps) {
18
18
  if (auth.user_id && !auth.user_id.startsWith("guest_")) response.redirect("/dashboard");
19
19
  const { brand } = siteConfig;
20
20
  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 reservations.
33
- </p>
34
- <div className="mt-6">
35
- <AuthForm />
21
+ <div className="flex min-h-screen flex-col bg-[var(--ink)] text-[var(--cream)]">
22
+ <div className="border-b border-white/10">
23
+ <div className="mx-auto flex h-16 w-full max-w-5xl items-center px-6">
24
+ <Link href="/" className="font-display text-[22px] tracking-tight">
25
+ {brand.name}
26
+ </Link>
27
+ </div>
28
+ </div>
29
+ <div className="flex flex-1 items-center justify-center px-6 py-16">
30
+ <div className="w-full max-w-[380px]">
31
+ <h1 className="font-display text-[2.5rem] font-medium leading-none">Owner sign in</h1>
32
+ <p className="mt-3 text-[15px] text-[var(--cream-2)]">Tonight&apos;s book, confirmations, and cancellations.</p>
33
+ <div className="mt-8 rounded-[6px] border border-white/10 bg-[var(--ink-2)] p-6">
34
+ <AuthForm />
35
+ </div>
36
36
  </div>
37
37
  </div>
38
38
  </div>
@@ -0,0 +1,44 @@
1
+ import { mutation } from "@pylonsync/functions";
2
+ import { demoDataEnabled, demoReservations } from "../lib/demo";
3
+ import { siteConfig } from "../lib/site.config";
4
+
5
+ // seedDemo — fill an empty reservation book with fictional tables in
6
+ // development, so the public calendar and the owner dashboard show the same
7
+ // reservations.
8
+ //
9
+ // The reservation widget calls this when it finds no reserved tables, and the
10
+ // dashboard calls it once on load. It writes nothing unless demo data is on
11
+ // (lib/demo.ts: PYLON_DEMO_DATA, else a `pylon dev` process), so `pylon start`,
12
+ // Docker, and Pylon Cloud deploys do not get demo rows. It also writes nothing
13
+ // once any Reservation exists, so it cannot mix demo rows into a real book. The
14
+ // advisory lock stops two first visits from seeding twice.
15
+ //
16
+ // Each active reservation gets its ReservationSlot marker, the same pair
17
+ // createReservation writes. Public because the first visitor in `pylon dev` is
18
+ // anonymous. It returns only a flag, never a row.
19
+ export default mutation<Record<string, never>, { seeded: boolean }>({
20
+ auth: "public",
21
+ async handler(ctx) {
22
+ if (!demoDataEnabled(ctx.env)) return { seeded: false };
23
+
24
+ await ctx.db.advisoryLock("restaurant_seed_demo");
25
+ const existing = await ctx.db.unsafe.list("Reservation");
26
+ if (existing.length > 0) return { seeded: false };
27
+
28
+ for (const r of demoReservations(Date.now(), siteConfig)) {
29
+ const reservationId = await ctx.db.unsafe.insert("Reservation", {
30
+ startsAt: r.startsAt,
31
+ partySize: r.partySize,
32
+ customerName: r.customerName,
33
+ customerEmail: r.customerEmail,
34
+ customerPhone: r.customerPhone,
35
+ notes: r.notes,
36
+ status: r.status,
37
+ createdAt: r.createdAt,
38
+ });
39
+ if (r.status === "cancelled") continue;
40
+ await ctx.db.unsafe.insert("ReservationSlot", { startsAt: r.startsAt, reservationId });
41
+ }
42
+ return { seeded: true };
43
+ },
44
+ });