@pylonsync/create-pylon 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Demo reservations for local development.
|
|
2
|
+
//
|
|
3
|
+
// `pylon dev` fills an empty reservation book with fictional tables from the
|
|
4
|
+
// past week and the next two weeks, so the calendar shows fewer tables left
|
|
5
|
+
// (and some full seatings) and the owner dashboard has a book to run. Both
|
|
6
|
+
// read the same rows: every active demo Reservation gets its ReservationSlot
|
|
7
|
+
// marker, exactly like createReservation writes.
|
|
8
|
+
//
|
|
9
|
+
// A deploy does not seed: `demoDataEnabled` is true only when PYLON_DEMO_DATA is
|
|
10
|
+
// on, or when it is unset and the process runs under `pylon dev`. Set PYLON_DEMO_DATA=0 to
|
|
11
|
+
// turn it off in development too.
|
|
12
|
+
//
|
|
13
|
+
// Pure data + pure shaping, so functions/seedDemo.ts stays a thin wrapper and
|
|
14
|
+
// this file is testable without a server.
|
|
15
|
+
|
|
16
|
+
import { localDateKey, seatingsForDay, weekdayOf } from "./slots";
|
|
17
|
+
import type { RestaurantConfig } from "./site.config";
|
|
18
|
+
|
|
19
|
+
const ON = new Set(["1", "true", "yes", "on"]);
|
|
20
|
+
const OFF = new Set(["0", "false", "no", "off"]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* True when demo rows may be written. An explicit PYLON_DEMO_DATA wins.
|
|
24
|
+
* Otherwise it needs both PYLON_DEV_MODE on and PYLON_DEV_WATCH_DIR set. Only
|
|
25
|
+
* `pylon dev` sets PYLON_DEV_WATCH_DIR; `pylon start` and the Docker image do
|
|
26
|
+
* not, and the image turns PYLON_DEV_MODE on by default, so dev mode alone is
|
|
27
|
+
* not proof of a local machine.
|
|
28
|
+
*/
|
|
29
|
+
export function demoDataEnabled(env: Record<string, string | undefined>): boolean {
|
|
30
|
+
const explicit = env.PYLON_DEMO_DATA?.trim().toLowerCase();
|
|
31
|
+
if (explicit && ON.has(explicit)) return true;
|
|
32
|
+
if (explicit && OFF.has(explicit)) return false;
|
|
33
|
+
const dev = env.PYLON_DEV_MODE?.trim().toLowerCase();
|
|
34
|
+
const devMode = dev === "1" || dev === "true";
|
|
35
|
+
return devMode && Boolean(env.PYLON_DEV_WATCH_DIR?.trim());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const GUESTS = [
|
|
39
|
+
"Ana Whitaker", "James Osei", "Priya Raman", "Tom Alvarez", "Claire Dubois", "Marcus Iyer",
|
|
40
|
+
"Leah Goldberg", "Noah Fischer", "Sofia Marin", "Daniel Park", "Grace Mensah", "Henry Walsh",
|
|
41
|
+
"Isabel Cruz", "Owen Reilly", "Nadia Haddad", "Ethan Brooks", "Maya Tanaka", "Luca Romano",
|
|
42
|
+
"Zoe Bennett", "Samir Khan", "Ruth Castillo", "Felix Wagner", "Hana Sato", "Diego Vega",
|
|
43
|
+
];
|
|
44
|
+
const NOTES = [
|
|
45
|
+
"Anniversary dinner",
|
|
46
|
+
"One guest is vegetarian",
|
|
47
|
+
"Window table if possible",
|
|
48
|
+
"Celebrating a birthday",
|
|
49
|
+
"Shellfish allergy",
|
|
50
|
+
"Arriving from the theater, may run 10 minutes late",
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/** Small deterministic PRNG (mulberry32) so the demo set is stable per seed. */
|
|
54
|
+
function rng(seed: number): () => number {
|
|
55
|
+
let a = seed >>> 0;
|
|
56
|
+
return () => {
|
|
57
|
+
a = (a + 0x6d2b79f5) >>> 0;
|
|
58
|
+
let t = a;
|
|
59
|
+
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
60
|
+
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
61
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface DemoReservation {
|
|
66
|
+
startsAt: string;
|
|
67
|
+
partySize: number;
|
|
68
|
+
customerName: string;
|
|
69
|
+
customerEmail: string;
|
|
70
|
+
customerPhone: string | null;
|
|
71
|
+
notes: string | null;
|
|
72
|
+
status: "pending" | "confirmed" | "cancelled";
|
|
73
|
+
createdAt: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type Cfg = Pick<RestaurantConfig, "reservations">;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reservations on open days from 7 days ago to 13 days ahead, on the seating
|
|
80
|
+
* grid. Active reservations never exceed `tablesPerSlot` at one seating.
|
|
81
|
+
* Weekend evenings around 7–8 PM fill up; early and late seatings stay light.
|
|
82
|
+
*/
|
|
83
|
+
export function demoReservations(nowMs: number, cfg: Cfg, seed = 5): DemoReservation[] {
|
|
84
|
+
const r = cfg.reservations;
|
|
85
|
+
const rand = rng(seed);
|
|
86
|
+
const out: DemoReservation[] = [];
|
|
87
|
+
|
|
88
|
+
for (let offset = -7; offset <= 13; offset++) {
|
|
89
|
+
const day = localDateKey(offset, nowMs);
|
|
90
|
+
const dow = weekdayOf(day);
|
|
91
|
+
const hours = r.hours[dow];
|
|
92
|
+
if (!hours) continue;
|
|
93
|
+
const seatings = seatingsForDay({
|
|
94
|
+
dayISODate: day,
|
|
95
|
+
open: hours.open,
|
|
96
|
+
close: hours.close,
|
|
97
|
+
slotMinutes: r.slotMinutes,
|
|
98
|
+
leadTimeHours: 0,
|
|
99
|
+
nowMs: 0,
|
|
100
|
+
});
|
|
101
|
+
const weekend = dow === 5 || dow === 6;
|
|
102
|
+
// Nearer dates are more booked than dates two weeks out.
|
|
103
|
+
const horizon = offset <= 3 ? 1 : offset <= 8 ? 0.7 : 0.4;
|
|
104
|
+
for (const seat of seatings) {
|
|
105
|
+
const hour = new Date(seat.startsAt).getHours();
|
|
106
|
+
const peak = hour >= 19 && hour < 21 ? 1 : hour >= 18 && hour < 22 ? 0.6 : 0.3;
|
|
107
|
+
const expected = r.tablesPerSlot * peak * horizon * (weekend ? 0.95 : 0.5);
|
|
108
|
+
let tables = Math.min(r.tablesPerSlot, Math.round(expected + (rand() - 0.4) * 2));
|
|
109
|
+
if (tables < 0) tables = 0;
|
|
110
|
+
const start = Date.parse(seat.startsAt);
|
|
111
|
+
let active = 0;
|
|
112
|
+
for (let t = 0; t < tables + 1 && active < r.tablesPerSlot; t++) {
|
|
113
|
+
// The extra pass may add a cancelled row, which frees its table.
|
|
114
|
+
const extra = t === tables;
|
|
115
|
+
if (extra && rand() > 0.15) break;
|
|
116
|
+
const past = start <= nowMs;
|
|
117
|
+
const roll = rand();
|
|
118
|
+
const status: DemoReservation["status"] = extra
|
|
119
|
+
? "cancelled"
|
|
120
|
+
: past
|
|
121
|
+
? "confirmed"
|
|
122
|
+
: roll < 0.86
|
|
123
|
+
? "confirmed"
|
|
124
|
+
: "pending";
|
|
125
|
+
if (status !== "cancelled") active++;
|
|
126
|
+
const name = GUESTS[Math.floor(rand() * GUESTS.length)];
|
|
127
|
+
const [first, last] = name.toLowerCase().split(" ");
|
|
128
|
+
const party = rand() < 0.55 ? 2 : Math.min(r.maxPartySize, 3 + Math.floor(rand() * 4));
|
|
129
|
+
const bookedAt = Math.min(nowMs - 3_600_000, start - (1 + Math.floor(rand() * 10)) * 86_400_000);
|
|
130
|
+
out.push({
|
|
131
|
+
startsAt: seat.startsAt,
|
|
132
|
+
partySize: party,
|
|
133
|
+
customerName: name,
|
|
134
|
+
customerEmail: `${first}.${last}@example.com`,
|
|
135
|
+
customerPhone: rand() < 0.75 ? `(214) 555-01${String(Math.floor(rand() * 100)).padStart(2, "0")}` : null,
|
|
136
|
+
notes: rand() < 0.18 ? NOTES[Math.floor(rand() * NOTES.length)] : null,
|
|
137
|
+
status,
|
|
138
|
+
createdAt: new Date(bookedAt).toISOString(),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Weekly hours as display rows, from `siteConfig.reservations.hours` (the same
|
|
2
|
+
// hours the reservation calendar uses), so the page cannot disagree with the
|
|
3
|
+
// calendar. Consecutive days with the same hours share a row:
|
|
4
|
+
// "Wed–Thu 5 PM–9:30 PM".
|
|
5
|
+
|
|
6
|
+
import type { DayHours } from "./site.config";
|
|
7
|
+
|
|
8
|
+
const DAY = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
9
|
+
// Rows run Monday to Sunday, the way a door sign lists them.
|
|
10
|
+
const ORDER = [1, 2, 3, 4, 5, 6, 0];
|
|
11
|
+
|
|
12
|
+
export interface HoursRow {
|
|
13
|
+
days: string;
|
|
14
|
+
time: string; // "5 PM–10 PM" or "Closed"
|
|
15
|
+
closed: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** "09:00" → "9 AM", "18:30" → "6:30 PM". */
|
|
19
|
+
export function clock(hhmm: string): string {
|
|
20
|
+
const [h, m] = hhmm.split(":").map(Number);
|
|
21
|
+
const suffix = h >= 12 ? "PM" : "AM";
|
|
22
|
+
const h12 = h % 12 === 0 ? 12 : h % 12;
|
|
23
|
+
return m ? `${h12}:${String(m).padStart(2, "0")} ${suffix}` : `${h12} ${suffix}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function hoursRows(hours: Record<number, DayHours>): HoursRow[] {
|
|
27
|
+
const rows: { first: number; last: number; time: string }[] = [];
|
|
28
|
+
for (const d of ORDER) {
|
|
29
|
+
const h = hours[d];
|
|
30
|
+
const time = h ? `${clock(h.open)}–${clock(h.close)}` : "Closed";
|
|
31
|
+
const prev = rows[rows.length - 1];
|
|
32
|
+
if (prev && prev.time === time) prev.last = d;
|
|
33
|
+
else rows.push({ first: d, last: d, time });
|
|
34
|
+
}
|
|
35
|
+
return rows.map((r) => ({
|
|
36
|
+
days: r.first === r.last ? DAY[r.first] : `${DAY[r.first]}–${DAY[r.last]}`,
|
|
37
|
+
time: r.time,
|
|
38
|
+
closed: r.time === "Closed",
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A Google Maps directions link for a street address. Needs no API key. */
|
|
43
|
+
export function directionsUrl(address: string): string {
|
|
44
|
+
return `https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(address)}`;
|
|
45
|
+
}
|
|
@@ -57,8 +57,9 @@ export type RestaurantConfig = BaseConfig & {
|
|
|
57
57
|
location: {
|
|
58
58
|
headline: string;
|
|
59
59
|
address: string;
|
|
60
|
+
// A Google Maps embed URL (Share → Embed a map → the iframe's src). When
|
|
61
|
+
// empty, the page shows the week's service hours beside the address.
|
|
60
62
|
mapEmbedUrl?: string;
|
|
61
|
-
hoursText: string;
|
|
62
63
|
phone: string;
|
|
63
64
|
email: string;
|
|
64
65
|
};
|
|
@@ -99,7 +100,7 @@ export const siteConfig: RestaurantConfig = {
|
|
|
99
100
|
"Seasonal small plates, wood-fired mains, and a short natural-wine list. The reservation calendar shows current table availability.",
|
|
100
101
|
ctaLabel: "Reserve a table",
|
|
101
102
|
quickFacts: {
|
|
102
|
-
hours: "Wed–Sun
|
|
103
|
+
hours: "Wed–Sun from 5 PM",
|
|
103
104
|
area: "Bishop Arts, Dallas",
|
|
104
105
|
phone: "(214) 555-0172",
|
|
105
106
|
},
|
|
@@ -194,7 +195,6 @@ export const siteConfig: RestaurantConfig = {
|
|
|
194
195
|
headline: "Find us in Bishop Arts.",
|
|
195
196
|
address: "412 N Bishop Ave, Dallas, TX 75208",
|
|
196
197
|
mapEmbedUrl: "",
|
|
197
|
-
hoursText: "Wed–Thu 5–9:30 · Fri–Sat 5–10 · Sun 5–9 · Mon–Tue closed",
|
|
198
198
|
phone: "(214) 555-0172",
|
|
199
199
|
email: "hello@cedarandvine.example",
|
|
200
200
|
},
|
|
Binary file
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { demoDataEnabled, demoReservations } from "../lib/demo";
|
|
3
|
+
import { clock, directionsUrl, hoursRows } from "../lib/hours";
|
|
4
|
+
import { siteConfig } from "../lib/site.config";
|
|
5
|
+
import { weekdayOf } from "../lib/slots";
|
|
6
|
+
|
|
7
|
+
describe("hoursRows", () => {
|
|
8
|
+
test("groups days with the same service hours", () => {
|
|
9
|
+
expect(hoursRows(siteConfig.reservations.hours)).toEqual([
|
|
10
|
+
{ days: "Mon–Tue", time: "Closed", closed: true },
|
|
11
|
+
{ days: "Wed–Thu", time: "5 PM–9:30 PM", closed: false },
|
|
12
|
+
{ days: "Fri–Sat", time: "5 PM–10 PM", closed: false },
|
|
13
|
+
{ days: "Sun", time: "5 PM–9 PM", closed: false },
|
|
14
|
+
]);
|
|
15
|
+
});
|
|
16
|
+
test("clock and directions link", () => {
|
|
17
|
+
expect(clock("21:30")).toBe("9:30 PM");
|
|
18
|
+
expect(directionsUrl("412 N Bishop Ave")).toContain("destination=412%20N%20Bishop%20Ave");
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("demo reservations", () => {
|
|
23
|
+
test("only under `pylon dev` unless PYLON_DEMO_DATA says otherwise", () => {
|
|
24
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "1", PYLON_DEV_WATCH_DIR: "/src/app" })).toBe(true);
|
|
25
|
+
// The Docker base image turns dev mode on by default; without `pylon dev` it must not seed.
|
|
26
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "true" })).toBe(false);
|
|
27
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "false" })).toBe(false);
|
|
28
|
+
expect(demoDataEnabled({})).toBe(false);
|
|
29
|
+
expect(demoDataEnabled({ PYLON_DEV_MODE: "true", PYLON_DEV_WATCH_DIR: "/src/app", PYLON_DEMO_DATA: "off" })).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const now = new Date(2026, 8, 26, 14, 0).getTime();
|
|
33
|
+
const rows = demoReservations(now, siteConfig);
|
|
34
|
+
const cfg = siteConfig.reservations;
|
|
35
|
+
|
|
36
|
+
test("never books more active tables than a seating has", () => {
|
|
37
|
+
const perSeat = new Map<string, number>();
|
|
38
|
+
for (const r of rows) {
|
|
39
|
+
if (r.status === "cancelled") continue;
|
|
40
|
+
perSeat.set(r.startsAt, (perSeat.get(r.startsAt) ?? 0) + 1);
|
|
41
|
+
}
|
|
42
|
+
expect(Math.max(...perSeat.values())).toBeLessThanOrEqual(cfg.tablesPerSlot);
|
|
43
|
+
// Some weekend seatings fill up, so the calendar shows "Full".
|
|
44
|
+
expect([...perSeat.values()].some((n) => n === cfg.tablesPerSlot)).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("only on open days, within the seating window, with valid parties", () => {
|
|
48
|
+
for (const r of rows) {
|
|
49
|
+
const d = new Date(r.startsAt);
|
|
50
|
+
const key = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
51
|
+
const hours = cfg.hours[weekdayOf(key)];
|
|
52
|
+
expect(hours).not.toBeNull();
|
|
53
|
+
const hhmm = `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
54
|
+
expect(hhmm >= hours!.open && hhmm <= hours!.close).toBe(true);
|
|
55
|
+
expect(r.partySize).toBeGreaterThanOrEqual(1);
|
|
56
|
+
expect(r.partySize).toBeLessThanOrEqual(cfg.maxPartySize);
|
|
57
|
+
expect(Date.parse(r.createdAt)).toBeLessThan(now);
|
|
58
|
+
if (r.customerPhone) expect(r.customerPhone).toMatch(/^\(214\) 555-01\d{2}$/);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("past tables are confirmed; upcoming ones include some to confirm", () => {
|
|
63
|
+
const past = rows.filter((r) => Date.parse(r.startsAt) <= now && r.status !== "cancelled");
|
|
64
|
+
expect(past.every((r) => r.status === "confirmed")).toBe(true);
|
|
65
|
+
expect(rows.some((r) => Date.parse(r.startsAt) > now && r.status === "pending")).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
});
|
package/templates/saas/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# __APP_NAME__
|
|
2
2
|
|
|
3
3
|
A full-stack, multi-tenant SaaS starter on [Pylon](https://pylonsync.com),
|
|
4
|
-
branded as the fictional product **Acme
|
|
5
|
-
marketing site, first-run onboarding
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
branded as the fictional product **Acme**: project and task tracking for small
|
|
5
|
+
teams. It includes a server-rendered marketing site, first-run onboarding with
|
|
6
|
+
sample projects, email/password and Google auth, organizations with members
|
|
7
|
+
and roles, tenant-scoped projects and task boards that update live, and
|
|
8
|
+
per-workspace Stripe billing. Pylon serves it from one process.
|
|
8
9
|
|
|
9
10
|
## Develop
|
|
10
11
|
|
|
@@ -12,34 +13,38 @@ Stripe billing. Pylon serves it from one process.
|
|
|
12
13
|
__RUN_DEV__
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
Open http://localhost:4321 to load the Acme landing page. Sign up
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
Open http://localhost:4321 to load the Acme landing page. Sign up, name a
|
|
17
|
+
workspace, and pick **Start with sample projects** to land on a dashboard
|
|
18
|
+
with two projects and 16 tasks. Open a project board in two tabs and change a
|
|
19
|
+
task's status in one: the other updates within a second. Create a second
|
|
20
|
+
workspace and switch between them; each one's data stays private. Editing a
|
|
21
|
+
file under `app/` reloads the page.
|
|
19
22
|
|
|
20
23
|
## Layout
|
|
21
24
|
|
|
22
25
|
```
|
|
23
|
-
app.ts
|
|
24
|
-
app/page.tsx
|
|
25
|
-
app/layout.tsx
|
|
26
|
-
app/{products,solutions,resources,company,compare}/[slug]/ data-driven
|
|
27
|
-
app/
|
|
28
|
-
app/onboarding/
|
|
29
|
-
app/
|
|
30
|
-
app/
|
|
31
|
-
app/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
lib/
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
app.ts User + Org/OrgMember/OrgInvite + Project + Task + Stripe billing manifest
|
|
27
|
+
app/(marketing)/page.tsx "/": the landing page (hero, product sections, quote, pricing, FAQ)
|
|
28
|
+
app/(marketing)/layout.tsx marketing nav + footer
|
|
29
|
+
app/(marketing)/{products,solutions,resources,company,compare}/[slug]/ data-driven pages
|
|
30
|
+
app/(auth)/ /login, /signup, forgot/reset password
|
|
31
|
+
app/onboarding/ first-run wizard: workspace → invite → first project or samples → plan
|
|
32
|
+
app/dashboard/ overview, projects, projects/[id] (task board), members, billing, settings
|
|
33
|
+
app/icon.svg, public/favicon.ico the app icon
|
|
34
|
+
app/{robots,sitemap,llms}.ts /robots.txt, /sitemap.xml, /llms.txt
|
|
35
|
+
functions/ project + task mutations, seedWorkspace, onboarding, profile, Stripe handlers
|
|
36
|
+
lib/site.config.ts every piece of marketing copy, the products, and the colors
|
|
37
|
+
lib/plans.ts prices, the free project cap, the trial length
|
|
38
|
+
lib/tasks.ts task statuses, priorities, validation, ordering
|
|
39
|
+
lib/sample-workspace.ts the sample projects and tasks seedWorkspace adds
|
|
40
|
+
public/screenshots/ dashboard captures the landing page shows
|
|
41
|
+
scripts/capture-screenshots.mjs regenerates public/screenshots from the running app
|
|
42
|
+
components/ dashboard shell, task board pieces, marketing pieces, shadcn primitives
|
|
38
43
|
```
|
|
39
44
|
|
|
40
45
|
## How it works
|
|
41
46
|
|
|
42
|
-
**The landing page** (`app/page.tsx`) is server-rendered React — view source and
|
|
47
|
+
**The landing page** (`app/(marketing)/page.tsx`) is server-rendered React — view source and
|
|
43
48
|
the copy + SEO `<head>` are in the HTML, so it's fully indexable. It reads the
|
|
44
49
|
session during the render, so the call-to-action is "Get started" for visitors
|
|
45
50
|
and "Open dashboard" once you're signed in — no flash, no client fetch.
|
|
@@ -51,25 +56,39 @@ visitors with a real 3xx before any HTML (works with JS off).
|
|
|
51
56
|
**Multi-tenancy** is a framework primitive. Declaring `Org` / `OrgMember` /
|
|
52
57
|
`OrgInvite` lights up `/api/auth/orgs/*` + `/api/auth/select-org`, driven by
|
|
53
58
|
`<OrganizationSwitcher>` from `@pylonsync/client`. Your data lives in
|
|
54
|
-
tenant-scoped entities (`Project`), gated by policy:
|
|
59
|
+
tenant-scoped entities (`Project`, `Task`), gated by policy:
|
|
55
60
|
|
|
56
61
|
```ts
|
|
57
62
|
allowRead: "auth.tenantId == data.orgId"
|
|
58
|
-
allowInsert: "auth.tenantId == data.orgId"
|
|
59
63
|
```
|
|
60
64
|
|
|
61
|
-
So `db.useQuery("Project")` returns only your **active org's** projects
|
|
62
|
-
orgs
|
|
63
|
-
|
|
65
|
+
So `db.useQuery("Project")` returns only your **active org's** projects, and
|
|
66
|
+
switching orgs changes the list. `Task` uses the same read policy.
|
|
67
|
+
|
|
68
|
+
Writes to projects and tasks go through server functions (`createProject`,
|
|
69
|
+
`setProjectStatus`, `deleteProject`, `createTask`, `updateTask`,
|
|
70
|
+
`deleteTask`). Each one loads the row, takes the workspace from the row, and
|
|
71
|
+
calls `ctx.requireMember` for that workspace. The entity policies deny direct
|
|
72
|
+
client writes, and `orgId` is `.readonly()`, so a client cannot move a row into
|
|
73
|
+
another workspace or skip the free plan's project cap.
|
|
64
74
|
|
|
65
75
|
## Make it yours
|
|
66
76
|
|
|
67
|
-
- **Rebrand:**
|
|
68
|
-
|
|
69
|
-
`lib/
|
|
70
|
-
footer, and the `[slug]`
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
- **Rebrand and edit the copy:** everything the marketing site says, including
|
|
78
|
+
the products, the quote, the FAQ, and the colors, lives in
|
|
79
|
+
`lib/site.config.ts`. Edit one entry and the landing page, the nav, the
|
|
80
|
+
footer, and the `[slug]` pages all follow. The quote and the "vs" pages are
|
|
81
|
+
placeholders; replace them or delete them.
|
|
82
|
+
- **Replace the sample data:** edit `lib/sample-workspace.ts`, or remove the
|
|
83
|
+
"Start with sample projects" option in `app/onboarding/onboarding-client.tsx`.
|
|
84
|
+
- **Refresh the screenshots:** after you change the dashboard, run
|
|
85
|
+
`pylon dev`, then `node scripts/capture-screenshots.mjs` (it needs
|
|
86
|
+
Playwright: `npm i -D playwright && npx playwright install chromium`). Use a
|
|
87
|
+
fresh development database so the captures show only the sample workspace.
|
|
88
|
+
- **Add tenant data:** a new `entity()` with an `orgId: field.id("Org").readonly()`
|
|
89
|
+
and the same read policy gives you a tenant-scoped table with a typed client
|
|
90
|
+
and a REST and realtime API. Route its writes through functions that call
|
|
91
|
+
`ctx.requireMember`, as `functions/createTask.ts` does.
|
|
73
92
|
- **Add a route:** drop `app/about/page.tsx` and visit `/about`.
|
|
74
93
|
- **Change prices or the free cap:** edit `lib/plans.ts`. The pricing page, the
|
|
75
94
|
Billing tab, and the server-side cap in `functions/createProject.ts` all read it.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useState } from "react";
|
|
4
4
|
import { Link } from "@pylonsync/react";
|
|
5
|
+
import { Lock, Mail, UserRound } from "lucide-react";
|
|
5
6
|
import {
|
|
6
7
|
passwordLogin,
|
|
7
8
|
passwordRegister,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
// engine's calls). Sign-up then lands on /onboarding, which creates the
|
|
20
21
|
// workspace; sign-in lands on /dashboard.
|
|
21
22
|
export function AuthForm({ mode }: { mode: "login" | "signup" }) {
|
|
23
|
+
const [name, setName] = useState("");
|
|
22
24
|
const [email, setEmail] = useState("");
|
|
23
25
|
const [password, setPassword] = useState("");
|
|
24
26
|
const [code, setCode] = useState("");
|
|
@@ -72,7 +74,11 @@ export function AuthForm({ mode }: { mode: "login" | "signup" }) {
|
|
|
72
74
|
const session =
|
|
73
75
|
mode === "login"
|
|
74
76
|
? await passwordLogin({ email, password })
|
|
75
|
-
: await passwordRegister({
|
|
77
|
+
: await passwordRegister({
|
|
78
|
+
email,
|
|
79
|
+
password,
|
|
80
|
+
...(name.trim() ? { displayName: name.trim() } : {}),
|
|
81
|
+
});
|
|
76
82
|
persistSession(session);
|
|
77
83
|
window.location.assign(destination);
|
|
78
84
|
} catch (err) {
|
|
@@ -84,10 +90,23 @@ export function AuthForm({ mode }: { mode: "login" | "signup" }) {
|
|
|
84
90
|
return (
|
|
85
91
|
<div className="space-y-5">
|
|
86
92
|
<form onSubmit={onSubmit} className="space-y-4">
|
|
93
|
+
{mode === "signup" && method === "password" ? (
|
|
94
|
+
<IconField
|
|
95
|
+
label="Full name"
|
|
96
|
+
type="text"
|
|
97
|
+
icon={<UserRound className="size-[15px]" strokeWidth={1.75} />}
|
|
98
|
+
value={name}
|
|
99
|
+
onChange={setName}
|
|
100
|
+
required
|
|
101
|
+
autoComplete="name"
|
|
102
|
+
maxLength={60}
|
|
103
|
+
placeholder="Dana Reyes"
|
|
104
|
+
/>
|
|
105
|
+
) : null}
|
|
87
106
|
<IconField
|
|
88
107
|
label="Email"
|
|
89
108
|
type="email"
|
|
90
|
-
icon={<
|
|
109
|
+
icon={<Mail className="size-[15px]" strokeWidth={1.75} />}
|
|
91
110
|
value={email}
|
|
92
111
|
onChange={setEmail}
|
|
93
112
|
required
|
|
@@ -99,7 +118,7 @@ export function AuthForm({ mode }: { mode: "login" | "signup" }) {
|
|
|
99
118
|
<IconField
|
|
100
119
|
label="Password"
|
|
101
120
|
type="password"
|
|
102
|
-
icon={<
|
|
121
|
+
icon={<Lock className="size-[15px]" strokeWidth={1.75} />}
|
|
103
122
|
value={password}
|
|
104
123
|
onChange={setPassword}
|
|
105
124
|
required
|
|
@@ -120,7 +139,7 @@ export function AuthForm({ mode }: { mode: "login" | "signup" }) {
|
|
|
120
139
|
<IconField
|
|
121
140
|
label="6-digit code"
|
|
122
141
|
type="text"
|
|
123
|
-
icon={<
|
|
142
|
+
icon={<Lock className="size-[15px]" strokeWidth={1.75} />}
|
|
124
143
|
value={code}
|
|
125
144
|
onChange={(v) => setCode(v.replace(/\D/g, "").slice(0, 6))}
|
|
126
145
|
required
|
|
@@ -258,7 +277,7 @@ function IconField({
|
|
|
258
277
|
<input
|
|
259
278
|
value={value}
|
|
260
279
|
onChange={(e) => onChange(e.target.value)}
|
|
261
|
-
className="h-10 w-full rounded-lg border border-zinc-
|
|
280
|
+
className="h-10 w-full rounded-lg border border-zinc-200 bg-white pl-9 pr-3 text-sm text-zinc-900 shadow-[0_1px_1px_rgba(0,0,0,0.03)] outline-none transition placeholder:text-zinc-400 focus:border-zinc-400 focus:ring-4 focus:ring-zinc-900/5 disabled:bg-zinc-50 disabled:text-zinc-500"
|
|
262
281
|
{...rest}
|
|
263
282
|
/>
|
|
264
283
|
</span>
|
|
@@ -267,24 +286,6 @@ function IconField({
|
|
|
267
286
|
);
|
|
268
287
|
}
|
|
269
288
|
|
|
270
|
-
function MailIcon() {
|
|
271
|
-
return (
|
|
272
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
273
|
-
<rect x="3" y="5" width="18" height="14" rx="2" />
|
|
274
|
-
<path d="m3 7 9 6 9-6" />
|
|
275
|
-
</svg>
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function LockIcon() {
|
|
280
|
-
return (
|
|
281
|
-
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
282
|
-
<rect x="4" y="11" width="16" height="10" rx="2" />
|
|
283
|
-
<path d="M8 11V7a4 4 0 0 1 8 0v4" />
|
|
284
|
-
</svg>
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
289
|
function GitHubIcon() {
|
|
289
290
|
return (
|
|
290
291
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
|
|
@@ -310,6 +311,7 @@ function messageFor(err: unknown): string {
|
|
|
310
311
|
case "INVALID_CREDENTIALS":
|
|
311
312
|
return "Wrong email or password.";
|
|
312
313
|
case "USER_EXISTS":
|
|
314
|
+
case "EMAIL_TAKEN":
|
|
313
315
|
return "That email is already in use — sign in instead.";
|
|
314
316
|
case "WEAK_PASSWORD":
|
|
315
317
|
return "Pick a longer password — at least 10 characters.";
|
|
@@ -10,7 +10,7 @@ export const metadata: Metadata = {
|
|
|
10
10
|
export default function ForgotPasswordPage() {
|
|
11
11
|
return (
|
|
12
12
|
<>
|
|
13
|
-
<h1 className="mt-
|
|
13
|
+
<h1 className="mt-10 text-[26px] font-semibold tracking-[-0.025em] text-zinc-950">Reset your password</h1>
|
|
14
14
|
<p className="mt-1 text-[13px] text-zinc-500">Enter your email and we will send a reset link.</p>
|
|
15
15
|
<div className="mt-6">
|
|
16
16
|
<ForgotPasswordForm />
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Link, type PageAuth, type SsrResponse } from "@pylonsync/react";
|
|
3
|
+
import { siteConfig } from "@/lib/site.config";
|
|
3
4
|
|
|
4
5
|
// `(auth)` route group → the shared split-screen frame for /login and /signup:
|
|
5
|
-
// the form
|
|
6
|
-
//
|
|
6
|
+
// the form on the left, a product screenshot on the right (hidden on small
|
|
7
|
+
// screens). Each page supplies what differs — heading, switch link, and
|
|
7
8
|
// the form — as `children`. The group segment adds no URL prefix, and because
|
|
8
9
|
// these routes sit outside `(marketing)`, none of the site nav/footer renders
|
|
9
10
|
// here.
|
|
@@ -22,46 +23,39 @@ export default function AuthLayout({ children, auth, response }: LayoutProps) {
|
|
|
22
23
|
return null;
|
|
23
24
|
}
|
|
24
25
|
return (
|
|
25
|
-
<div className="grid min-h-screen bg-white lg:grid-cols-
|
|
26
|
+
<div className="grid min-h-screen bg-white lg:grid-cols-[minmax(0,1fr)_minmax(0,1.1fr)]">
|
|
26
27
|
{/* Form side */}
|
|
27
|
-
<div className="flex items-center justify-center px-
|
|
28
|
-
<div className="w-full max-w-[
|
|
29
|
-
<Link href="/" className="inline-flex">
|
|
30
|
-
<span className="flex size-
|
|
31
|
-
|
|
28
|
+
<div className="flex items-center justify-center px-4 py-12 sm:px-6">
|
|
29
|
+
<div className="w-full max-w-[380px]">
|
|
30
|
+
<Link href="/" className="inline-flex items-center gap-2">
|
|
31
|
+
<span className="flex size-8 items-center justify-center rounded-[9px] bg-zinc-950 text-[15px] font-bold text-white">
|
|
32
|
+
{siteConfig.brand.letter}
|
|
32
33
|
</span>
|
|
34
|
+
<span className="text-[16px] font-semibold tracking-tight text-zinc-950">{siteConfig.brand.name}</span>
|
|
33
35
|
</Link>
|
|
34
36
|
{children}
|
|
35
37
|
</div>
|
|
36
38
|
</div>
|
|
37
39
|
|
|
38
|
-
{/*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
</div>
|
|
56
|
-
<div className="text-[13px] text-zinc-500">
|
|
57
|
-
Head of Product, Northwind
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
40
|
+
{/* Product side: a capture of this app's own task board, cropped so it
|
|
41
|
+
runs off the right edge. */}
|
|
42
|
+
<div className="relative hidden overflow-hidden bg-zinc-50 lg:block">
|
|
43
|
+
<div
|
|
44
|
+
aria-hidden
|
|
45
|
+
className="absolute inset-0 bg-[radial-gradient(70%_60%_at_30%_20%,var(--brand-soft),transparent_70%)]"
|
|
46
|
+
/>
|
|
47
|
+
<div className="relative flex h-full flex-col justify-center py-16 pl-14">
|
|
48
|
+
<p className="max-w-md text-[1.5rem] font-semibold leading-snug tracking-[-0.02em] text-zinc-950">
|
|
49
|
+
{siteConfig.hero.headline}
|
|
50
|
+
</p>
|
|
51
|
+
<div className="mt-10 w-[900px] rounded-l-[20px] bg-zinc-950/[0.035] py-2 pl-2 ring-1 ring-zinc-950/[0.06]">
|
|
52
|
+
<img
|
|
53
|
+
src="/screenshots/board.png"
|
|
54
|
+
alt="A project board in Acme"
|
|
55
|
+
className="block w-full rounded-l-[14px] shadow-[0_0_0_1px_rgba(0,0,0,0.06),0_24px_48px_-24px_rgba(24,24,60,0.25)]"
|
|
56
|
+
/>
|
|
60
57
|
</div>
|
|
61
58
|
</div>
|
|
62
|
-
<p className="absolute bottom-8 left-14 text-[13px] text-zinc-400">
|
|
63
|
-
Projects, docs, and automation. All in one place.
|
|
64
|
-
</p>
|
|
65
59
|
</div>
|
|
66
60
|
</div>
|
|
67
61
|
);
|
|
@@ -13,7 +13,7 @@ export const metadata: Metadata = {
|
|
|
13
13
|
export default function LoginPage() {
|
|
14
14
|
return (
|
|
15
15
|
<>
|
|
16
|
-
<h1 className="mt-
|
|
16
|
+
<h1 className="mt-10 text-[26px] font-semibold tracking-[-0.025em] text-zinc-950">
|
|
17
17
|
Welcome back
|
|
18
18
|
</h1>
|
|
19
19
|
<p className="mt-1 text-[13px] text-zinc-500">
|
|
@@ -14,7 +14,7 @@ export default function ResetPasswordPage({ searchParams }: PageProps) {
|
|
|
14
14
|
const token = Array.isArray(raw) ? raw[0] ?? "" : raw ?? "";
|
|
15
15
|
return (
|
|
16
16
|
<>
|
|
17
|
-
<h1 className="mt-
|
|
17
|
+
<h1 className="mt-10 text-[26px] font-semibold tracking-[-0.025em] text-zinc-950">Choose a new password</h1>
|
|
18
18
|
<p className="mt-1 text-[13px] text-zinc-500">You will be signed in on every device after this.</p>
|
|
19
19
|
<div className="mt-6">
|
|
20
20
|
<ResetPasswordForm token={token} />
|