@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
|
@@ -1,21 +1,26 @@
|
|
|
1
|
-
// Business-specific copy and settings live here. The
|
|
2
|
-
// read this file. The product list, including starting stock, seeds the
|
|
3
|
-
// first visit; after that, stock lives in the database and
|
|
1
|
+
// Business-specific copy and settings live here. The storefront pages and
|
|
2
|
+
// layout read this file. The product list, including starting stock, seeds the
|
|
3
|
+
// Product table on first visit; after that, stock lives in the database and
|
|
4
|
+
// updates live.
|
|
4
5
|
//
|
|
5
6
|
// Colors live here (applied as CSS variables on <html> in app/layout.tsx).
|
|
6
7
|
// Fictional demo copy. Replace the values and keep the shape.
|
|
7
8
|
|
|
8
9
|
/* ----------------------------- types ----------------------------- */
|
|
9
10
|
|
|
11
|
+
import type { ShippingTerms } from "./shop";
|
|
12
|
+
|
|
10
13
|
export type Social = { label: string; href: string; path: string };
|
|
11
14
|
|
|
12
15
|
export type BaseConfig = {
|
|
13
16
|
brand: {
|
|
14
17
|
name: string;
|
|
15
|
-
letter: string;
|
|
18
|
+
letter: string; // the monogram on the sign-in and dashboard screens
|
|
16
19
|
domain: string;
|
|
17
20
|
email: string;
|
|
21
|
+
announcement: string;
|
|
18
22
|
footerBlurb: string;
|
|
23
|
+
address: string[];
|
|
19
24
|
copyrightName: string;
|
|
20
25
|
socials: Social[];
|
|
21
26
|
};
|
|
@@ -28,21 +33,29 @@ export type ProductItem = {
|
|
|
28
33
|
name: string;
|
|
29
34
|
priceCents: number;
|
|
30
35
|
description?: string;
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
size?: string; // one short line under the name, e.g. "8 oz, 60-hour burn"
|
|
37
|
+
image: string; // a product photo URL or /public path (4:5 portrait works best)
|
|
38
|
+
stock: number; // STARTING stock, seeded once, then live in the DB
|
|
33
39
|
};
|
|
34
40
|
|
|
35
|
-
export type
|
|
36
|
-
export type Review = { quote: string; name: string;
|
|
41
|
+
export type Fact = { value: string; label: string };
|
|
42
|
+
export type Review = { quote: string; name: string; product?: string };
|
|
37
43
|
export type Policy = { title: string; body: string };
|
|
38
44
|
|
|
39
45
|
export type ShopConfig = BaseConfig & {
|
|
40
|
-
hero: {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
hero: {
|
|
47
|
+
headline: string;
|
|
48
|
+
subcopy: string;
|
|
49
|
+
ctaLabel: string;
|
|
50
|
+
secondaryLabel: string;
|
|
51
|
+
image: string;
|
|
52
|
+
imageAlt: string;
|
|
53
|
+
};
|
|
54
|
+
products: { headline: string; subcopy: string; items: ProductItem[] };
|
|
55
|
+
studio: { headline: string; body: string[]; facts: Fact[] };
|
|
56
|
+
checkout: { shipping: ShippingTerms; reservedMessage: string; paidMessage: string; image: string };
|
|
57
|
+
reviews?: { headline: string; items: Review[] };
|
|
58
|
+
policies: { headline: string; items: Policy[] };
|
|
46
59
|
};
|
|
47
60
|
|
|
48
61
|
/* ----------------------------- config ---------------------------- */
|
|
@@ -53,145 +66,142 @@ export const siteConfig: ShopConfig = {
|
|
|
53
66
|
letter: "E",
|
|
54
67
|
domain: "embergoods.com",
|
|
55
68
|
email: "hello@embergoods.example",
|
|
56
|
-
|
|
57
|
-
|
|
69
|
+
announcement: "Free shipping on orders over $50. Ships from Dallas in two business days.",
|
|
70
|
+
footerBlurb: "Candles poured by hand in small batches in Dallas, Texas.",
|
|
71
|
+
address: ["2809 Elm Street", "Dallas, TX 75226", "Studio open Saturdays, 10 to 4"],
|
|
58
72
|
copyrightName: "Ember Goods",
|
|
59
73
|
socials: [
|
|
60
74
|
{
|
|
61
75
|
label: "Instagram",
|
|
62
76
|
href: "https://instagram.com",
|
|
63
|
-
path: "M12 2.2c3.2 0 3.6 0 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s0 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58 0-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.42-.36-1.06-.41-2.23C2.2 15.58 2.2 15.2 2.2 12s0-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.2 8.8 2.2 12 2.2zm0 1.8c-3.15 0-3.5 0-4.74.07-.9.04-1.38.19-1.7.32-.43.16-.74.36-1.06.68-.32.32-.52.63-.68 1.06-.13.32-.28.8-.32 1.7C3.8 8.5 3.8 8.85 3.8 12s0 3.5.07 4.74c.04.9.19 1.38.32 1.7.16.43.36.74.68 1.06.32.32.63.52 1.06.68.32.13.8.28 1.7.32 1.24.07 1.59.07 4.74.07s3.5 0 4.74-.07c.9-.04 1.38-.19 1.7-.32.43-.16.74-.36 1.06-.68.32-.32.52-.63.68-1.06.13-.32.28-.8.32-1.7.07-1.24.07-1.59.07-4.74s0-3.5-.07-4.74c-.04-.9-.19-1.38-.32-1.7a2.85 2.85 0 0 0-.68-1.06 2.85 2.85 0 0 0-1.06-.68c-.32-.13-.8-.28-1.7-.32C15.5 4 15.15 4 12 4zm0 3.06A4.94 4.94 0 1 0 12 16.94 4.94 4.94 0 0 0 12 7.06zm0 8.15A3.21 3.21 0 1 1 12 8.8a3.21 3.21 0 0 1 0 6.4zm6.3-8.35a1.15 1.15 0 1 1-2.3 0 1.15 1.15 0 0 1 2.3 0z",
|
|
64
|
-
},
|
|
77
|
+
path: "M12 2.2c3.2 0 3.6 0 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s0 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58 0-4.85-.07c-1.17-.05-1.8-.25-2.23-.41a3.7 3.7 0 0 1-1.38-.9 3.7 3.7 0 0 1-.9-1.38c-.16-.42-.36-1.06-.41-2.23C2.2 15.58 2.2 15.2 2.2 12s0-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41C8.42 2.2 8.8 2.2 12 2.2zm0 1.8c-3.15 0-3.5 0-4.74.07-.9.04-1.38.19-1.7.32-.43.16-.74.36-1.06.68-.32.32-.52.63-.68 1.06-.13.32-.28.8-.32 1.7C3.8 8.5 3.8 8.85 3.8 12s0 3.5.07 4.74c.04.9.19 1.38.32 1.7.16.43.36.74.68 1.06.32.32.63.52 1.06.68.32.13.8.28 1.7.32 1.24.07 1.59.07 4.74.07s3.5 0 4.74-.07c.9-.04 1.38-.19 1.7-.32.43-.16.74-.36 1.06-.68.32-.32.52-.63.68-1.06.13-.32.28-.8.32-1.7.07-1.24.07-1.59.07-4.74s0-3.5-.07-4.74c-.04-.9-.19-1.38-.32-1.7a2.85 2.85 0 0 0-.68-1.06 2.85 2.85 0 0 0-1.06-.68c-.32-.13-.8-.28-1.7-.32C15.5 4 15.15 4 12 4zm0 3.06A4.94 4.94 0 1 0 12 16.94 4.94 4.94 0 0 0 12 7.06zm0 8.15A3.21 3.21 0 1 1 12 8.8a3.21 3.21 0 0 1 0 6.4zm6.3-8.35a1.15 1.15 0 1 1-2.3 0 1.15 1.15 0 0 1 2.3 0z", },
|
|
65
78
|
],
|
|
66
79
|
},
|
|
67
80
|
|
|
68
|
-
colors: { brand: "#
|
|
81
|
+
colors: { brand: "#8b4a2b", brandSoft: "#efe3d6", paper: "#e9e1d5" },
|
|
69
82
|
|
|
70
83
|
seo: {
|
|
71
|
-
title: "Ember Goods
|
|
84
|
+
title: "Ember Goods: candles poured by hand in Dallas",
|
|
72
85
|
description:
|
|
73
|
-
"Small-batch candles and
|
|
86
|
+
"Small-batch candles poured in Dallas. Soy and coconut wax, cotton wicks, 60-hour burn. Stock counts match the studio shelf. Free shipping over $50.",
|
|
74
87
|
},
|
|
75
88
|
|
|
76
89
|
hero: {
|
|
77
|
-
|
|
78
|
-
headline: "Hand-poured candles, made to last.",
|
|
90
|
+
headline: "Candles poured by hand in Dallas.",
|
|
79
91
|
subcopy:
|
|
80
|
-
"
|
|
81
|
-
ctaLabel: "Shop
|
|
92
|
+
"Soy and coconut wax, cotton wicks, and scents we blend ourselves. We pour forty at a time, so the stock counts are the real shelf.",
|
|
93
|
+
ctaLabel: "Shop candles",
|
|
94
|
+
secondaryLabel: "See the studio",
|
|
95
|
+
image: "/images/hero.jpg",
|
|
96
|
+
imageAlt: "Three amber glass candles on a travertine shelf, the middle one lit, in late afternoon light.",
|
|
82
97
|
},
|
|
83
98
|
|
|
84
99
|
products: {
|
|
85
|
-
|
|
86
|
-
|
|
100
|
+
headline: "The shelf",
|
|
101
|
+
subcopy: "Stock updates live. When a batch sells out, it is gone until the next pour.",
|
|
87
102
|
items: [
|
|
103
|
+
{
|
|
104
|
+
slug: "tobacco-oak",
|
|
105
|
+
name: "Tobacco & Oak",
|
|
106
|
+
priceCents: 4200,
|
|
107
|
+
description: "Cured tobacco leaf, oak barrel, and amber. Our darkest scent, poured once a season.",
|
|
108
|
+
size: "8 oz, 60-hour burn",
|
|
109
|
+
image: "/images/products/tobacco-oak.jpg",
|
|
110
|
+
stock: 2,
|
|
111
|
+
},
|
|
88
112
|
{
|
|
89
113
|
slug: "cedar-smoke",
|
|
90
114
|
name: "Cedar & Smoke",
|
|
91
|
-
priceCents:
|
|
92
|
-
description: "Cedarwood, smoked vanilla, a little leather.
|
|
93
|
-
|
|
115
|
+
priceCents: 3800,
|
|
116
|
+
description: "Cedarwood, smoked vanilla, and a little leather.",
|
|
117
|
+
size: "8 oz, 60-hour burn",
|
|
118
|
+
image: "/images/products/cedar-smoke.jpg",
|
|
94
119
|
stock: 8,
|
|
95
120
|
},
|
|
96
121
|
{
|
|
97
122
|
slug: "linen",
|
|
98
|
-
name: "
|
|
99
|
-
priceCents:
|
|
100
|
-
description: "Clean cotton
|
|
101
|
-
|
|
123
|
+
name: "Linen",
|
|
124
|
+
priceCents: 3800,
|
|
125
|
+
description: "Clean cotton, white musk, and iris.",
|
|
126
|
+
size: "8 oz, 60-hour burn",
|
|
127
|
+
image: "/images/products/linen.jpg",
|
|
102
128
|
stock: 3,
|
|
103
129
|
},
|
|
104
130
|
{
|
|
105
|
-
slug: "
|
|
106
|
-
name: "
|
|
107
|
-
priceCents:
|
|
108
|
-
description: "
|
|
109
|
-
|
|
110
|
-
|
|
131
|
+
slug: "fig-leaf",
|
|
132
|
+
name: "Fig Leaf",
|
|
133
|
+
priceCents: 3800,
|
|
134
|
+
description: "Green fig, cedar, and coconut.",
|
|
135
|
+
size: "8 oz, 60-hour burn",
|
|
136
|
+
image: "/images/products/fig-leaf.jpg",
|
|
137
|
+
stock: 11,
|
|
111
138
|
},
|
|
112
139
|
{
|
|
113
|
-
slug: "
|
|
114
|
-
name: "
|
|
115
|
-
priceCents:
|
|
116
|
-
description: "
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
{
|
|
121
|
-
slug: "wick-trimmer",
|
|
122
|
-
name: "Wick trimmer",
|
|
123
|
-
priceCents: 1800,
|
|
124
|
-
description: "The one tool that makes a candle last. Matte black.",
|
|
125
|
-
image: "✂️",
|
|
126
|
-
stock: 0,
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
slug: "gift-set",
|
|
130
|
-
name: "The trio gift set",
|
|
131
|
-
priceCents: 8400,
|
|
132
|
-
description: "Three minis, boxed and ready to give.",
|
|
133
|
-
image: "🎁",
|
|
134
|
-
stock: 6,
|
|
140
|
+
slug: "sea-salt",
|
|
141
|
+
name: "Sea Salt & Sage",
|
|
142
|
+
priceCents: 3800,
|
|
143
|
+
description: "Salt air, sage, and driftwood.",
|
|
144
|
+
size: "8 oz, 60-hour burn",
|
|
145
|
+
image: "/images/products/sea-salt.jpg",
|
|
146
|
+
stock: 9,
|
|
135
147
|
},
|
|
136
148
|
],
|
|
137
149
|
},
|
|
138
150
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
studio: {
|
|
152
|
+
headline: "Poured in small batches, cured for two weeks.",
|
|
153
|
+
body: [
|
|
154
|
+
"Every candle is poured, cured, and trimmed by hand in our studio in Deep Ellum. We use a soy and coconut wax blend, cotton wicks, and phthalate-free fragrance oils.",
|
|
155
|
+
"Each batch cures for fourteen days before it goes on the shelf, so the scent carries across the room from the first burn.",
|
|
156
|
+
],
|
|
157
|
+
facts: [
|
|
158
|
+
{ value: "40", label: "candles per batch" },
|
|
159
|
+
{ value: "14 days", label: "cure before sale" },
|
|
160
|
+
{ value: "60 hr", label: "burn time, 8 oz" },
|
|
161
|
+
],
|
|
142
162
|
},
|
|
143
163
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
body: "Every candle is poured, wicked, and labelled by hand in our Dallas studio.",
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
icon: "◇",
|
|
155
|
-
title: "Real small batch",
|
|
156
|
-
body: "We make what we can do well. When the shelf says 3 left, there are 3 left.",
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
icon: "✦",
|
|
160
|
-
title: "Free shipping over $50",
|
|
161
|
-
body: "Carbon-neutral shipping, plastic-free packaging, and easy returns.",
|
|
162
|
-
},
|
|
163
|
-
],
|
|
164
|
+
checkout: {
|
|
165
|
+
shipping: { flatCents: 600, freeOverCents: 5000 },
|
|
166
|
+
reservedMessage:
|
|
167
|
+
"We are holding your items and will email a payment link within one business day. Orders ship from Dallas within two business days of payment.",
|
|
168
|
+
paidMessage:
|
|
169
|
+
"Your payment went through and the receipt is on its way to your inbox. Orders ship from Dallas within two business days.",
|
|
170
|
+
image: "/images/products/tobacco-oak.jpg", // shown on /success
|
|
164
171
|
},
|
|
165
172
|
|
|
166
173
|
reviews: {
|
|
167
|
-
|
|
168
|
-
headline: "What people say.",
|
|
174
|
+
headline: "Reviews",
|
|
169
175
|
items: [
|
|
170
176
|
{
|
|
171
|
-
quote: "Cedar & Smoke
|
|
177
|
+
quote: "Cedar & Smoke fills the whole living room without being loud about it. On my third one.",
|
|
172
178
|
name: "Maya C.",
|
|
173
|
-
|
|
179
|
+
product: "Cedar & Smoke",
|
|
174
180
|
},
|
|
175
181
|
{
|
|
176
|
-
quote: "
|
|
182
|
+
quote: "Bought Linen for my mom. It arrived in two days, packed in paper, no plastic anywhere.",
|
|
177
183
|
name: "Daniel R.",
|
|
178
|
-
|
|
184
|
+
product: "Linen",
|
|
179
185
|
},
|
|
180
186
|
{
|
|
181
|
-
quote: "
|
|
187
|
+
quote: "Fig Leaf burned evenly all the way to the bottom of the jar. I kept the jar for pens.",
|
|
182
188
|
name: "Hannah K.",
|
|
183
|
-
|
|
189
|
+
product: "Fig Leaf",
|
|
184
190
|
},
|
|
185
191
|
],
|
|
186
192
|
},
|
|
187
193
|
|
|
188
194
|
policies: {
|
|
189
|
-
|
|
190
|
-
headline: "Shipping & returns.",
|
|
195
|
+
headline: "Shipping and returns",
|
|
191
196
|
items: [
|
|
192
|
-
{ title: "Shipping", body: "Ships in
|
|
193
|
-
{ title: "Returns", body: "
|
|
194
|
-
{ title: "
|
|
197
|
+
{ title: "Shipping", body: "Ships from Dallas in two business days. Free over $50, $6 flat otherwise." },
|
|
198
|
+
{ title: "Returns", body: "Return unused and unburned items within 30 days for a full refund." },
|
|
199
|
+
{ title: "Local pickup", body: "Pick up at the studio on Saturdays. Reply to your order email to switch to pickup." },
|
|
200
|
+
{ title: "Wholesale", body: "Email us for the line sheet and wholesale pricing." },
|
|
195
201
|
],
|
|
196
202
|
},
|
|
197
203
|
};
|
|
204
|
+
|
|
205
|
+
export function productBySlug(slug: string): ProductItem | undefined {
|
|
206
|
+
return siteConfig.products.items.find((p) => p.slug === slug);
|
|
207
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { formatPrice, freeShippingRemaining, shippingCents, stockStatus } from "../lib/shop";
|
|
5
|
+
import { productBySlug, siteConfig } from "../lib/site.config";
|
|
6
|
+
|
|
7
|
+
const terms = { flatCents: 600, freeOverCents: 5000 };
|
|
8
|
+
|
|
9
|
+
describe("stockStatus", () => {
|
|
10
|
+
test("zero or less is sold out", () => {
|
|
11
|
+
expect(stockStatus(0)).toEqual({ kind: "sold_out", label: "Sold out" });
|
|
12
|
+
expect(stockStatus(-2).kind).toBe("sold_out");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("three or fewer is low", () => {
|
|
16
|
+
expect(stockStatus(1)).toEqual({ kind: "low", label: "Last one" });
|
|
17
|
+
expect(stockStatus(3)).toEqual({ kind: "low", label: "Only 3 left" });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("more than three shows the count", () => {
|
|
21
|
+
expect(stockStatus(4)).toEqual({ kind: "in_stock", label: "4 in stock" });
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("shipping", () => {
|
|
26
|
+
test("flat rate below the threshold", () => {
|
|
27
|
+
expect(shippingCents(4999, terms)).toBe(600);
|
|
28
|
+
expect(freeShippingRemaining(3800, terms)).toBe(1200);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("free at and above the threshold", () => {
|
|
32
|
+
expect(shippingCents(5000, terms)).toBe(0);
|
|
33
|
+
expect(freeShippingRemaining(8400, terms)).toBe(0);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("formatPrice drops zero cents", () => {
|
|
38
|
+
expect(formatPrice(3800)).toBe("$38");
|
|
39
|
+
expect(formatPrice(1850)).toBe("$18.50");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("catalog config", () => {
|
|
43
|
+
const items = siteConfig.products.items;
|
|
44
|
+
|
|
45
|
+
test("slugs are unique", () => {
|
|
46
|
+
expect(new Set(items.map((p) => p.slug)).size).toBe(items.length);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("productBySlug finds a product and misses an unknown slug", () => {
|
|
50
|
+
expect(productBySlug(items[0].slug)?.name).toBe(items[0].name);
|
|
51
|
+
expect(productBySlug("nope")).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Every local image the storefront references must ship in public/.
|
|
55
|
+
test("every local image exists in public/", () => {
|
|
56
|
+
const paths = [
|
|
57
|
+
siteConfig.hero.image,
|
|
58
|
+
siteConfig.checkout.image,
|
|
59
|
+
...items.map((p) => p.image),
|
|
60
|
+
].filter((src) => src.startsWith("/"));
|
|
61
|
+
const missing = paths.filter((src) => !existsSync(join(import.meta.dir, "..", "public", src)));
|
|
62
|
+
expect(missing).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -10,3 +10,10 @@ PYLON_OWNER_EMAIL=you@yourbusiness.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://yourbusiness.com
|
|
13
|
+
|
|
14
|
+
# ── Demo data ────────────────────────────────────────────────────────────────
|
|
15
|
+
# `pylon dev` fills an empty waitlist with fictional signups. Seeding runs only
|
|
16
|
+
# under `pylon dev` (it checks PYLON_DEV_MODE and PYLON_DEV_WATCH_DIR), so
|
|
17
|
+
# `pylon start`, Docker, and Pylon Cloud deploys never seed. Set 0 to start
|
|
18
|
+
# empty in development. Never set 1 on a deploy.
|
|
19
|
+
# PYLON_DEMO_DATA=0
|
|
@@ -18,14 +18,24 @@ increment in another without refreshing.
|
|
|
18
18
|
## How the realtime works
|
|
19
19
|
|
|
20
20
|
- `functions/joinWaitlist.ts`: a public **mutation** that validates, lowercases,
|
|
21
|
-
and dedupes the email,
|
|
22
|
-
|
|
23
|
-
- `
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
and dedupes the email, inserts one `Signup` row, and rewrites the PII-free
|
|
22
|
+
`WaitlistStat` row with the new count.
|
|
23
|
+
- The landing page reads `WaitlistStat` with `db.useQuery`. The row syncs to
|
|
24
|
+
every open tab, so the counter moves as soon as anyone joins. No polling.
|
|
25
|
+
- The counter island (`app/(marketing)/waitlist-hero.tsx`) is wrapped in
|
|
26
|
+
`<EnsureGuest>`, which mints an anonymous session so the live connection can
|
|
27
|
+
open.
|
|
28
|
+
|
|
29
|
+
The counter is the `Signup` table's row count. `counter.importedCount` in
|
|
30
|
+
`lib/site.config.ts` adds signups you collected somewhere else before this site
|
|
31
|
+
(an old form, a spreadsheet). It ships as 0.
|
|
32
|
+
|
|
33
|
+
## Demo data
|
|
34
|
+
|
|
35
|
+
In `pylon dev`, an empty waitlist gets about 180 fictional signups from the last
|
|
36
|
+
30 days, so the counter, chart, and list have data (`functions/seedDemo.ts`,
|
|
37
|
+
`lib/demo.ts`). Seeding runs only when `PYLON_DEMO_DATA` is on, or when it is unset and the app runs under `pylon dev`, and only while the `Signup` table is empty. `pylon start`, Docker, and Pylon Cloud deploys do not seed, because only `pylon dev` sets `PYLON_DEV_WATCH_DIR`. Set `PYLON_DEMO_DATA=0` to start
|
|
38
|
+
empty in development. Delete both files once you have real signups.
|
|
29
39
|
|
|
30
40
|
## Privacy
|
|
31
41
|
|
|
@@ -47,22 +57,28 @@ account can see signups; anyone else gets a locked screen.
|
|
|
47
57
|
|
|
48
58
|
## Rebrand it
|
|
49
59
|
|
|
50
|
-
Brand, colors, hero copy,
|
|
60
|
+
Brand, colors, hero copy, the launch date, value props, and FAQ content live in
|
|
51
61
|
**`lib/site.config.ts`**. Edit or generate that file to update the page without
|
|
52
62
|
changing JSX or CSS.
|
|
53
63
|
|
|
64
|
+
The tab icon is `app/icon.svg` (with `public/favicon.ico` for browsers that
|
|
65
|
+
request `/favicon.ico`). Both carry this brand's letter and colors; replace
|
|
66
|
+
them when you rename the brand.
|
|
67
|
+
|
|
54
68
|
## Layout
|
|
55
69
|
|
|
56
70
|
```
|
|
57
71
|
app.ts data model + manifest (Signup, User, policies, auth)
|
|
58
72
|
lib/site.config.ts ALL business copy + brand + colors (edit this)
|
|
59
73
|
lib/owner.ts owner-email gate (PYLON_OWNER_EMAIL)
|
|
60
|
-
lib/stats.ts
|
|
74
|
+
lib/stats.ts dashboard types + the public count
|
|
75
|
+
lib/launch.ts the launch line from hero.launchDate
|
|
76
|
+
lib/demo.ts demo signups + the dev-only gate
|
|
61
77
|
functions/joinWaitlist.ts public mutation: validate + dedupe + insert
|
|
62
|
-
functions/
|
|
78
|
+
functions/seedDemo.ts dev-only demo signups (see Demo data)
|
|
63
79
|
functions/waitlistStats.ts owner-only reactive query: total + chart + list
|
|
64
|
-
app/page.tsx
|
|
65
|
-
app/waitlist-hero.tsx
|
|
80
|
+
app/(marketing)/page.tsx the landing page (server-rendered)
|
|
81
|
+
app/(marketing)/waitlist-hero.tsx client island: signup form + live counter
|
|
66
82
|
app/login/page.tsx owner sign-in
|
|
67
83
|
app/dashboard/ owner dashboard (auth-gated, live)
|
|
68
84
|
app/globals.css Tailwind entrypoint (compiled by Pylon)
|
|
@@ -3,6 +3,7 @@ import { type Metadata } from "@pylonsync/react";
|
|
|
3
3
|
import { WRAP, FactList, ProductMock } from "@/components/marketing";
|
|
4
4
|
import { SignupForm, LiveCount } from "./waitlist-hero";
|
|
5
5
|
import { siteConfig } from "@/lib/site.config";
|
|
6
|
+
import { launchLabel } from "@/lib/launch";
|
|
6
7
|
|
|
7
8
|
// SEO metadata: server-rendered into <head>, so the page is fully indexable.
|
|
8
9
|
// All copy lives in lib/site.config.ts; edit it there to rebrand.
|
|
@@ -25,6 +26,7 @@ export const metadata: Metadata = {
|
|
|
25
26
|
// Signed-in state (the nav's "Dashboard" link) is resolved in the layout.
|
|
26
27
|
export default function LandingPage() {
|
|
27
28
|
const { hero, counter, mock, facts, faq } = siteConfig;
|
|
29
|
+
const launch = launchLabel(hero.launchDate, Date.now());
|
|
28
30
|
|
|
29
31
|
return (
|
|
30
32
|
<div>
|
|
@@ -36,9 +38,14 @@ export default function LandingPage() {
|
|
|
36
38
|
</h1>
|
|
37
39
|
<p className="mt-6 max-w-md text-[17px] leading-relaxed text-chalk-2">{hero.subcopy}</p>
|
|
38
40
|
<div className="mt-8 max-w-md">
|
|
39
|
-
<SignupForm hero={hero} />
|
|
41
|
+
<SignupForm hero={hero} showCountNote={counter.enabled} />
|
|
42
|
+
</div>
|
|
43
|
+
<div className="mt-8 flex flex-col gap-2 border-t border-line pt-5">
|
|
44
|
+
{counter.enabled ? (
|
|
45
|
+
<LiveCount importedCount={counter.importedCount} label={counter.label} labelOne={counter.labelOne} />
|
|
46
|
+
) : null}
|
|
47
|
+
{launch ? <p className="font-mono-ui text-[13px] text-brand">{launch}</p> : null}
|
|
40
48
|
</div>
|
|
41
|
-
<p className="font-mono-ui mt-6 text-[13px] text-brand">{hero.launchNote}</p>
|
|
42
49
|
</div>
|
|
43
50
|
<ProductMock
|
|
44
51
|
windowTitle={mock.windowTitle}
|
|
@@ -56,11 +63,6 @@ export default function LandingPage() {
|
|
|
56
63
|
<div className="mt-8">
|
|
57
64
|
<FactList items={facts.items} />
|
|
58
65
|
</div>
|
|
59
|
-
{counter.enabled ? (
|
|
60
|
-
<div className="mt-12 border-t border-line pt-5">
|
|
61
|
-
<LiveCount seed={counter.seedCount ?? 0} label={counter.label} />
|
|
62
|
-
</div>
|
|
63
|
-
) : null}
|
|
64
66
|
</section>
|
|
65
67
|
|
|
66
68
|
{/* ============================== FAQ ============================= */}
|
|
@@ -4,9 +4,10 @@ import React, { useEffect, useRef, useState } from "react";
|
|
|
4
4
|
import { db, callFn } from "@pylonsync/react";
|
|
5
5
|
import { EnsureGuest } from "@pylonsync/client";
|
|
6
6
|
import type { WaitlistConfig } from "@/lib/site.config";
|
|
7
|
+
import { publicCount } from "@/lib/stats";
|
|
7
8
|
|
|
8
9
|
// The two client islands on the landing page: the email-capture form (in the
|
|
9
|
-
// hero) and the LIVE signup count (under the
|
|
10
|
+
// hero) and the LIVE signup count (in the hero, under the form). The count is a live
|
|
10
11
|
// `db.useQuery("WaitlistStat")` over the public, PII-free aggregate row, so
|
|
11
12
|
// the moment anyone (this tab or another) submits an email, joinWaitlist
|
|
12
13
|
// updates that row and the new count syncs to every open tab through the
|
|
@@ -20,7 +21,13 @@ import type { WaitlistConfig } from "@/lib/site.config";
|
|
|
20
21
|
|
|
21
22
|
/* ----------------------------- signup form ---------------------------- */
|
|
22
23
|
|
|
23
|
-
export function SignupForm({
|
|
24
|
+
export function SignupForm({
|
|
25
|
+
hero,
|
|
26
|
+
showCountNote,
|
|
27
|
+
}: {
|
|
28
|
+
hero: WaitlistConfig["hero"];
|
|
29
|
+
showCountNote: boolean;
|
|
30
|
+
}) {
|
|
24
31
|
const [email, setEmail] = useState("");
|
|
25
32
|
const [status, setStatus] = useState<"idle" | "sending" | "done">("idle");
|
|
26
33
|
const [alreadyJoined, setAlreadyJoined] = useState(false);
|
|
@@ -57,9 +64,11 @@ export function SignupForm({ hero }: { hero: WaitlistConfig["hero"] }) {
|
|
|
57
64
|
<p className="text-[15px] font-medium text-chalk">
|
|
58
65
|
{alreadyJoined ? "You are already on the list." : hero.successMessage}
|
|
59
66
|
</p>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
{!alreadyJoined && showCountNote ? (
|
|
68
|
+
<p className="mt-1 font-mono-ui text-[12px] text-chalk-2">
|
|
69
|
+
The count below now includes you.
|
|
70
|
+
</p>
|
|
71
|
+
) : null}
|
|
63
72
|
</div>
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -76,7 +85,7 @@ export function SignupForm({ hero }: { hero: WaitlistConfig["hero"] }) {
|
|
|
76
85
|
placeholder={hero.emailPlaceholder}
|
|
77
86
|
aria-label="Email address"
|
|
78
87
|
required
|
|
79
|
-
className="h-11
|
|
88
|
+
className="h-11 w-full min-w-0 rounded-md border border-line bg-paper sm:flex-1 px-3.5 text-[15px] text-chalk outline-none transition placeholder:text-chalk-2 focus:border-brand"
|
|
80
89
|
/>
|
|
81
90
|
<button
|
|
82
91
|
type="submit"
|
|
@@ -101,44 +110,68 @@ interface WaitlistStatRow {
|
|
|
101
110
|
updatedAt: string;
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
export function LiveCount({
|
|
113
|
+
export function LiveCount({
|
|
114
|
+
importedCount,
|
|
115
|
+
label,
|
|
116
|
+
labelOne,
|
|
117
|
+
}: {
|
|
118
|
+
importedCount: number;
|
|
119
|
+
label: string;
|
|
120
|
+
labelOne: string;
|
|
121
|
+
}) {
|
|
105
122
|
return (
|
|
106
|
-
<EnsureGuest fallback={
|
|
107
|
-
<LiveCountInner
|
|
123
|
+
<EnsureGuest fallback={null}>
|
|
124
|
+
<LiveCountInner importedCount={importedCount} label={label} labelOne={labelOne} />
|
|
108
125
|
</EnsureGuest>
|
|
109
126
|
);
|
|
110
127
|
}
|
|
111
128
|
|
|
112
|
-
function LiveCountInner({
|
|
113
|
-
|
|
114
|
-
// the instant the row changes, in THIS tab and every other open tab, because
|
|
115
|
-
// the change syncs through the shared replica. `loading` is true until the
|
|
116
|
-
// first sync settles; until then we show the seed (never a flash of 0).
|
|
117
|
-
const { data, loading } = db.useQuery<WaitlistStatRow>("WaitlistStat");
|
|
118
|
-
const real = data.length > 0 ? data[0].count : 0;
|
|
119
|
-
const value = seed + real;
|
|
120
|
-
return <CountView value={value} label={label} live={!loading} />;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function CountView({
|
|
124
|
-
value,
|
|
129
|
+
function LiveCountInner({
|
|
130
|
+
importedCount,
|
|
125
131
|
label,
|
|
126
|
-
|
|
132
|
+
labelOne,
|
|
127
133
|
}: {
|
|
128
|
-
|
|
134
|
+
importedCount: number;
|
|
129
135
|
label: string;
|
|
130
|
-
|
|
136
|
+
labelOne: string;
|
|
131
137
|
}) {
|
|
138
|
+
// Live entity query over the public WaitlistStat row. `db.useQuery` re-renders
|
|
139
|
+
// the instant the row changes, in this tab and every other open tab, because
|
|
140
|
+
// the change syncs through the shared replica.
|
|
141
|
+
const { data, loading } = db.useQuery<WaitlistStatRow>("WaitlistStat");
|
|
142
|
+
|
|
143
|
+
// In `pylon dev` an empty waitlist gets demo signups (functions/seedDemo.ts).
|
|
144
|
+
// The call goes out only when the synced list is empty, so a live site with
|
|
145
|
+
// signups never makes it.
|
|
146
|
+
useSeedWhenEmpty(!loading && data.length === 0);
|
|
147
|
+
|
|
148
|
+
// Nothing renders until the first sync settles or while the list is empty,
|
|
149
|
+
// so the page never shows a placeholder number or "0 people on the list".
|
|
150
|
+
if (loading) return null;
|
|
151
|
+
const value = publicCount(data, importedCount);
|
|
152
|
+
if (value === 0) return null;
|
|
153
|
+
return <CountView value={value} label={value === 1 ? labelOne : label} />;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function useSeedWhenEmpty(empty: boolean) {
|
|
157
|
+
const sent = useRef(false);
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
if (!empty || sent.current) return;
|
|
160
|
+
sent.current = true;
|
|
161
|
+
void callFn("seedDemo", {}).catch(() => {});
|
|
162
|
+
}, [empty]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function CountView({ value, label }: { value: number; label: string }) {
|
|
132
166
|
const shown = useCountUp(value);
|
|
133
167
|
return (
|
|
134
168
|
<p className="flex items-center gap-3 font-mono-ui text-[14px] text-chalk-2">
|
|
135
|
-
<span
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
<span className="tabular-nums text-chalk">{shown.toLocaleString()}</span>
|
|
169
|
+
<span aria-hidden className="size-2 rounded-full bg-brand" />
|
|
170
|
+
<span className="tabular-nums text-chalk" data-testid="live-count">
|
|
171
|
+
{shown.toLocaleString("en-US")}
|
|
172
|
+
</span>
|
|
140
173
|
<span>{label}</span>
|
|
141
|
-
<span className="text-[12px]">
|
|
174
|
+
<span className="text-[12px]">live</span>
|
|
142
175
|
</p>
|
|
143
176
|
);
|
|
144
177
|
}
|