@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.
- package/bin/create-pylon.js +30 -7
- package/package.json +1 -1
- package/templates/agency/.env.example +7 -0
- package/templates/agency/README.md +28 -3
- package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
- package/templates/agency/app/(marketing)/page.tsx +3 -3
- package/templates/agency/app/(marketing)/work/page.tsx +3 -3
- package/templates/agency/app/auth-form.tsx +4 -4
- package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
- package/templates/agency/app/dashboard/page.tsx +14 -16
- package/templates/agency/app/icon.svg +3 -0
- package/templates/agency/app/login/page.tsx +12 -14
- package/templates/agency/components/marketing.tsx +12 -5
- package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
- package/templates/agency/lib/agency.ts +22 -0
- package/templates/agency/lib/demo.ts +118 -0
- package/templates/agency/lib/site.config.ts +19 -13
- package/templates/agency/public/favicon.ico +0 -0
- package/templates/agency/tests/agency.test.ts +57 -0
- package/templates/ai-chat/.env.example +13 -27
- package/templates/ai-chat/README.md +53 -60
- package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
- package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
- package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
- package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
- package/templates/ai-chat/app/(chat)/page.tsx +7 -11
- package/templates/ai-chat/app/auth-form.tsx +45 -38
- package/templates/ai-chat/app/error.tsx +9 -12
- package/templates/ai-chat/app/globals.css +464 -102
- package/templates/ai-chat/app/layout.tsx +11 -19
- package/templates/ai-chat/app/login/page.tsx +39 -24
- package/templates/ai-chat/app.ts +118 -43
- package/templates/ai-chat/components/chat/composer.tsx +193 -0
- package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
- package/templates/ai-chat/components/chat/icons.tsx +198 -0
- package/templates/ai-chat/components/chat/markdown.tsx +164 -0
- package/templates/ai-chat/components/chat/message.tsx +183 -0
- package/templates/ai-chat/components/chat/setup.tsx +71 -0
- package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
- package/templates/ai-chat/functions/_beginTurn.ts +145 -0
- package/templates/ai-chat/functions/_finishTurn.ts +55 -0
- package/templates/ai-chat/functions/assistantStatus.ts +13 -0
- package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
- package/templates/ai-chat/functions/createConversation.ts +28 -0
- package/templates/ai-chat/functions/deleteConversation.ts +15 -20
- package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
- package/templates/ai-chat/functions/renameConversation.ts +19 -0
- package/templates/ai-chat/functions/respond.ts +88 -0
- package/templates/ai-chat/functions/seedExamples.ts +46 -0
- package/templates/ai-chat/functions/stopResponse.ts +30 -0
- package/templates/ai-chat/lib/brand-icon.ts +7 -0
- package/templates/ai-chat/lib/chat.ts +277 -0
- package/templates/ai-chat/lib/claim.ts +15 -0
- package/templates/ai-chat/lib/examples.ts +179 -0
- package/templates/ai-chat/lib/highlight.ts +107 -0
- package/templates/ai-chat/lib/markdown.ts +317 -0
- package/templates/ai-chat/lib/provider.ts +38 -0
- package/templates/ai-chat/lib/site.config.ts +55 -39
- package/templates/ai-chat/tests/chat.test.ts +174 -0
- package/templates/ai-chat/tests/components.test.tsx +57 -0
- package/templates/ai-chat/tests/markdown.test.ts +88 -0
- package/templates/ai-chat/tests/provider.test.ts +32 -0
- package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
- package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
- package/templates/backend/consumer/apps/api/app.ts +166 -0
- package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
- package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
- package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
- package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
- package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
- package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
- package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
- package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
- package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
- package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
- package/templates/backend/consumer/apps/api/package.json +8 -2
- package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
- package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
- package/templates/chat/README.md +43 -21
- package/templates/chat/app/chat-client.tsx +495 -85
- package/templates/chat/app/error.tsx +1 -1
- package/templates/chat/app/globals.css +170 -54
- package/templates/chat/app/layout.tsx +12 -9
- package/templates/chat/app/not-found.tsx +4 -2
- package/templates/chat/app/page.tsx +9 -19
- package/templates/chat/app.ts +96 -17
- package/templates/chat/components/chat/avatar.tsx +45 -0
- package/templates/chat/components/chat/composer.tsx +110 -0
- package/templates/chat/components/chat/message-list.tsx +285 -0
- package/templates/chat/components/chat/sidebar.tsx +190 -0
- package/templates/chat/functions/joinChat.ts +83 -0
- package/templates/chat/functions/renameMember.ts +25 -0
- package/templates/chat/lib/chat.ts +188 -0
- package/templates/chat/lib/seed.ts +78 -0
- package/templates/chat/tests/chat.test.ts +159 -0
- package/templates/chat/tests/composer.test.tsx +54 -0
- package/templates/consumer/README.md +3 -3
- package/templates/consumer/app/globals.css +10 -3
- package/templates/consumer/app/icon.svg +1 -0
- package/templates/consumer/components/social/avatar.tsx +25 -11
- package/templates/consumer/functions/seedFeed.ts +3 -3
- package/templates/consumer/lib/seed.ts +43 -85
- package/templates/consumer/public/favicon.ico +0 -0
- package/templates/consumer/public/images/posts/arch.jpg +0 -0
- package/templates/consumer/public/images/posts/coffee.jpg +0 -0
- package/templates/consumer/public/images/posts/dog.jpg +0 -0
- package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
- package/templates/consumer/public/images/posts/ramen.jpg +0 -0
- package/templates/consumer/public/images/posts/run.jpg +0 -0
- package/templates/consumer/public/images/posts/sunset.jpg +0 -0
- package/templates/consumer/tests/seed.test.ts +39 -0
- package/templates/creator/.env.example +7 -0
- package/templates/creator/README.md +24 -5
- package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
- package/templates/creator/app/auth-form.tsx +7 -7
- package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
- package/templates/creator/app/dashboard/page.tsx +18 -18
- package/templates/creator/app/icon.svg +2 -0
- package/templates/creator/app/login/page.tsx +14 -14
- package/templates/creator/app.ts +4 -4
- package/templates/creator/functions/seedDemo.ts +32 -0
- package/templates/creator/functions/subscribe.ts +4 -15
- package/templates/creator/lib/demo.ts +94 -0
- package/templates/creator/lib/site.config.ts +15 -10
- package/templates/creator/lib/stats.ts +35 -0
- package/templates/creator/public/favicon.ico +0 -0
- package/templates/creator/tests/counter.test.ts +92 -0
- package/templates/creator/tests/live-count.test.tsx +45 -0
- package/templates/crm/README.md +22 -1
- package/templates/crm/app/apple-icon.png +0 -0
- package/templates/crm/app/companies/companies-view.tsx +32 -14
- package/templates/crm/app/companies/page.tsx +2 -1
- package/templates/crm/app/contacts/contacts-view.tsx +34 -10
- package/templates/crm/app/contacts/page.tsx +2 -1
- package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
- package/templates/crm/app/deals/[id]/page.tsx +2 -1
- package/templates/crm/app/globals.css +29 -14
- package/templates/crm/app/icon.svg +1 -0
- package/templates/crm/app/layout.tsx +7 -5
- package/templates/crm/app/page.tsx +2 -1
- package/templates/crm/app/pipeline-view.tsx +5 -5
- package/templates/crm/app/workspace.tsx +29 -17
- package/templates/crm/app.ts +5 -5
- package/templates/crm/components/activity-timeline.tsx +14 -4
- package/templates/crm/components/app-shell.tsx +94 -0
- package/templates/crm/components/auth-form.tsx +35 -5
- package/templates/crm/components/avatar.tsx +5 -1
- package/templates/crm/components/board-skeleton.tsx +5 -5
- package/templates/crm/components/brand-mark.tsx +25 -0
- package/templates/crm/components/command-palette.tsx +17 -17
- package/templates/crm/components/data-table.tsx +97 -36
- package/templates/crm/components/deal-card.tsx +23 -27
- package/templates/crm/components/filter-tabs.tsx +101 -0
- package/templates/crm/components/metrics-bar.tsx +6 -6
- package/templates/crm/components/nav.tsx +15 -0
- package/templates/crm/components/page-header.tsx +43 -7
- package/templates/crm/components/pipeline-board.tsx +16 -10
- package/templates/crm/components/require-auth.tsx +24 -26
- package/templates/crm/components/sidebar.tsx +54 -49
- package/templates/crm/components/stage-badge.tsx +4 -2
- package/templates/crm/components/ui/button.tsx +5 -4
- package/templates/crm/components/ui/dialog.tsx +1 -1
- package/templates/crm/components/ui/input.tsx +1 -1
- package/templates/crm/components/ui/select.tsx +1 -1
- package/templates/crm/components/ui/textarea.tsx +1 -1
- package/templates/crm/functions/seedWorkspace.ts +32 -17
- package/templates/crm/lib/brand.ts +9 -0
- package/templates/crm/lib/demo-team.ts +51 -0
- package/templates/crm/lib/pipeline.ts +37 -0
- package/templates/crm/lib/search.ts +4 -3
- package/templates/crm/lib/seed.ts +53 -8
- package/templates/crm/public/favicon.ico +0 -0
- package/templates/crm/tests/components.test.tsx +5 -5
- package/templates/crm/tests/demo-team.test.ts +18 -0
- package/templates/crm/tests/pipeline.test.ts +59 -3
- package/templates/directory/.env.example +7 -0
- package/templates/directory/README.md +22 -3
- package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
- package/templates/directory/app/(marketing)/page.tsx +6 -4
- package/templates/directory/app/auth-form.tsx +4 -4
- package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
- package/templates/directory/app/dashboard/page.tsx +16 -14
- package/templates/directory/app/icon.svg +4 -0
- package/templates/directory/app/login/page.tsx +16 -15
- package/templates/directory/functions/seedDemo.ts +43 -0
- package/templates/directory/lib/demo.ts +145 -0
- package/templates/directory/lib/directory.ts +27 -0
- package/templates/directory/lib/site.config.ts +21 -16
- package/templates/directory/public/favicon.ico +0 -0
- package/templates/directory/tests/directory.test.ts +46 -0
- package/templates/expo/consumer/apps/expo/App.tsx +360 -288
- package/templates/expo/consumer/apps/expo/app.json +9 -1
- package/templates/expo/consumer/apps/expo/package.json +2 -1
- package/templates/helpdesk/README.md +21 -0
- package/templates/helpdesk/app/apple-icon.png +0 -0
- package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
- package/templates/helpdesk/app/customers/page.tsx +2 -1
- package/templates/helpdesk/app/globals.css +29 -14
- package/templates/helpdesk/app/icon.svg +1 -0
- package/templates/helpdesk/app/inbox-view.tsx +66 -47
- package/templates/helpdesk/app/layout.tsx +7 -5
- package/templates/helpdesk/app/page.tsx +4 -7
- package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
- package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
- package/templates/helpdesk/app/workspace.tsx +33 -22
- package/templates/helpdesk/app.ts +2 -2
- package/templates/helpdesk/components/app-shell.tsx +94 -0
- package/templates/helpdesk/components/auth-form.tsx +35 -5
- package/templates/helpdesk/components/avatar.tsx +5 -1
- package/templates/helpdesk/components/brand-mark.tsx +28 -0
- package/templates/helpdesk/components/command-palette.tsx +17 -17
- package/templates/helpdesk/components/data-table.tsx +97 -36
- package/templates/helpdesk/components/filter-tabs.tsx +101 -0
- package/templates/helpdesk/components/nav.tsx +14 -0
- package/templates/helpdesk/components/page-header.tsx +43 -7
- package/templates/helpdesk/components/priority-badge.tsx +49 -6
- package/templates/helpdesk/components/require-auth.tsx +24 -26
- package/templates/helpdesk/components/sidebar.tsx +54 -48
- package/templates/helpdesk/components/sla-indicator.tsx +5 -8
- package/templates/helpdesk/components/thread.tsx +7 -7
- package/templates/helpdesk/components/ticket-list.tsx +79 -19
- package/templates/helpdesk/components/ui/button.tsx +5 -4
- package/templates/helpdesk/components/ui/dialog.tsx +1 -1
- package/templates/helpdesk/components/ui/input.tsx +1 -1
- package/templates/helpdesk/components/ui/select.tsx +1 -1
- package/templates/helpdesk/components/ui/textarea.tsx +1 -1
- package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
- package/templates/helpdesk/lib/brand.ts +9 -0
- package/templates/helpdesk/lib/demo-team.ts +51 -0
- package/templates/helpdesk/lib/search.ts +4 -3
- package/templates/helpdesk/lib/seed.ts +43 -2
- package/templates/helpdesk/lib/tickets.ts +68 -20
- package/templates/helpdesk/public/favicon.ico +0 -0
- package/templates/helpdesk/tests/components.test.tsx +10 -0
- package/templates/helpdesk/tests/demo-team.test.ts +18 -0
- package/templates/helpdesk/tests/tickets.test.ts +70 -0
- package/templates/imessage/.env.example +49 -0
- package/templates/imessage/AGENTS.md +153 -0
- package/templates/imessage/CLAUDE.md +7 -0
- package/templates/imessage/README.md +187 -0
- package/templates/imessage/app/console.tsx +382 -0
- package/templates/imessage/app/contacts/page.tsx +13 -0
- package/templates/imessage/app/error.tsx +16 -0
- package/templates/imessage/app/gate.tsx +31 -0
- package/templates/imessage/app/globals.css +183 -0
- package/templates/imessage/app/layout.tsx +17 -0
- package/templates/imessage/app/login/login-form.tsx +115 -0
- package/templates/imessage/app/login/page.tsx +30 -0
- package/templates/imessage/app/not-found.tsx +13 -0
- package/templates/imessage/app/page.tsx +14 -0
- package/templates/imessage/app/robots.ts +6 -0
- package/templates/imessage/app/setup/page.tsx +13 -0
- package/templates/imessage/app/sign-out.tsx +20 -0
- package/templates/imessage/app.ts +299 -0
- package/templates/imessage/bunfig +4 -0
- package/templates/imessage/components/avatar.tsx +46 -0
- package/templates/imessage/components/contact-panel.tsx +263 -0
- package/templates/imessage/components/contacts-view.tsx +172 -0
- package/templates/imessage/components/conversation-list.tsx +129 -0
- package/templates/imessage/components/nav-rail.tsx +67 -0
- package/templates/imessage/components/setup-view.tsx +408 -0
- package/templates/imessage/components/thread.tsx +229 -0
- package/templates/imessage/components/ui/badge.tsx +37 -0
- package/templates/imessage/components/ui/button.tsx +56 -0
- package/templates/imessage/components/ui/card.tsx +90 -0
- package/templates/imessage/components/ui/dialog.tsx +110 -0
- package/templates/imessage/components/ui/input.tsx +30 -0
- package/templates/imessage/components/ui/label.tsx +21 -0
- package/templates/imessage/components/ui/select.tsx +39 -0
- package/templates/imessage/components/ui/table.tsx +124 -0
- package/templates/imessage/components/ui/textarea.tsx +24 -0
- package/templates/imessage/components.json +20 -0
- package/templates/imessage/functions/answerNow.ts +34 -0
- package/templates/imessage/functions/assistant.ts +96 -0
- package/templates/imessage/functions/beginTurn.ts +101 -0
- package/templates/imessage/functions/cancelReminder.ts +13 -0
- package/templates/imessage/functions/claimOutbound.ts +44 -0
- package/templates/imessage/functions/clearDemo.ts +18 -0
- package/templates/imessage/functions/deleteNote.ts +11 -0
- package/templates/imessage/functions/deliverOutbound.ts +55 -0
- package/templates/imessage/functions/finishTurn.ts +84 -0
- package/templates/imessage/functions/fireReminder.ts +42 -0
- package/templates/imessage/functions/ingestInbound.ts +22 -0
- package/templates/imessage/functions/ownerAccount.ts +13 -0
- package/templates/imessage/functions/processTurn.ts +74 -0
- package/templates/imessage/functions/relayApply.ts +102 -0
- package/templates/imessage/functions/relaySync.ts +63 -0
- package/templates/imessage/functions/releaseOutbound.ts +18 -0
- package/templates/imessage/functions/resolveTurn.ts +25 -0
- package/templates/imessage/functions/saveContact.ts +27 -0
- package/templates/imessage/functions/seedDemo.ts +70 -0
- package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
- package/templates/imessage/functions/sendblueWebhook.ts +49 -0
- package/templates/imessage/functions/setContactStatus.ts +17 -0
- package/templates/imessage/functions/settleOutbound.ts +35 -0
- package/templates/imessage/functions/setupStatus.ts +33 -0
- package/templates/imessage/functions/sweep.ts +58 -0
- package/templates/imessage/functions/toolCancelReminder.ts +17 -0
- package/templates/imessage/functions/toolForgetNote.ts +15 -0
- package/templates/imessage/functions/toolListNotes.ts +16 -0
- package/templates/imessage/functions/toolListReminders.ts +17 -0
- package/templates/imessage/functions/toolSaveNote.ts +22 -0
- package/templates/imessage/functions/toolSetReminder.ts +55 -0
- package/templates/imessage/functions/updateSettings.ts +48 -0
- package/templates/imessage/gitignore +11 -0
- package/templates/imessage/lib/agent-call.ts +251 -0
- package/templates/imessage/lib/assistant.ts +74 -0
- package/templates/imessage/lib/chunk.ts +135 -0
- package/templates/imessage/lib/demo.ts +68 -0
- package/templates/imessage/lib/format.ts +170 -0
- package/templates/imessage/lib/gate.ts +104 -0
- package/templates/imessage/lib/handles.ts +67 -0
- package/templates/imessage/lib/provider.ts +38 -0
- package/templates/imessage/lib/relay-protocol.ts +95 -0
- package/templates/imessage/lib/reminders.ts +41 -0
- package/templates/imessage/lib/secrets.ts +59 -0
- package/templates/imessage/lib/sendblue.ts +173 -0
- package/templates/imessage/lib/store.ts +407 -0
- package/templates/imessage/lib/transport.ts +135 -0
- package/templates/imessage/lib/utils.ts +10 -0
- package/templates/imessage/package.json +40 -0
- package/templates/imessage/relay/.env.example +14 -0
- package/templates/imessage/relay/applescript.ts +70 -0
- package/templates/imessage/relay/chatdb.ts +165 -0
- package/templates/imessage/relay/client.ts +41 -0
- package/templates/imessage/relay/main.ts +137 -0
- package/templates/imessage/relay/state.ts +35 -0
- package/templates/imessage/tests/agent-call.test.ts +112 -0
- package/templates/imessage/tests/applescript.test.ts +88 -0
- package/templates/imessage/tests/chatdb.test.ts +139 -0
- package/templates/imessage/tests/chunk.test.ts +61 -0
- package/templates/imessage/tests/fake-ctx.ts +88 -0
- package/templates/imessage/tests/ingest.test.ts +141 -0
- package/templates/imessage/tests/lib.test.ts +99 -0
- package/templates/imessage/tests/review-fixes.test.ts +138 -0
- package/templates/imessage/tests/secrets.test.ts +36 -0
- package/templates/imessage/tests/sendblue.test.ts +143 -0
- package/templates/imessage/tests/setup.ts +5 -0
- package/templates/imessage/tsconfig.json +18 -0
- package/templates/inventory/README.md +21 -0
- package/templates/inventory/app/apple-icon.png +0 -0
- package/templates/inventory/app/globals.css +29 -14
- package/templates/inventory/app/icon.svg +1 -0
- package/templates/inventory/app/layout.tsx +7 -5
- package/templates/inventory/app/movements/movements-view.tsx +32 -10
- package/templates/inventory/app/movements/page.tsx +2 -1
- package/templates/inventory/app/page.tsx +4 -6
- package/templates/inventory/app/products/[id]/page.tsx +2 -1
- package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
- package/templates/inventory/app/products-view.tsx +74 -33
- package/templates/inventory/app/workspace.tsx +34 -16
- package/templates/inventory/app.ts +10 -2
- package/templates/inventory/components/app-shell.tsx +94 -0
- package/templates/inventory/components/auth-form.tsx +35 -5
- package/templates/inventory/components/avatar.tsx +5 -1
- package/templates/inventory/components/brand-mark.tsx +25 -0
- package/templates/inventory/components/category-tile.tsx +49 -0
- package/templates/inventory/components/command-palette.tsx +17 -17
- package/templates/inventory/components/data-table.tsx +97 -36
- package/templates/inventory/components/filter-tabs.tsx +101 -0
- package/templates/inventory/components/nav.tsx +14 -0
- package/templates/inventory/components/page-header.tsx +43 -7
- package/templates/inventory/components/require-auth.tsx +24 -26
- package/templates/inventory/components/sidebar.tsx +54 -48
- package/templates/inventory/components/stock-level.tsx +30 -14
- package/templates/inventory/components/summary-bar.tsx +8 -15
- package/templates/inventory/components/ui/button.tsx +5 -4
- package/templates/inventory/components/ui/dialog.tsx +1 -1
- package/templates/inventory/components/ui/input.tsx +1 -1
- package/templates/inventory/components/ui/select.tsx +1 -1
- package/templates/inventory/components/ui/textarea.tsx +1 -1
- package/templates/inventory/functions/seedWorkspace.ts +18 -8
- package/templates/inventory/lib/brand.ts +9 -0
- package/templates/inventory/lib/demo-team.ts +51 -0
- package/templates/inventory/lib/search.ts +4 -3
- package/templates/inventory/lib/seed.ts +29 -2
- package/templates/inventory/lib/stock.ts +32 -0
- package/templates/inventory/public/favicon.ico +0 -0
- package/templates/inventory/tests/demo-team.test.ts +18 -0
- package/templates/inventory/tests/stock.test.ts +29 -0
- package/templates/invoices/README.md +16 -0
- package/templates/invoices/app/apple-icon.png +0 -0
- package/templates/invoices/app/clients/clients-view.tsx +18 -6
- package/templates/invoices/app/clients/page.tsx +2 -1
- package/templates/invoices/app/globals.css +29 -14
- package/templates/invoices/app/icon.svg +1 -0
- package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
- package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
- package/templates/invoices/app/invoices-view.tsx +81 -32
- package/templates/invoices/app/layout.tsx +7 -5
- package/templates/invoices/app/page.tsx +4 -6
- package/templates/invoices/app/workspace.tsx +26 -16
- package/templates/invoices/app.ts +2 -2
- package/templates/invoices/components/app-shell.tsx +94 -0
- package/templates/invoices/components/auth-form.tsx +35 -5
- package/templates/invoices/components/avatar.tsx +5 -1
- package/templates/invoices/components/brand-mark.tsx +26 -0
- package/templates/invoices/components/command-palette.tsx +17 -17
- package/templates/invoices/components/data-table.tsx +97 -36
- package/templates/invoices/components/filter-tabs.tsx +101 -0
- package/templates/invoices/components/line-items.tsx +19 -14
- package/templates/invoices/components/nav.tsx +14 -0
- package/templates/invoices/components/page-header.tsx +43 -7
- package/templates/invoices/components/require-auth.tsx +24 -26
- package/templates/invoices/components/sidebar.tsx +54 -48
- package/templates/invoices/components/status-badge.tsx +7 -7
- package/templates/invoices/components/summary-bar.tsx +5 -5
- package/templates/invoices/components/ui/button.tsx +5 -4
- package/templates/invoices/components/ui/dialog.tsx +1 -1
- package/templates/invoices/components/ui/input.tsx +1 -1
- package/templates/invoices/components/ui/select.tsx +1 -1
- package/templates/invoices/components/ui/textarea.tsx +1 -1
- package/templates/invoices/functions/recordPayment.ts +3 -0
- package/templates/invoices/lib/billing.ts +40 -0
- package/templates/invoices/lib/brand.ts +9 -0
- package/templates/invoices/lib/search.ts +4 -3
- package/templates/invoices/public/favicon.ico +0 -0
- package/templates/invoices/tests/billing.test.ts +27 -0
- package/templates/ios/consumer/apps/ios/Package.swift +4 -3
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
- package/templates/ios/consumer/apps/ios/project.yml +13 -9
- package/templates/local-service/.env.example +7 -0
- package/templates/local-service/README.md +26 -2
- package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
- package/templates/local-service/app/(marketing)/page.tsx +66 -42
- package/templates/local-service/app/auth-form.tsx +8 -8
- package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
- package/templates/local-service/app/dashboard/page.tsx +14 -14
- package/templates/local-service/app/icon.svg +2 -0
- package/templates/local-service/app/login/page.tsx +15 -15
- package/templates/local-service/functions/seedDemo.ts +49 -0
- package/templates/local-service/lib/demo.ts +135 -0
- package/templates/local-service/lib/hours.ts +44 -0
- package/templates/local-service/lib/site.config.ts +8 -3
- package/templates/local-service/lib/slots.ts +3 -0
- package/templates/local-service/public/favicon.ico +0 -0
- package/templates/local-service/tests/booking.test.ts +104 -0
- package/templates/marketplace/README.md +23 -15
- package/templates/marketplace/app/globals.css +172 -114
- package/templates/marketplace/app/layout.tsx +75 -30
- package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
- package/templates/marketplace/app/me/page.tsx +4 -4
- package/templates/marketplace/app/page.tsx +221 -298
- package/templates/marketplace/app/sell/page.tsx +8 -11
- package/templates/marketplace/app.ts +32 -7
- package/templates/marketplace/client/ActivityFeed.tsx +311 -0
- package/templates/marketplace/client/AuthNav.tsx +24 -16
- package/templates/marketplace/client/ListingCard.tsx +118 -0
- package/templates/marketplace/client/LiveGrid.tsx +92 -0
- package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
- package/templates/marketplace/client/LoginCard.tsx +114 -136
- package/templates/marketplace/client/MarketProvider.tsx +3 -5
- package/templates/marketplace/client/MyMarket.tsx +371 -185
- package/templates/marketplace/client/OfferPanel.tsx +421 -287
- package/templates/marketplace/client/SellForm.tsx +271 -192
- package/templates/marketplace/client/ThemeToggle.tsx +1 -0
- package/templates/marketplace/client/WatchButton.tsx +16 -16
- package/templates/marketplace/client/market.ts +61 -14
- package/templates/marketplace/functions/seedMarket.ts +143 -36
- package/templates/marketplace/functions/seedOffers.ts +101 -0
- package/templates/marketplace/lib/catalog.ts +72 -1
- package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
- package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
- package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
- package/templates/marketplace/public/images/listings/duffel.webp +0 -0
- package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
- package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
- package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
- package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
- package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
- package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
- package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
- package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
- package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
- package/templates/marketplace/public/images/listings/turntable.webp +0 -0
- package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
- package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
- package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
- package/templates/marketplace/public/images/listings/watch.webp +0 -0
- package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
- package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
- package/templates/marketplace/tests/example.test.ts +64 -2
- package/templates/marketplace/tests/listing-card.test.tsx +61 -0
- package/templates/projects/README.md +24 -3
- package/templates/projects/app/apple-icon.png +0 -0
- package/templates/projects/app/clients/clients-view.tsx +20 -6
- package/templates/projects/app/clients/page.tsx +2 -1
- package/templates/projects/app/globals.css +29 -14
- package/templates/projects/app/icon.svg +1 -0
- package/templates/projects/app/layout.tsx +7 -5
- package/templates/projects/app/page.tsx +3 -5
- package/templates/projects/app/projects/[id]/page.tsx +2 -1
- package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
- package/templates/projects/app/projects-view.tsx +105 -52
- package/templates/projects/app/workspace.tsx +41 -20
- package/templates/projects/app.ts +2 -2
- package/templates/projects/components/app-shell.tsx +94 -0
- package/templates/projects/components/auth-form.tsx +35 -5
- package/templates/projects/components/avatar.tsx +5 -1
- package/templates/projects/components/board-skeleton.tsx +3 -3
- package/templates/projects/components/brand-mark.tsx +25 -0
- package/templates/projects/components/command-palette.tsx +17 -17
- package/templates/projects/components/data-table.tsx +97 -36
- package/templates/projects/components/filter-tabs.tsx +101 -0
- package/templates/projects/components/meter.tsx +48 -0
- package/templates/projects/components/nav.tsx +14 -0
- package/templates/projects/components/page-header.tsx +43 -7
- package/templates/projects/components/require-auth.tsx +24 -26
- package/templates/projects/components/sidebar.tsx +54 -48
- package/templates/projects/components/summary-bar.tsx +49 -0
- package/templates/projects/components/task-board.tsx +10 -9
- package/templates/projects/components/time-dialog.tsx +29 -5
- package/templates/projects/components/ui/button.tsx +5 -4
- package/templates/projects/components/ui/dialog.tsx +1 -1
- package/templates/projects/components/ui/input.tsx +1 -1
- package/templates/projects/components/ui/select.tsx +1 -1
- package/templates/projects/components/ui/textarea.tsx +1 -1
- package/templates/projects/functions/seedWorkspace.ts +26 -12
- package/templates/projects/lib/brand.ts +9 -0
- package/templates/projects/lib/demo-team.ts +51 -0
- package/templates/projects/lib/search.ts +4 -3
- package/templates/projects/lib/seed.ts +35 -1
- package/templates/projects/lib/work.ts +63 -0
- package/templates/projects/public/favicon.ico +0 -0
- package/templates/projects/tests/demo-team.test.ts +18 -0
- package/templates/projects/tests/work.test.ts +65 -0
- package/templates/restaurant/.env.example +7 -0
- package/templates/restaurant/README.md +29 -3
- package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
- package/templates/restaurant/app/(marketing)/page.tsx +47 -23
- package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
- package/templates/restaurant/app/auth-form.tsx +8 -8
- package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
- package/templates/restaurant/app/dashboard/page.tsx +14 -14
- package/templates/restaurant/app/icon.svg +4 -0
- package/templates/restaurant/app/login/page.tsx +15 -15
- package/templates/restaurant/functions/seedDemo.ts +44 -0
- package/templates/restaurant/lib/demo.ts +144 -0
- package/templates/restaurant/lib/hours.ts +45 -0
- package/templates/restaurant/lib/site.config.ts +3 -3
- package/templates/restaurant/public/favicon.ico +0 -0
- package/templates/restaurant/tests/reservations.test.ts +67 -0
- package/templates/saas/README.md +54 -35
- package/templates/saas/app/(auth)/auth-form.tsx +25 -23
- package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
- package/templates/saas/app/(auth)/layout.tsx +27 -33
- package/templates/saas/app/(auth)/login/page.tsx +1 -1
- package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
- package/templates/saas/app/(auth)/signup/page.tsx +1 -1
- package/templates/saas/app/(marketing)/layout.tsx +127 -253
- package/templates/saas/app/(marketing)/page.tsx +133 -372
- package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
- package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
- package/templates/saas/app/dashboard/board-client.tsx +416 -0
- package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
- package/templates/saas/app/dashboard/layout.tsx +27 -13
- package/templates/saas/app/dashboard/overview-client.tsx +332 -0
- package/templates/saas/app/dashboard/page.tsx +29 -35
- package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
- package/templates/saas/app/dashboard/projects/page.tsx +11 -7
- package/templates/saas/app/dashboard/projects-client.tsx +398 -0
- package/templates/saas/app/globals.css +5 -4
- package/templates/saas/app/icon.svg +1 -0
- package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
- package/templates/saas/app/onboarding/page.tsx +6 -2
- package/templates/saas/app.ts +68 -13
- package/templates/saas/components/dashboard-shell.tsx +212 -132
- package/templates/saas/components/marketing.tsx +137 -274
- package/templates/saas/components/pricing-table.tsx +12 -11
- package/templates/saas/components/setup-checklist.tsx +31 -29
- package/templates/saas/components/task-ui.tsx +199 -0
- package/templates/saas/functions/createProject.ts +7 -13
- package/templates/saas/functions/createTask.ts +67 -0
- package/templates/saas/functions/deleteProject.ts +17 -0
- package/templates/saas/functions/deleteTask.ts +14 -0
- package/templates/saas/functions/seedWorkspace.ts +46 -0
- package/templates/saas/functions/setProjectStatus.ts +41 -0
- package/templates/saas/functions/updateProfile.ts +13 -0
- package/templates/saas/functions/updateProject.ts +4 -5
- package/templates/saas/functions/updateTask.ts +88 -0
- package/templates/saas/lib/members.ts +53 -0
- package/templates/saas/lib/names.ts +53 -0
- package/templates/saas/lib/plans.ts +11 -0
- package/templates/saas/lib/sample-workspace.ts +117 -0
- package/templates/saas/lib/site.config.ts +200 -463
- package/templates/saas/lib/tasks.ts +101 -0
- package/templates/saas/lib/use-live.ts +25 -0
- package/templates/saas/public/favicon.ico +0 -0
- package/templates/saas/public/screenshots/board.png +0 -0
- package/templates/saas/public/screenshots/members.png +0 -0
- package/templates/saas/public/screenshots/overview.png +0 -0
- package/templates/saas/public/screenshots/projects.png +0 -0
- package/templates/saas/scripts/capture-screenshots.mjs +156 -0
- package/templates/saas/tests/names.test.ts +35 -0
- package/templates/saas/tests/plans.test.ts +12 -0
- package/templates/saas/tests/sample-workspace.test.ts +46 -0
- package/templates/saas/tests/tasks.test.ts +81 -0
- package/templates/shop/README.md +25 -13
- package/templates/shop/app/(marketing)/layout.tsx +94 -67
- package/templates/shop/app/(marketing)/not-found.tsx +13 -12
- package/templates/shop/app/(marketing)/page.tsx +120 -59
- package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
- package/templates/shop/app/(marketing)/success/page.tsx +75 -22
- package/templates/shop/app/globals.css +94 -1
- package/templates/shop/app/layout.tsx +4 -4
- package/templates/shop/app/login/page.tsx +4 -4
- package/templates/shop/app.ts +18 -6
- package/templates/shop/components/cart-button.tsx +33 -0
- package/templates/shop/components/marketing.tsx +17 -87
- package/templates/shop/components/shelf-count.tsx +36 -0
- package/templates/shop/functions/checkout.ts +17 -1
- package/templates/shop/lib/cart-store.ts +57 -0
- package/templates/shop/lib/shelf-store.ts +35 -0
- package/templates/shop/lib/shop.ts +27 -0
- package/templates/shop/lib/site.config.ts +108 -98
- package/templates/shop/public/images/hero.jpg +0 -0
- package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
- package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
- package/templates/shop/public/images/products/linen.jpg +0 -0
- package/templates/shop/public/images/products/sea-salt.jpg +0 -0
- package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
- package/templates/shop/tests/shop.test.ts +64 -0
- package/templates/waitlist/.env.example +7 -0
- package/templates/waitlist/README.md +29 -13
- package/templates/waitlist/app/(marketing)/page.tsx +9 -7
- package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
- package/templates/waitlist/app/auth-form.tsx +8 -8
- package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
- package/templates/waitlist/app/dashboard/page.tsx +17 -16
- package/templates/waitlist/app/icon.svg +2 -0
- package/templates/waitlist/app/login/page.tsx +14 -14
- package/templates/waitlist/app.ts +6 -6
- package/templates/waitlist/functions/joinWaitlist.ts +6 -18
- package/templates/waitlist/functions/seedDemo.ts +32 -0
- package/templates/waitlist/lib/demo.ts +94 -0
- package/templates/waitlist/lib/launch.ts +19 -0
- package/templates/waitlist/lib/site.config.ts +14 -9
- package/templates/waitlist/lib/stats.ts +35 -0
- package/templates/waitlist/public/favicon.ico +0 -0
- package/templates/waitlist/tests/counter.test.ts +107 -0
- package/templates/waitlist/tests/live-count.test.tsx +48 -0
- package/templates/ai-chat/tests/example.test.ts +0 -12
- package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
- package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
- package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
- package/templates/backend/consumer/apps/api/schema.ts +0 -143
- package/templates/chat/functions/_keep.ts +0 -13
- package/templates/chat/tests/example.test.ts +0 -12
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
- package/templates/marketplace/client/LiveTicker.tsx +0 -116
- package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
- package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
|
@@ -65,6 +65,39 @@ export interface TimeEntry {
|
|
|
65
65
|
spentOn?: string | null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
export interface ProjectTab {
|
|
69
|
+
id: string;
|
|
70
|
+
label: string;
|
|
71
|
+
/** Project status the tab shows; undefined shows every project. */
|
|
72
|
+
status?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The project list's tabs, left to right. */
|
|
76
|
+
export const PROJECT_TABS: ProjectTab[] = [
|
|
77
|
+
{ id: "active", label: "Active", status: "active" },
|
|
78
|
+
{ id: "paused", label: "Paused", status: "paused" },
|
|
79
|
+
{ id: "complete", label: "Complete", status: "complete" },
|
|
80
|
+
{ id: "all", label: "All" },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
/** The tab the list opens on. The sidebar's Projects count is this tab's count. */
|
|
84
|
+
export const DEFAULT_PROJECT_TAB = "active";
|
|
85
|
+
|
|
86
|
+
export function inProjectTab(tabId: string, project: { status: string }): boolean {
|
|
87
|
+
const tab = PROJECT_TABS.find((t) => t.id === tabId);
|
|
88
|
+
return !tab?.status || project.status === tab.status;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* How many projects each tab lists. The sidebar badge, the page header, and
|
|
93
|
+
* the tab counts all read this, so they always agree.
|
|
94
|
+
*/
|
|
95
|
+
export function projectTabCounts(projects: Array<{ status: string }>): Record<string, number> {
|
|
96
|
+
return Object.fromEntries(
|
|
97
|
+
PROJECT_TABS.map((tab) => [tab.id, projects.filter((p) => inProjectTab(tab.id, p)).length]),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
68
101
|
export function taskStatusById(id: string): Status | undefined {
|
|
69
102
|
return TASK_STATUSES.find((s) => s.id === id);
|
|
70
103
|
}
|
|
@@ -231,3 +264,33 @@ export function parseDuration(input: string): number | null {
|
|
|
231
264
|
|
|
232
265
|
return null;
|
|
233
266
|
}
|
|
267
|
+
|
|
268
|
+
export interface Portfolio {
|
|
269
|
+
/** Projects with status "active". */
|
|
270
|
+
activeCount: number;
|
|
271
|
+
/** Minutes logged against active projects. */
|
|
272
|
+
loggedMinutes: number;
|
|
273
|
+
/** What that time is worth at each project's rate. */
|
|
274
|
+
billableCents: number;
|
|
275
|
+
/** Active projects past their budget. */
|
|
276
|
+
overBudgetCount: number;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** The numbers above the project list, over active projects only. */
|
|
280
|
+
export function portfolio(projects: Project[], entries: TimeEntry[]): Portfolio {
|
|
281
|
+
const result: Portfolio = {
|
|
282
|
+
activeCount: 0,
|
|
283
|
+
loggedMinutes: 0,
|
|
284
|
+
billableCents: 0,
|
|
285
|
+
overBudgetCount: 0,
|
|
286
|
+
};
|
|
287
|
+
for (const project of projects) {
|
|
288
|
+
if (project.status !== "active") continue;
|
|
289
|
+
const minutes = minutesForProject(project.id, entries);
|
|
290
|
+
result.activeCount += 1;
|
|
291
|
+
result.loggedMinutes += minutes;
|
|
292
|
+
result.billableCents += billableCents(minutes, project.hourlyRateCents);
|
|
293
|
+
if (budgetState(project.budgetMinutes, minutes) === "over") result.overBudgetCount += 1;
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { demoEmail, isDemoRow } from "../lib/demo-team";
|
|
3
|
+
|
|
4
|
+
describe("demo teammates", () => {
|
|
5
|
+
test("the first address is the person's own, then numbered aliases", () => {
|
|
6
|
+
expect(demoEmail("maya.castillo@demo.invalid", 0)).toBe("maya.castillo@demo.invalid");
|
|
7
|
+
expect(demoEmail("maya.castillo@demo.invalid", 1)).toBe("maya.castillo+2@demo.invalid");
|
|
8
|
+
expect(demoEmail("maya.castillo@demo.invalid", 2)).toBe("maya.castillo+3@demo.invalid");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("a registered account is never reused as a demo teammate", () => {
|
|
12
|
+
// Someone who registered a demo address first must not be handed the
|
|
13
|
+
// seeded records.
|
|
14
|
+
expect(isDemoRow({ email: "x@demo.invalid", passwordHash: "argon2id$..." })).toBe(false);
|
|
15
|
+
expect(isDemoRow({ email: "x@demo.invalid", emailVerified: "2026-07-01T00:00:00Z" })).toBe(false);
|
|
16
|
+
expect(isDemoRow({ email: "x@demo.invalid", displayName: "Maya Castillo" })).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
import {
|
|
3
|
+
DEFAULT_PROJECT_TAB,
|
|
4
|
+
PROJECT_TABS,
|
|
3
5
|
TASK_STATUSES,
|
|
4
6
|
billableCents,
|
|
5
7
|
budgetState,
|
|
6
8
|
duration,
|
|
7
9
|
groupByStatus,
|
|
10
|
+
inProjectTab,
|
|
8
11
|
isOpen,
|
|
9
12
|
isValidProjectStatus,
|
|
10
13
|
isValidTaskStatus,
|
|
@@ -13,10 +16,13 @@ import {
|
|
|
13
16
|
minutesForTask,
|
|
14
17
|
money,
|
|
15
18
|
parseDuration,
|
|
19
|
+
portfolio,
|
|
16
20
|
progress,
|
|
21
|
+
projectTabCounts,
|
|
17
22
|
type Task,
|
|
18
23
|
type TimeEntry,
|
|
19
24
|
} from "../lib/work";
|
|
25
|
+
import { SEED_TEAM, shapeSeed } from "../lib/seed";
|
|
20
26
|
|
|
21
27
|
const task = (over: Partial<Task> = {}): Task => ({
|
|
22
28
|
id: "t1",
|
|
@@ -236,3 +242,62 @@ describe("money", () => {
|
|
|
236
242
|
expect(money(null)).toBe("$0.00");
|
|
237
243
|
});
|
|
238
244
|
});
|
|
245
|
+
|
|
246
|
+
describe("project tabs", () => {
|
|
247
|
+
const projects = [
|
|
248
|
+
{ id: "a", status: "active" },
|
|
249
|
+
{ id: "b", status: "active" },
|
|
250
|
+
{ id: "c", status: "paused" },
|
|
251
|
+
{ id: "d", status: "complete" },
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
test("the sidebar count is the default tab's row count", () => {
|
|
255
|
+
// The sidebar once counted active projects (2) while the list showed all
|
|
256
|
+
// of them (4). Both now read projectTabCounts for the default tab.
|
|
257
|
+
const counts = projectTabCounts(projects);
|
|
258
|
+
expect(counts[DEFAULT_PROJECT_TAB]).toBe(
|
|
259
|
+
projects.filter((p) => inProjectTab(DEFAULT_PROJECT_TAB, p)).length,
|
|
260
|
+
);
|
|
261
|
+
expect(counts[DEFAULT_PROJECT_TAB]).toBe(2);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("each tab counts what it lists", () => {
|
|
265
|
+
const counts = projectTabCounts(projects);
|
|
266
|
+
for (const tab of PROJECT_TABS) {
|
|
267
|
+
expect(counts[tab.id]).toBe(projects.filter((p) => inProjectTab(tab.id, p)).length);
|
|
268
|
+
}
|
|
269
|
+
expect(counts).toEqual({ active: 2, paused: 1, complete: 1, all: 4 });
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
describe("seed", () => {
|
|
274
|
+
test("tasks are spread across the whole team, with some left unassigned", () => {
|
|
275
|
+
const tasks = shapeSeed(Date.parse("2026-07-27T12:00:00Z")).tasks;
|
|
276
|
+
const owners = new Set(tasks.map((t) => t.assignee));
|
|
277
|
+
for (let i = 0; i <= SEED_TEAM.length; i += 1) expect(owners.has(i)).toBe(true);
|
|
278
|
+
expect(owners.has(null)).toBe(true);
|
|
279
|
+
// Only work that hasn't started is unassigned.
|
|
280
|
+
for (const t of tasks) if (t.assignee === null) expect(t.row.status).toBe("todo");
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe("portfolio", () => {
|
|
285
|
+
test("totals active projects only and counts the ones over budget", () => {
|
|
286
|
+
const projects = [
|
|
287
|
+
{ id: "a", name: "A", status: "active", budgetMinutes: 60, hourlyRateCents: 10_000 },
|
|
288
|
+
{ id: "b", name: "B", status: "active", budgetMinutes: 600, hourlyRateCents: 0 },
|
|
289
|
+
{ id: "c", name: "C", status: "complete", budgetMinutes: 60, hourlyRateCents: 10_000 },
|
|
290
|
+
];
|
|
291
|
+
const entries = [
|
|
292
|
+
{ id: "1", taskId: "t", projectId: "a", minutes: 90 },
|
|
293
|
+
{ id: "2", taskId: "t", projectId: "b", minutes: 30 },
|
|
294
|
+
{ id: "3", taskId: "t", projectId: "c", minutes: 500 },
|
|
295
|
+
];
|
|
296
|
+
expect(portfolio(projects, entries)).toEqual({
|
|
297
|
+
activeCount: 2,
|
|
298
|
+
loggedMinutes: 120,
|
|
299
|
+
billableCents: 15_000,
|
|
300
|
+
overBudgetCount: 1,
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -10,3 +10,10 @@ PYLON_OWNER_EMAIL=you@yourrestaurant.com
|
|
|
10
10
|
# ── Site URL (optional) ──────────────────────────────────────────────────────
|
|
11
11
|
# Used by robots.txt + sitemap.xml. Point it at your real domain in production.
|
|
12
12
|
# SITE_URL=https://yourrestaurant.com
|
|
13
|
+
|
|
14
|
+
# ── Demo data ────────────────────────────────────────────────────────────────
|
|
15
|
+
# `pylon dev` fills an empty reservation book with fictional tables. Seeding
|
|
16
|
+
# runs only under `pylon dev` (it checks PYLON_DEV_MODE and
|
|
17
|
+
# PYLON_DEV_WATCH_DIR), so `pylon start`, Docker, and Pylon Cloud deploys never
|
|
18
|
+
# seed. Set 0 to start empty in development. Never set 1 on a deploy.
|
|
19
|
+
# PYLON_DEMO_DATA=0
|
|
@@ -17,7 +17,7 @@ time in one tab and watch it change to "Full" in another without refreshing.
|
|
|
17
17
|
|
|
18
18
|
## How the realtime works
|
|
19
19
|
|
|
20
|
-
- `app/reservation-widget.tsx` subscribes to the public, PII-free
|
|
20
|
+
- `app/(marketing)/reservation-widget.tsx` subscribes to the public, PII-free
|
|
21
21
|
`ReservationSlot` markers with `db.useQuery` and COUNTS them per seating, so
|
|
22
22
|
"tables left" ticks down live across every tab.
|
|
23
23
|
- `functions/createReservation.ts` is a public **mutation** that re-checks the
|
|
@@ -43,25 +43,51 @@ details, notes — with confirm/cancel, plus live covers + counts.
|
|
|
43
43
|
Set `PYLON_OWNER_EMAIL` in `.env` (see `.env.example`) to the email you'll sign
|
|
44
44
|
in with, then create that account at `/login`.
|
|
45
45
|
|
|
46
|
+
## Demo data
|
|
47
|
+
|
|
48
|
+
In `pylon dev`, an empty reservation book gets fictional tables from the past
|
|
49
|
+
week and the next two weeks (`functions/seedDemo.ts`, `lib/demo.ts`). Each one
|
|
50
|
+
writes a `Reservation` and its `ReservationSlot` marker, like a real
|
|
51
|
+
reservation, so the public calendar shows fewer tables left and the dashboard
|
|
52
|
+
lists the same guests. Seeding runs only when `PYLON_DEMO_DATA` is on, or when
|
|
53
|
+
it is unset and the app runs under `pylon dev`, and only while the `Reservation` table
|
|
54
|
+
is empty. `pylon start`, Docker, and Pylon Cloud deploys do not seed, because only `pylon dev` sets `PYLON_DEV_WATCH_DIR`. Set
|
|
55
|
+
`PYLON_DEMO_DATA=0` to start empty in development. Delete both files once you
|
|
56
|
+
take real reservations.
|
|
57
|
+
|
|
58
|
+
## Map or hours card
|
|
59
|
+
|
|
60
|
+
Set `location.mapEmbedUrl` in `lib/site.config.ts` to a Google Maps embed URL to
|
|
61
|
+
show a map beside the address. Without one, the site shows the week's service
|
|
62
|
+
hours (from `reservations.hours`, the same hours the calendar uses) and a
|
|
63
|
+
directions link. The dashboard reminds you while the URL is empty.
|
|
64
|
+
|
|
46
65
|
## Rebrand + reconfigure it
|
|
47
66
|
|
|
48
67
|
Brand, colors, the menu, seating hours, tables per seating, lead time, reviews,
|
|
49
68
|
location, and FAQ content live in **`lib/site.config.ts`**. Editing or
|
|
50
69
|
generating that file updates both the site and reservation engine.
|
|
51
70
|
|
|
71
|
+
The tab icon is `app/icon.svg` (with `public/favicon.ico` for browsers that
|
|
72
|
+
request `/favicon.ico`). Both carry this brand's letter and colors; replace
|
|
73
|
+
them when you rename the brand.
|
|
74
|
+
|
|
52
75
|
## Layout
|
|
53
76
|
|
|
54
77
|
```
|
|
55
78
|
app.ts Reservation + ReservationSlot + User + policies
|
|
56
79
|
lib/site.config.ts ALL copy + brand + menu + seating config
|
|
57
80
|
lib/slots.ts pure seating math, shared by picker + server
|
|
81
|
+
lib/hours.ts weekly hours rows + directions link
|
|
82
|
+
lib/demo.ts demo reservations + the dev-only gate
|
|
83
|
+
functions/seedDemo.ts dev-only demo reservations (see Demo data)
|
|
58
84
|
lib/reservation.ts shared reservation-row types
|
|
59
85
|
lib/owner.ts owner-email gate (PYLON_OWNER_EMAIL)
|
|
60
86
|
functions/createReservation.ts public mutation: capacity re-check + reserve
|
|
61
87
|
functions/reservationsForOwner.ts owner-only query: reservations + guest PII
|
|
62
88
|
functions/{confirm,cancel}Reservation.ts owner-only mutations
|
|
63
|
-
app/page.tsx
|
|
64
|
-
app/reservation-widget.tsx
|
|
89
|
+
app/(marketing)/page.tsx landing (hero + menu + reviews + location)
|
|
90
|
+
app/(marketing)/reservation-widget.tsx client island: live table picker + form
|
|
65
91
|
app/dashboard/ owner dashboard (auth-gated, live)
|
|
66
92
|
```
|
|
67
93
|
|
|
@@ -22,7 +22,9 @@ export default function MarketingLayout({ children, auth }: LayoutProps) {
|
|
|
22
22
|
const { brand } = siteConfig;
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
|
-
|
|
25
|
+
// The ink background sits behind the translucent nav, so the nav reads as
|
|
26
|
+
// dark glass over the page instead of grey over the white body.
|
|
27
|
+
<div className="flex min-h-screen flex-col bg-[var(--ink)]">
|
|
26
28
|
{/* The nav sits over the hero photo: dark, translucent, cream type. */}
|
|
27
29
|
<header className="sticky top-0 z-30 border-b border-white/10 bg-[var(--ink)]/80 text-[var(--cream)] backdrop-blur">
|
|
28
30
|
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-6">
|
|
@@ -57,7 +59,7 @@ export default function MarketingLayout({ children, auth }: LayoutProps) {
|
|
|
57
59
|
<main className="flex-1">{children}</main>
|
|
58
60
|
|
|
59
61
|
<SiteFooter />
|
|
60
|
-
|
|
62
|
+
</div>
|
|
61
63
|
);
|
|
62
64
|
}
|
|
63
65
|
|
|
@@ -3,6 +3,7 @@ import { type Metadata } from "@pylonsync/react";
|
|
|
3
3
|
import { LiveBadge } from "@/components/marketing";
|
|
4
4
|
import { ReservationWidget } from "./reservation-widget";
|
|
5
5
|
import { siteConfig, type DayHours } from "@/lib/site.config";
|
|
6
|
+
import { clock, directionsUrl, hoursRows } from "@/lib/hours";
|
|
6
7
|
|
|
7
8
|
export const metadata: Metadata = {
|
|
8
9
|
title: siteConfig.seo.title,
|
|
@@ -24,7 +25,8 @@ export default function LandingPage() {
|
|
|
24
25
|
return (
|
|
25
26
|
<div className="bg-[var(--ink)] text-[var(--cream)]">
|
|
26
27
|
{/* HERO: the room, full bleed, the words on top. */}
|
|
27
|
-
|
|
28
|
+
{/* -mt-16 runs the photo up under the translucent nav. */}
|
|
29
|
+
<section className="relative isolate -mt-16 min-h-[88vh] overflow-hidden">
|
|
28
30
|
<img src={hero.image} alt="" className="absolute inset-0 -z-20 size-full object-cover" />
|
|
29
31
|
<div className="absolute inset-0 -z-10 bg-gradient-to-t from-[var(--ink)] via-[var(--ink)]/60 to-[var(--ink)]/20" />
|
|
30
32
|
<div className={`${WRAP} flex min-h-[88vh] flex-col justify-end pb-16 pt-32`}>
|
|
@@ -155,29 +157,34 @@ export default function LandingPage() {
|
|
|
155
157
|
|
|
156
158
|
{/* VISIT */}
|
|
157
159
|
<section id="visit" className="border-t border-white/10">
|
|
158
|
-
<div className={`${WRAP} grid gap-10 py-16 lg:grid-cols-2`}>
|
|
159
|
-
<div>
|
|
160
|
+
<div className={`${WRAP} grid grid-cols-[minmax(0,1fr)] gap-10 py-16 lg:grid-cols-2`}>
|
|
161
|
+
<div className="min-w-0">
|
|
160
162
|
<h2 className="font-display text-balance text-[2.25rem] leading-[1.08] sm:text-[3rem]">{location.headline}</h2>
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
<p className="font-display mt-6 max-w-sm text-[1.5rem] leading-snug sm:text-[1.75rem]">{location.address}</p>
|
|
164
|
+
<div className="mt-8 flex flex-wrap items-center gap-3">
|
|
165
|
+
<a
|
|
166
|
+
href={directionsUrl(location.address)}
|
|
167
|
+
target="_blank"
|
|
168
|
+
rel="noreferrer"
|
|
169
|
+
className="inline-flex items-center rounded-full bg-brand px-5 py-2.5 text-[14px] font-medium text-white transition-opacity hover:opacity-90"
|
|
170
|
+
>
|
|
171
|
+
Get directions
|
|
172
|
+
</a>
|
|
173
|
+
<a href={`tel:${location.phone}`} className="inline-flex items-center rounded-full border border-white/25 px-5 py-2.5 text-[14px] font-medium transition-colors hover:bg-white/10">
|
|
174
|
+
Call {location.phone}
|
|
175
|
+
</a>
|
|
170
176
|
</div>
|
|
171
|
-
<
|
|
172
|
-
{
|
|
173
|
-
|
|
177
|
+
<p className="mt-6 text-[14px] text-[var(--cream-2)]">
|
|
178
|
+
Or write to{" "}
|
|
179
|
+
<a href={`mailto:${location.email}`} className="text-[var(--cream)] underline-offset-4 hover:underline">
|
|
180
|
+
{location.email}
|
|
181
|
+
</a>
|
|
182
|
+
</p>
|
|
174
183
|
</div>
|
|
175
184
|
{location.mapEmbedUrl ? (
|
|
176
|
-
<iframe title=
|
|
185
|
+
<iframe title={`Map to ${siteConfig.brand.name}`} src={location.mapEmbedUrl} className="h-80 w-full rounded-[6px] border border-white/10 grayscale" loading="lazy" />
|
|
177
186
|
) : (
|
|
178
|
-
<
|
|
179
|
-
Set <code className="mx-1 font-mono">location.mapEmbedUrl</code> to show a map here
|
|
180
|
-
</div>
|
|
187
|
+
<HoursCard />
|
|
181
188
|
)}
|
|
182
189
|
</div>
|
|
183
190
|
</section>
|
|
@@ -210,8 +217,25 @@ function hoursLabel(h: DayHours): string {
|
|
|
210
217
|
return `${clock(h.open)} – ${clock(h.close)}`;
|
|
211
218
|
}
|
|
212
219
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
220
|
+
// The week's service hours on a cream card, from the same config the
|
|
221
|
+
// reservation calendar reads. Shown in place of a map when
|
|
222
|
+
// `location.mapEmbedUrl` is empty.
|
|
223
|
+
function HoursCard() {
|
|
224
|
+
const rows = hoursRows(siteConfig.reservations.hours);
|
|
225
|
+
return (
|
|
226
|
+
<div className="rounded-[6px] bg-[var(--cream)] p-6 text-[var(--ink)] sm:p-8">
|
|
227
|
+
<h3 className="font-display text-[1.75rem] italic leading-none">Dinner service</h3>
|
|
228
|
+
<dl className="mt-6 divide-y divide-[var(--ink)]/10">
|
|
229
|
+
{rows.map((r) => (
|
|
230
|
+
<div key={r.days} className="flex items-baseline justify-between gap-4 py-3">
|
|
231
|
+
<dt className="text-[15px] font-medium">{r.days}</dt>
|
|
232
|
+
<dd className={"text-[15px] tabular-nums " + (r.closed ? "text-[var(--ink)]/45" : "")}>{r.time}</dd>
|
|
233
|
+
</div>
|
|
234
|
+
))}
|
|
235
|
+
</dl>
|
|
236
|
+
<a href="#reserve" className="mt-6 inline-flex items-center text-[14px] font-medium text-brand underline-offset-4 hover:underline">
|
|
237
|
+
{siteConfig.hero.ctaLabel}
|
|
238
|
+
</a>
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
217
241
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import React, { useMemo, useState } from "react";
|
|
3
|
+
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { db, callFn } from "@pylonsync/react";
|
|
5
5
|
import { EnsureGuest } from "@pylonsync/client";
|
|
6
6
|
import { siteConfig } from "@/lib/site.config";
|
|
@@ -33,13 +33,38 @@ export function ReservationWidget() {
|
|
|
33
33
|
function Picker() {
|
|
34
34
|
const cfg = siteConfig.reservations;
|
|
35
35
|
const { data: markers, loading } = db.useQuery<SlotMarker>("ReservationSlot");
|
|
36
|
+
|
|
37
|
+
// In `pylon dev` an empty book gets demo reservations (functions/seedDemo.ts),
|
|
38
|
+
// which show up here as fewer tables left. The call goes out only when no
|
|
39
|
+
// table is reserved, so a live site with reservations never makes it.
|
|
40
|
+
const empty = !loading && markers.length === 0;
|
|
41
|
+
const seedSent = useRef(false);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!empty || seedSent.current) return;
|
|
44
|
+
seedSent.current = true;
|
|
45
|
+
void callFn("seedDemo", {}).catch(() => {});
|
|
46
|
+
}, [empty]);
|
|
36
47
|
const [nowMs] = useState(() => Date.now());
|
|
37
48
|
|
|
38
49
|
const openDays = useMemo(() => {
|
|
39
50
|
const days: string[] = [];
|
|
40
51
|
for (let i = 0; i < cfg.daysAhead; i++) {
|
|
41
52
|
const key = localDateKey(i, nowMs);
|
|
42
|
-
|
|
53
|
+
const hrs = cfg.hours[weekdayOf(key)];
|
|
54
|
+
if (!hrs) continue;
|
|
55
|
+
// Today drops out once its last seating is too soon to book.
|
|
56
|
+
if (i === 0) {
|
|
57
|
+
const left = seatingsForDay({
|
|
58
|
+
dayISODate: key,
|
|
59
|
+
open: hrs.open,
|
|
60
|
+
close: hrs.close,
|
|
61
|
+
slotMinutes: cfg.slotMinutes,
|
|
62
|
+
leadTimeHours: cfg.leadTimeHours,
|
|
63
|
+
nowMs,
|
|
64
|
+
}).some((s) => !s.past);
|
|
65
|
+
if (!left) continue;
|
|
66
|
+
}
|
|
67
|
+
days.push(key);
|
|
43
68
|
}
|
|
44
69
|
return days;
|
|
45
70
|
}, [cfg.daysAhead, cfg.hours, nowMs]);
|
|
@@ -68,10 +93,13 @@ function Picker() {
|
|
|
68
93
|
slotMinutes: cfg.slotMinutes,
|
|
69
94
|
leadTimeHours: cfg.leadTimeHours,
|
|
70
95
|
nowMs,
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
96
|
+
})
|
|
97
|
+
// Seatings too soon to book are left out, so "Full" only ever means full.
|
|
98
|
+
.filter((s) => !s.past)
|
|
99
|
+
.map((s) => {
|
|
100
|
+
const remaining = Math.max(0, cfg.tablesPerSlot - (takenByTime.get(s.startsAt) ?? 0));
|
|
101
|
+
return { startsAt: s.startsAt, remaining, available: remaining > 0 };
|
|
102
|
+
});
|
|
75
103
|
}, [day, cfg, takenByTime, nowMs]);
|
|
76
104
|
|
|
77
105
|
// Clear an in-progress selection if its seating just filled up (live).
|
|
@@ -142,8 +170,10 @@ function Picker() {
|
|
|
142
170
|
<div className="mt-5 border-t border-zinc-100 pt-5">
|
|
143
171
|
{loading ? (
|
|
144
172
|
<SeatingsSkeleton />
|
|
173
|
+
) : !cfg.hours[weekdayOf(day)] ? (
|
|
174
|
+
<p className="py-6 text-center text-sm text-zinc-500">Closed that day. Pick another.</p>
|
|
145
175
|
) : seatings.length === 0 ? (
|
|
146
|
-
<p className="py-6 text-center text-sm text-zinc-500">
|
|
176
|
+
<p className="py-6 text-center text-sm text-zinc-500">No seatings left that day. Pick another.</p>
|
|
147
177
|
) : (
|
|
148
178
|
<div className="grid grid-cols-3 gap-2 sm:grid-cols-4">
|
|
149
179
|
{seatings.map((seat) => {
|
|
@@ -51,7 +51,7 @@ export function AuthForm() {
|
|
|
51
51
|
<div className="space-y-5">
|
|
52
52
|
<form onSubmit={onSubmit} className="space-y-4">
|
|
53
53
|
<label className="block">
|
|
54
|
-
<span className="mb-1.5 block text-[13px] font-medium text-
|
|
54
|
+
<span className="mb-1.5 block text-[13px] font-medium text-[var(--cream-2)]">Email</span>
|
|
55
55
|
<input
|
|
56
56
|
type="email"
|
|
57
57
|
value={email}
|
|
@@ -59,11 +59,11 @@ export function AuthForm() {
|
|
|
59
59
|
required
|
|
60
60
|
autoComplete="email"
|
|
61
61
|
placeholder="you@yourbusiness.com"
|
|
62
|
-
className="h-
|
|
62
|
+
className="h-11 w-full rounded-[6px] border border-white/15 bg-[var(--ink)] px-3.5 text-[15px] text-[var(--cream)] outline-none transition placeholder:text-[var(--cream-2)]/60 focus:border-brand"
|
|
63
63
|
/>
|
|
64
64
|
</label>
|
|
65
65
|
<label className="block">
|
|
66
|
-
<span className="mb-1.5 block text-[13px] font-medium text-
|
|
66
|
+
<span className="mb-1.5 block text-[13px] font-medium text-[var(--cream-2)]">Password</span>
|
|
67
67
|
<input
|
|
68
68
|
type="password"
|
|
69
69
|
value={password}
|
|
@@ -71,24 +71,24 @@ export function AuthForm() {
|
|
|
71
71
|
required
|
|
72
72
|
autoComplete={mode === "login" ? "current-password" : "new-password"}
|
|
73
73
|
placeholder={mode === "login" ? "Your password" : "At least 10 characters"}
|
|
74
|
-
className="h-
|
|
74
|
+
className="h-11 w-full rounded-[6px] border border-white/15 bg-[var(--ink)] px-3.5 text-[15px] text-[var(--cream)] outline-none transition placeholder:text-[var(--cream-2)]/60 focus:border-brand"
|
|
75
75
|
/>
|
|
76
76
|
</label>
|
|
77
77
|
{error ? (
|
|
78
|
-
<p className="rounded-
|
|
78
|
+
<p className="rounded-[6px] border border-brand/40 bg-brand/10 px-3 py-2 text-[13px] leading-snug text-[var(--cream)]">
|
|
79
79
|
{error}
|
|
80
80
|
</p>
|
|
81
81
|
) : null}
|
|
82
82
|
<button
|
|
83
83
|
type="submit"
|
|
84
84
|
disabled={pending}
|
|
85
|
-
className="inline-flex h-
|
|
85
|
+
className="inline-flex h-11 w-full items-center justify-center rounded-full bg-brand text-[14px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60"
|
|
86
86
|
>
|
|
87
87
|
{pending ? "…" : mode === "login" ? "Sign in" : "Create account"}
|
|
88
88
|
</button>
|
|
89
89
|
</form>
|
|
90
90
|
|
|
91
|
-
<p className="text-center text-[13px] text-
|
|
91
|
+
<p className="text-center text-[13px] text-[var(--cream-2)]">
|
|
92
92
|
{mode === "login" ? "First time here?" : "Already have an account?"}{" "}
|
|
93
93
|
<button
|
|
94
94
|
type="button"
|
|
@@ -96,7 +96,7 @@ export function AuthForm() {
|
|
|
96
96
|
setMode(mode === "login" ? "signup" : "login");
|
|
97
97
|
setError(null);
|
|
98
98
|
}}
|
|
99
|
-
className="font-medium text-
|
|
99
|
+
className="font-medium text-[var(--cream)] underline underline-offset-4"
|
|
100
100
|
>
|
|
101
101
|
{mode === "login" ? "Create the owner account" : "Sign in"}
|
|
102
102
|
</button>
|