@pylonsync/create-pylon 0.19.1 → 0.21.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/_root/AGENTS.md +1 -1
- package/templates/agency/.env.example +7 -0
- package/templates/agency/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/ai-studio/AGENTS.md +1 -1
- 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/barebones/AGENTS.md +1 -1
- package/templates/chat/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/AGENTS.md +1 -1
- 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/todo/AGENTS.md +1 -1
- package/templates/waitlist/.env.example +7 -0
- package/templates/waitlist/AGENTS.md +1 -1
- 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,112 +1,117 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Link } from "@pylonsync/react";
|
|
3
|
-
import {
|
|
3
|
+
import { LogOut, Search } from "lucide-react";
|
|
4
4
|
import { cn } from "@/lib/utils";
|
|
5
|
+
import { BRAND } from "@/lib/brand";
|
|
6
|
+
import { NAV } from "@/components/nav";
|
|
5
7
|
import { Avatar } from "@/components/avatar";
|
|
8
|
+
import { BrandMark } from "@/components/brand-mark";
|
|
6
9
|
import { Kbd } from "@/components/kbd";
|
|
7
10
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
count?: number;
|
|
11
|
+
/** True when `href` is the section `pathname` belongs to. */
|
|
12
|
+
export function isActive(href: string, pathname: string): boolean {
|
|
13
|
+
if (href === "/") return pathname === "/";
|
|
14
|
+
return pathname === href || pathname.startsWith(`${href}/`);
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
export const NAV: Array<Omit<NavItem, "count">> = [
|
|
16
|
-
{ href: "/", label: "Pipeline", icon: <LayoutGrid /> },
|
|
17
|
-
{ href: "/companies", label: "Companies", icon: <Building2 /> },
|
|
18
|
-
{ href: "/contacts", label: "Contacts", icon: <Users /> },
|
|
19
|
-
];
|
|
20
|
-
|
|
21
17
|
/**
|
|
22
|
-
* The
|
|
23
|
-
*
|
|
18
|
+
* The navigation rail: brand, search, links with counts, and the signed-in
|
|
19
|
+
* person. The same component renders as the fixed rail on wide screens and
|
|
20
|
+
* inside the drawer on phones (see components/app-shell.tsx).
|
|
21
|
+
*
|
|
22
|
+
* Presentational: the path, counts, and handlers arrive as props, so it
|
|
23
|
+
* renders the same in a test.
|
|
24
24
|
*/
|
|
25
25
|
export function Sidebar({
|
|
26
|
-
|
|
27
|
-
email,
|
|
26
|
+
user,
|
|
28
27
|
pathname,
|
|
29
28
|
counts,
|
|
30
29
|
onOpenCommand,
|
|
30
|
+
onNavigate,
|
|
31
31
|
onSignOut,
|
|
32
|
+
className,
|
|
32
33
|
}: {
|
|
33
|
-
|
|
34
|
-
email: string;
|
|
34
|
+
user: { name: string; email: string };
|
|
35
35
|
pathname: string;
|
|
36
|
+
/** Badge per nav href. Omit a key to show no number. */
|
|
36
37
|
counts?: Record<string, number>;
|
|
37
38
|
onOpenCommand: () => void;
|
|
39
|
+
/** Called after a link is chosen, so the phone drawer can close. */
|
|
40
|
+
onNavigate?: () => void;
|
|
38
41
|
onSignOut: () => void;
|
|
42
|
+
className?: string;
|
|
39
43
|
}) {
|
|
40
44
|
return (
|
|
41
|
-
<
|
|
42
|
-
<div className="flex h-
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
+
<div className={cn("flex h-full w-full flex-col bg-surface-1", className)}>
|
|
46
|
+
<div className="flex h-13 shrink-0 items-center gap-2.5 px-4">
|
|
47
|
+
<BrandMark />
|
|
48
|
+
<span className="truncate text-[14px] font-semibold tracking-[-0.01em]">
|
|
49
|
+
{BRAND.name}
|
|
45
50
|
</span>
|
|
46
|
-
<span className="truncate text-[13px] font-semibold">{workspace}</span>
|
|
47
51
|
</div>
|
|
48
52
|
|
|
49
|
-
<div className="px-
|
|
53
|
+
<div className="px-3 pb-3">
|
|
50
54
|
<button
|
|
51
55
|
type="button"
|
|
52
56
|
onClick={onOpenCommand}
|
|
53
57
|
className={cn(
|
|
54
|
-
"flex w-full items-center gap-2 rounded-md border border-border bg-background px-2
|
|
55
|
-
"text-[
|
|
58
|
+
"flex h-8 w-full items-center gap-2 rounded-md border border-border bg-background px-2.5",
|
|
59
|
+
"text-[12.5px] text-muted-foreground shadow-[0_1px_1px_rgb(0_0_0/0.03)] transition-colors",
|
|
60
|
+
"hover:border-foreground/15 hover:text-foreground",
|
|
56
61
|
)}
|
|
57
62
|
>
|
|
58
|
-
<
|
|
59
|
-
<span className="flex-1 text-left">Search
|
|
60
|
-
<Kbd>⌘K</Kbd>
|
|
63
|
+
<Search className="size-3.5" />
|
|
64
|
+
<span className="flex-1 text-left">Search</span>
|
|
65
|
+
<Kbd className="max-md:hidden">⌘K</Kbd>
|
|
61
66
|
</button>
|
|
62
67
|
</div>
|
|
63
68
|
|
|
64
|
-
<nav className="flex-1 space-y-
|
|
69
|
+
<nav className="flex-1 space-y-px overflow-y-auto px-3" aria-label="Main">
|
|
65
70
|
{NAV.map((item) => {
|
|
66
|
-
const active =
|
|
67
|
-
|
|
71
|
+
const active = isActive(item.href, pathname);
|
|
72
|
+
const count = counts?.[item.href];
|
|
68
73
|
return (
|
|
69
74
|
<Link
|
|
70
75
|
key={item.href}
|
|
71
76
|
href={item.href}
|
|
77
|
+
onClick={onNavigate}
|
|
72
78
|
aria-current={active ? "page" : undefined}
|
|
73
79
|
className={cn(
|
|
74
|
-
"flex h-
|
|
75
|
-
"[&_svg]:size-
|
|
80
|
+
"flex h-8 items-center gap-2.5 rounded-md px-2.5 text-[13px] transition-colors md:h-7.5",
|
|
81
|
+
"[&_svg]:size-4 [&_svg]:shrink-0",
|
|
76
82
|
active
|
|
77
|
-
? "bg-
|
|
78
|
-
: "text-muted-foreground hover:bg-
|
|
83
|
+
? "bg-foreground/[0.06] font-medium text-foreground [&_svg]:text-primary"
|
|
84
|
+
: "text-muted-foreground hover:bg-foreground/[0.04] hover:text-foreground",
|
|
79
85
|
)}
|
|
80
86
|
>
|
|
81
87
|
{item.icon}
|
|
82
88
|
<span className="flex-1 truncate">{item.label}</span>
|
|
83
|
-
{
|
|
84
|
-
<span className="tabular text-[
|
|
85
|
-
{counts[item.href]}
|
|
86
|
-
</span>
|
|
89
|
+
{typeof count === "number" ? (
|
|
90
|
+
<span className="tabular text-[11.5px] text-muted-foreground">{count}</span>
|
|
87
91
|
) : null}
|
|
88
92
|
</Link>
|
|
89
93
|
);
|
|
90
94
|
})}
|
|
91
95
|
</nav>
|
|
92
96
|
|
|
93
|
-
<div className="border-t border-border p-
|
|
94
|
-
<div className="flex items-center gap-2
|
|
95
|
-
<Avatar name={email}
|
|
96
|
-
<
|
|
97
|
-
{
|
|
98
|
-
|
|
97
|
+
<div className="border-t border-border p-3">
|
|
98
|
+
<div className="flex items-center gap-2.5">
|
|
99
|
+
<Avatar name={user.name || user.email} />
|
|
100
|
+
<div className="min-w-0 flex-1 leading-tight">
|
|
101
|
+
<p className="truncate text-[12.5px] font-medium">{user.name || "Signed in"}</p>
|
|
102
|
+
<p className="truncate text-[11.5px] text-muted-foreground">{user.email}</p>
|
|
103
|
+
</div>
|
|
99
104
|
<button
|
|
100
105
|
type="button"
|
|
101
106
|
onClick={onSignOut}
|
|
102
107
|
title="Sign out"
|
|
103
108
|
aria-label="Sign out"
|
|
104
|
-
className="rounded p-1 text-muted-foreground transition-colors hover:bg-
|
|
109
|
+
className="rounded-md p-1.5 text-muted-foreground transition-colors hover:bg-foreground/[0.06] hover:text-foreground"
|
|
105
110
|
>
|
|
106
|
-
<LogOut className="size-
|
|
111
|
+
<LogOut className="size-4" />
|
|
107
112
|
</button>
|
|
108
113
|
</div>
|
|
109
114
|
</div>
|
|
110
|
-
</
|
|
115
|
+
</div>
|
|
111
116
|
);
|
|
112
117
|
}
|
|
@@ -2,10 +2,12 @@ import React from "react";
|
|
|
2
2
|
import { cn } from "@/lib/utils";
|
|
3
3
|
import { stageById } from "@/lib/pipeline";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/** The colour dot for each stage, shared by the badge and the board columns. */
|
|
6
|
+
export const STAGE_DOT: Record<string, string> = {
|
|
6
7
|
lead: "bg-stage-lead",
|
|
7
8
|
qualified: "bg-stage-qualified",
|
|
8
9
|
proposal: "bg-stage-proposal",
|
|
10
|
+
negotiation: "bg-stage-negotiation",
|
|
9
11
|
won: "bg-stage-won",
|
|
10
12
|
lost: "bg-stage-lost",
|
|
11
13
|
};
|
|
@@ -25,7 +27,7 @@ export function StageBadge({
|
|
|
25
27
|
return (
|
|
26
28
|
<span className={cn("inline-flex items-center gap-1.5 whitespace-nowrap", className)}>
|
|
27
29
|
<span
|
|
28
|
-
className={cn("size-1.5 shrink-0 rounded-full",
|
|
30
|
+
className={cn("size-1.5 shrink-0 rounded-full", STAGE_DOT[stage] ?? "bg-muted-foreground")}
|
|
29
31
|
/>
|
|
30
32
|
<span className="text-muted-foreground">{meta?.label ?? stage}</span>
|
|
31
33
|
</span>
|
|
@@ -5,15 +5,16 @@ import { cva, type VariantProps } from "class-variance-authority";
|
|
|
5
5
|
import { cn } from "@/lib/utils";
|
|
6
6
|
|
|
7
7
|
const buttonVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-
|
|
8
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-[13px] font-medium transition-[color,background-color,box-shadow,transform] duration-150 active:scale-[0.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
12
|
-
default:
|
|
12
|
+
default:
|
|
13
|
+
"bg-primary text-primary-foreground shadow-[inset_0_1px_0_rgb(255_255_255/0.15),0_1px_2px_rgb(0_0_0/0.1)] hover:bg-primary/90",
|
|
13
14
|
destructive:
|
|
14
15
|
"bg-destructive text-white hover:bg-destructive/90",
|
|
15
16
|
outline:
|
|
16
|
-
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
17
|
+
"border border-input bg-background shadow-[0_1px_1px_rgb(0_0_0/0.03)] hover:bg-accent hover:text-accent-foreground",
|
|
17
18
|
secondary:
|
|
18
19
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
19
20
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
@@ -21,7 +22,7 @@ const buttonVariants = cva(
|
|
|
21
22
|
},
|
|
22
23
|
size: {
|
|
23
24
|
default: "h-9 px-4 py-2",
|
|
24
|
-
sm: "h-8 rounded-md px-3 text-
|
|
25
|
+
sm: "h-8 rounded-md px-3 text-[12.5px] md:h-7.5 [&_svg]:size-3.5",
|
|
25
26
|
lg: "h-10 rounded-md px-8",
|
|
26
27
|
icon: "h-9 w-9",
|
|
27
28
|
},
|
|
@@ -35,7 +35,7 @@ const DialogContent = React.forwardRef<
|
|
|
35
35
|
<DialogPrimitive.Content
|
|
36
36
|
ref={ref}
|
|
37
37
|
className={cn(
|
|
38
|
-
"fixed left-[50%] top-[50%] z-50 grid w-
|
|
38
|
+
"fixed left-[50%] top-[50%] z-50 grid max-h-[calc(100dvh-2rem)] w-[calc(100%-2rem)] max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-xl border bg-background p-5 shadow-[0_24px_48px_-12px_rgb(0_0_0/0.25)] duration-200 sm:p-6",
|
|
39
39
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
40
40
|
className,
|
|
41
41
|
)}
|
|
@@ -12,7 +12,7 @@ function Input({
|
|
|
12
12
|
type={type}
|
|
13
13
|
data-slot="input"
|
|
14
14
|
className={cn(
|
|
15
|
-
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-
|
|
15
|
+
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm md:text-[13px] transition-colors",
|
|
16
16
|
"placeholder:text-muted-foreground",
|
|
17
17
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
18
18
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
@@ -22,7 +22,7 @@ function Select({
|
|
|
22
22
|
<select
|
|
23
23
|
data-slot="select"
|
|
24
24
|
className={cn(
|
|
25
|
-
"flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-
|
|
25
|
+
"flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-base shadow-sm md:text-[13px] transition-colors",
|
|
26
26
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
27
27
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
28
28
|
className,
|
|
@@ -10,7 +10,7 @@ function Textarea({
|
|
|
10
10
|
<textarea
|
|
11
11
|
data-slot="textarea"
|
|
12
12
|
className={cn(
|
|
13
|
-
"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-
|
|
13
|
+
"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-sm md:text-[13px] transition-colors",
|
|
14
14
|
"placeholder:text-muted-foreground",
|
|
15
15
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
16
16
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { mutation } from "@pylonsync/functions";
|
|
2
|
-
import { shapeSeed } from "../lib/seed";
|
|
2
|
+
import { SEED_TEAM, shapeSeed } from "../lib/seed";
|
|
3
|
+
import { ensureDemoUser } from "../lib/demo-team";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Fill a brand-new workspace with a realistic pipeline, once.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* The client calls this after sign-in. It returns immediately if any company
|
|
9
|
+
* already exists, so it is safe to call on every load and never duplicates the
|
|
10
|
+
* fixtures or touches real data. An advisory lock stops two first loads from
|
|
11
|
+
* seeding twice.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
+
* Deals are spread across the person who signed in and four demo teammates
|
|
14
|
+
* (see SEED_TEAM and lib/demo-team.ts). Delete this function,
|
|
15
|
+
* lib/seed.ts, and the `seedWorkspace` call in app/workspace.tsx once the
|
|
16
|
+
* workspace has real customers, then delete the demo users.
|
|
13
17
|
*/
|
|
14
18
|
export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
15
19
|
auth: "user",
|
|
@@ -17,44 +21,55 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
|
17
21
|
async handler(ctx) {
|
|
18
22
|
// The guard is "has anything at all", not "has the seed", so a workspace
|
|
19
23
|
// whose demo rows were deleted on purpose stays deleted.
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
if ((await ctx.db.query("Company", { $limit: 1 })).length > 0) return { seeded: false };
|
|
25
|
+
await ctx.db.advisoryLock("crm_seed_workspace");
|
|
26
|
+
if ((await ctx.db.query("Company", { $limit: 1 })).length > 0) return { seeded: false };
|
|
22
27
|
|
|
23
28
|
const seed = shapeSeed();
|
|
24
|
-
const
|
|
29
|
+
const me = ctx.auth.userId;
|
|
30
|
+
|
|
31
|
+
// Position 0 is the person who signed in; SEED_TEAM follows.
|
|
32
|
+
const owners: Array<string | null> = [me];
|
|
33
|
+
for (const person of SEED_TEAM) owners.push(await ensureDemoUser(ctx.db, person));
|
|
25
34
|
|
|
26
35
|
const companyIds = new Map<string, string>();
|
|
27
|
-
for (const company of seed.companies) {
|
|
28
|
-
const id = await ctx.db.insert("Company", {
|
|
36
|
+
for (const [index, company] of seed.companies.entries()) {
|
|
37
|
+
const id = await ctx.db.insert("Company", {
|
|
38
|
+
...company.row,
|
|
39
|
+
ownerId: owners[index % owners.length],
|
|
40
|
+
});
|
|
29
41
|
companyIds.set(company.key, id as string);
|
|
30
42
|
}
|
|
31
43
|
|
|
32
44
|
const contactIds = new Map<string, string>();
|
|
33
|
-
for (const contact of seed.contacts) {
|
|
45
|
+
for (const [index, contact] of seed.contacts.entries()) {
|
|
34
46
|
const id = await ctx.db.insert("Contact", {
|
|
35
47
|
...contact.row,
|
|
36
48
|
companyId: companyIds.get(contact.company) ?? null,
|
|
37
|
-
ownerId:
|
|
49
|
+
ownerId: owners[index % owners.length],
|
|
38
50
|
});
|
|
39
51
|
contactIds.set(String(contact.row.name), id as string);
|
|
40
52
|
}
|
|
41
53
|
|
|
42
|
-
const dealIds = new Map<string, string>();
|
|
54
|
+
const dealIds = new Map<string, { id: string; owner: string | null }>();
|
|
43
55
|
for (const deal of seed.deals) {
|
|
56
|
+
const owner = owners[deal.owner] ?? me;
|
|
44
57
|
const id = await ctx.db.insert("Deal", {
|
|
45
58
|
...deal.row,
|
|
46
59
|
companyId: companyIds.get(deal.company) ?? null,
|
|
47
60
|
contactId: contactIds.get(deal.contact) ?? null,
|
|
48
61
|
ownerId: owner,
|
|
49
62
|
});
|
|
50
|
-
dealIds.set(deal.title, id as string);
|
|
63
|
+
dealIds.set(deal.title, { id: id as string, owner });
|
|
51
64
|
}
|
|
52
65
|
|
|
66
|
+
// Activity on a deal is logged by the deal's owner.
|
|
53
67
|
for (const activity of seed.activities) {
|
|
68
|
+
const deal = dealIds.get(activity.deal);
|
|
54
69
|
await ctx.db.insert("Activity", {
|
|
55
70
|
...activity.row,
|
|
56
|
-
dealId:
|
|
57
|
-
ownerId: owner,
|
|
71
|
+
dealId: deal?.id ?? null,
|
|
72
|
+
ownerId: deal?.owner ?? me,
|
|
58
73
|
});
|
|
59
74
|
}
|
|
60
75
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The product name, in one place. The sidebar, the sign-in screen, the page
|
|
2
|
+
// title, and the drawer on phones all read it. Rename the product here; the
|
|
3
|
+
// mark is in components/brand-mark.tsx and the favicon is app/icon.svg.
|
|
4
|
+
|
|
5
|
+
export const BRAND = {
|
|
6
|
+
name: "Dealbook",
|
|
7
|
+
/** One line on the sign-in screen: what the app holds and who sees it. */
|
|
8
|
+
description: "Your team's pipeline, companies, and contacts. Everyone on the team sees the same board.",
|
|
9
|
+
} as const;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Creates the demo teammates the seed spreads records across. Each is a User
|
|
2
|
+
// row with a `@demo.invalid` address and no password. Outside dev mode nobody
|
|
3
|
+
// can sign in as one: the address can't receive a sign-in or reset email.
|
|
4
|
+
// In dev mode (`PYLON_DEV_MODE=true`) the auth API returns sign-in codes
|
|
5
|
+
// directly, so anyone can sign in as a demo teammate there.
|
|
6
|
+
|
|
7
|
+
import type { DbWriter } from "@pylonsync/functions";
|
|
8
|
+
|
|
9
|
+
export interface DemoPerson {
|
|
10
|
+
name: string;
|
|
11
|
+
email: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The address to try on `attempt` (0, 1, 2, …): the person's own address
|
|
16
|
+
* first, then `local+2@demo.invalid`, `local+3@…`, and so on.
|
|
17
|
+
*/
|
|
18
|
+
export function demoEmail(email: string, attempt: number): string {
|
|
19
|
+
if (attempt === 0) return email;
|
|
20
|
+
const at = email.indexOf("@");
|
|
21
|
+
return `${email.slice(0, at)}+${attempt + 1}${email.slice(at)}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A User row the seed may reuse: one it created itself on an earlier run.
|
|
26
|
+
* A row with a password or a verified email belongs to someone who
|
|
27
|
+
* registered that address, so the seed never assigns demo records to it.
|
|
28
|
+
*/
|
|
29
|
+
export function isDemoRow(row: Record<string, unknown>): boolean {
|
|
30
|
+
return !row.passwordHash && !row.emailVerified;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Returns the id of a demo User row for `person`, creating it if needed. */
|
|
34
|
+
export async function ensureDemoUser(db: DbWriter, person: DemoPerson): Promise<string> {
|
|
35
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
36
|
+
const email = demoEmail(person.email, attempt);
|
|
37
|
+
// Users are written by auth, and their policy denies client inserts, so
|
|
38
|
+
// demo rows go through the server-trust surface. The read sees
|
|
39
|
+
// passwordHash, which is how a registered account is told apart.
|
|
40
|
+
const [existing] = await db.unsafe.query("User", { email, $limit: 1 });
|
|
41
|
+
if (!existing) {
|
|
42
|
+
return db.unsafe.insert("User", {
|
|
43
|
+
email,
|
|
44
|
+
displayName: person.name,
|
|
45
|
+
createdAt: new Date(Date.now() - 200 * 86_400_000).toISOString(),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (isDemoRow(existing)) return existing.id as string;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`No free demo address for ${person.email}`);
|
|
51
|
+
}
|
|
@@ -17,6 +17,7 @@ export const PIPELINE: Stage[] = [
|
|
|
17
17
|
{ id: "lead", label: "Lead", probability: 0.1 },
|
|
18
18
|
{ id: "qualified", label: "Qualified", probability: 0.3 },
|
|
19
19
|
{ id: "proposal", label: "Proposal", probability: 0.6 },
|
|
20
|
+
{ id: "negotiation", label: "Negotiation", probability: 0.8 },
|
|
20
21
|
{ id: "won", label: "Won", closed: "won", probability: 1 },
|
|
21
22
|
{ id: "lost", label: "Lost", closed: "lost", probability: 0 },
|
|
22
23
|
];
|
|
@@ -219,3 +220,39 @@ export function daysUntil(
|
|
|
219
220
|
};
|
|
220
221
|
return Math.round((startOfDay(then) - startOfDay(now)) / 86_400_000);
|
|
221
222
|
}
|
|
223
|
+
|
|
224
|
+
export type CloseDue =
|
|
225
|
+
| { tone: "overdue"; days: number; label: string }
|
|
226
|
+
| { tone: "soon"; days: number; label: string };
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The close-date warning for a deal card: "3d overdue", "Today", or "5d".
|
|
230
|
+
*
|
|
231
|
+
* Only open deals get one. A won or lost deal has closed, so its expected
|
|
232
|
+
* close date is history, not a deadline. Beyond a week out there is nothing
|
|
233
|
+
* to act on yet, so that returns null too.
|
|
234
|
+
*/
|
|
235
|
+
export function closeDue(
|
|
236
|
+
deal: { stage: string; closeDate?: string | null },
|
|
237
|
+
now: number = Date.now(),
|
|
238
|
+
): CloseDue | null {
|
|
239
|
+
if (!isOpen(deal)) return null;
|
|
240
|
+
const days = daysUntil(deal.closeDate, now);
|
|
241
|
+
if (days === null || days > 7) return null;
|
|
242
|
+
if (days < 0) return { tone: "overdue", days, label: `${Math.abs(days)}d overdue` };
|
|
243
|
+
return { tone: "soon", days, label: days === 0 ? "Today" : `${days}d` };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** "Mar 3" for this year, "Mar 3, 2027" otherwise. */
|
|
247
|
+
export function shortDate(iso: string | null | undefined, now: number = Date.now()): string {
|
|
248
|
+
if (!iso) return "";
|
|
249
|
+
const then = Date.parse(iso);
|
|
250
|
+
if (!Number.isFinite(then)) return "";
|
|
251
|
+
const date = new Date(then);
|
|
252
|
+
const sameYear = date.getFullYear() === new Date(now).getFullYear();
|
|
253
|
+
return date.toLocaleDateString("en-US", {
|
|
254
|
+
month: "short",
|
|
255
|
+
day: "numeric",
|
|
256
|
+
...(sameYear ? {} : { year: "numeric" }),
|
|
257
|
+
});
|
|
258
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
// Client-side search powering the ⌘K palette.
|
|
2
2
|
//
|
|
3
|
-
// A small team's
|
|
3
|
+
// A small team's workspace fits in the synced replica, so search runs locally against
|
|
4
4
|
// data already in memory: results appear as you type, with no request per
|
|
5
5
|
// keystroke. Swap in `db.useSearch` (Pylon's FTS) when a workspace outgrows
|
|
6
6
|
// that — the component boundary doesn't change, only this file.
|
|
7
7
|
|
|
8
8
|
export interface SearchItem {
|
|
9
9
|
id: string;
|
|
10
|
-
|
|
10
|
+
/** The record kind, such as "deal". The palette picks its icon from this. */
|
|
11
|
+
type: string;
|
|
11
12
|
title: string;
|
|
12
|
-
/**
|
|
13
|
+
/** Shown on the right of the row: the company for a contact, say. */
|
|
13
14
|
subtitle?: string;
|
|
14
15
|
href: string;
|
|
15
16
|
/** Extra text matched but not displayed — an email, say. */
|
|
@@ -7,6 +7,37 @@
|
|
|
7
7
|
// Pure data + pure shaping, so the seeding function stays a thin wrapper and
|
|
8
8
|
// this file can be edited (or emptied) without reading any server code.
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* The demo sales team. Each becomes a User row with no
|
|
12
|
+
* password (see lib/demo-team.ts); they exist so deals have realistic owners.
|
|
13
|
+
*/
|
|
14
|
+
export interface SeedTeammate {
|
|
15
|
+
name: string;
|
|
16
|
+
email: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const SEED_TEAM: SeedTeammate[] = [
|
|
20
|
+
{ name: "Maya Castillo", email: "maya.castillo@demo.invalid" },
|
|
21
|
+
{ name: "Jordan Ellis", email: "jordan.ellis@demo.invalid" },
|
|
22
|
+
{ name: "Aisha Karim", email: "aisha.karim@demo.invalid" },
|
|
23
|
+
{ name: "Noah Brandt", email: "noah.brandt@demo.invalid" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Who owns the seeded deal at `index`, as a position in `[you, ...SEED_TEAM]`.
|
|
28
|
+
* Stepping by 3 through a team of 5 spreads every stage across everyone,
|
|
29
|
+
* including the person who signed up.
|
|
30
|
+
*/
|
|
31
|
+
export function seedOwner(index: number, teamSize: number = SEED_TEAM.length + 1): number {
|
|
32
|
+
return (index * 3 + 1) % teamSize;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A fictional US number in the 555-01xx range reserved for fiction. */
|
|
36
|
+
export function seedPhone(index: number): string {
|
|
37
|
+
const area = [212, 312, 415, 512, 617, 720][index % 6];
|
|
38
|
+
return `+1 (${area}) 555-01${String(index % 100).padStart(2, "0")}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
10
41
|
export interface SeedCompany {
|
|
11
42
|
key: string;
|
|
12
43
|
name: string;
|
|
@@ -131,14 +162,14 @@ export const SEED_DEALS: SeedDeal[] = [
|
|
|
131
162
|
{ company: "vantage", contact: "Anita Rao", title: "Installer scheduling", value: 26500, stage: "qualified", closeInDays: 38, age: 14 },
|
|
132
163
|
{ company: "tidewater", contact: "Sarah Lindqvist", title: "Shop floor work orders", value: 41000, stage: "qualified", closeInDays: 33, age: 19 },
|
|
133
164
|
|
|
134
|
-
// Proposal
|
|
165
|
+
// Proposal and negotiation
|
|
135
166
|
{ company: "northwind", contact: "Dana Whitfield", title: "Fleet dispatch rollout", value: 48000, stage: "proposal", closeInDays: 12, age: 34 },
|
|
136
167
|
{ company: "riverbed", contact: "Tom Alvarez", title: "Multi-store inventory", value: 22000, stage: "proposal", closeInDays: 5, age: 29 },
|
|
137
|
-
{ company: "meridian", contact: "Rachel Stone", title: "Customer tracking portal", value: 88000, stage: "
|
|
168
|
+
{ company: "meridian", contact: "Rachel Stone", title: "Customer tracking portal", value: 88000, stage: "negotiation", closeInDays: 9, age: 41 },
|
|
138
169
|
{ company: "bluefin", contact: "Elena Vasquez", title: "Embedded reporting module", value: 36000, stage: "proposal", closeInDays: 14, age: 27 },
|
|
139
|
-
{ company: "harbor", contact: "Robert Chen", title: "Guest messaging platform", value: 67000, stage: "
|
|
140
|
-
{ company: "halcyon", contact: "Margaret O'Neill", title: "Care plan tracking", value: 52000, stage: "
|
|
141
|
-
{ company: "summit", contact: "Greg Holloway", title: "Security review tooling", value: 18000, stage: "
|
|
170
|
+
{ company: "harbor", contact: "Robert Chen", title: "Guest messaging platform", value: 67000, stage: "negotiation", closeInDays: 6, age: 38 },
|
|
171
|
+
{ company: "halcyon", contact: "Margaret O'Neill", title: "Care plan tracking", value: 52000, stage: "negotiation", closeInDays: 13, age: 31 },
|
|
172
|
+
{ company: "summit", contact: "Greg Holloway", title: "Security review tooling", value: 18000, stage: "negotiation", closeInDays: -2, age: 24 },
|
|
142
173
|
{ company: "sable", contact: "Isabel Moreau", title: "Loyalty programme", value: 11200, stage: "proposal", closeInDays: 10, age: 26 },
|
|
143
174
|
|
|
144
175
|
// Won
|
|
@@ -208,7 +239,14 @@ export const SEED_ACTIVITIES: SeedActivity[] = [
|
|
|
208
239
|
export interface ShapedSeed {
|
|
209
240
|
companies: Array<{ key: string; row: Record<string, unknown> }>;
|
|
210
241
|
contacts: Array<{ company: string; row: Record<string, unknown> }>;
|
|
211
|
-
|
|
242
|
+
/** `owner` is a position in `[you, ...SEED_TEAM]`; see seedOwner. */
|
|
243
|
+
deals: Array<{
|
|
244
|
+
company: string;
|
|
245
|
+
contact: string;
|
|
246
|
+
title: string;
|
|
247
|
+
owner: number;
|
|
248
|
+
row: Record<string, unknown>;
|
|
249
|
+
}>;
|
|
212
250
|
activities: Array<{ deal: string; row: Record<string, unknown> }>;
|
|
213
251
|
}
|
|
214
252
|
|
|
@@ -239,12 +277,19 @@ export function shapeSeed(now: number = Date.now()): ShapedSeed {
|
|
|
239
277
|
})),
|
|
240
278
|
contacts: SEED_CONTACTS.map((c, index) => ({
|
|
241
279
|
company: c.company,
|
|
242
|
-
row: {
|
|
280
|
+
row: {
|
|
281
|
+
name: c.name,
|
|
282
|
+
email: c.email,
|
|
283
|
+
title: c.title,
|
|
284
|
+
phone: seedPhone(index),
|
|
285
|
+
createdAt: at(-110 + index * 2),
|
|
286
|
+
},
|
|
243
287
|
})),
|
|
244
|
-
deals: SEED_DEALS.map((d) => ({
|
|
288
|
+
deals: SEED_DEALS.map((d, index) => ({
|
|
245
289
|
company: d.company,
|
|
246
290
|
contact: d.contact,
|
|
247
291
|
title: d.title,
|
|
292
|
+
owner: seedOwner(index),
|
|
248
293
|
row: {
|
|
249
294
|
title: d.title,
|
|
250
295
|
value: d.value,
|
|
Binary file
|
|
@@ -31,7 +31,7 @@ describe("PipelineBoard", () => {
|
|
|
31
31
|
onOpen={() => {}}
|
|
32
32
|
/>,
|
|
33
33
|
);
|
|
34
|
-
for (const label of ["Lead", "Qualified", "Proposal", "Won"]) {
|
|
34
|
+
for (const label of ["Lead", "Qualified", "Proposal", "Negotiation", "Won"]) {
|
|
35
35
|
expect(screen.getByLabelText(label)).toBeDefined();
|
|
36
36
|
}
|
|
37
37
|
// Lost is real in the model but deliberately not a column.
|
|
@@ -181,7 +181,7 @@ describe("CommandPalette", () => {
|
|
|
181
181
|
render(
|
|
182
182
|
<CommandPalette open items={items} onClose={() => {}} onSelect={() => {}} />,
|
|
183
183
|
);
|
|
184
|
-
fireEvent.change(screen.getByLabelText(
|
|
184
|
+
fireEvent.change(screen.getByLabelText(/^search/i), {
|
|
185
185
|
target: { value: "acme" },
|
|
186
186
|
});
|
|
187
187
|
expect(screen.getByText("Acme Inc")).toBeDefined();
|
|
@@ -201,7 +201,7 @@ describe("CommandPalette", () => {
|
|
|
201
201
|
onSelect={(item) => selected.push(item.id)}
|
|
202
202
|
/>,
|
|
203
203
|
);
|
|
204
|
-
const input = screen.getByLabelText(
|
|
204
|
+
const input = screen.getByLabelText(/^search/i);
|
|
205
205
|
fireEvent.change(input, { target: { value: "fleet" } });
|
|
206
206
|
fireEvent.keyDown(input, { key: "Enter" });
|
|
207
207
|
expect(selected).toEqual(["1"]);
|
|
@@ -221,7 +221,7 @@ describe("CommandPalette", () => {
|
|
|
221
221
|
onSelect={(item) => selected.push(item.id)}
|
|
222
222
|
/>,
|
|
223
223
|
);
|
|
224
|
-
fireEvent.keyDown(screen.getByLabelText(
|
|
224
|
+
fireEvent.keyDown(screen.getByLabelText(/^search/i), { key: "Escape" });
|
|
225
225
|
expect(closed).toBe(true);
|
|
226
226
|
expect(selected).toEqual([]);
|
|
227
227
|
});
|
|
@@ -237,7 +237,7 @@ describe("CommandPalette", () => {
|
|
|
237
237
|
/>,
|
|
238
238
|
);
|
|
239
239
|
expect(screen.getByText("New deal")).toBeDefined();
|
|
240
|
-
fireEvent.change(screen.getByLabelText(
|
|
240
|
+
fireEvent.change(screen.getByLabelText(/^search/i), {
|
|
241
241
|
target: { value: "acme" },
|
|
242
242
|
});
|
|
243
243
|
expect(screen.queryByText("New deal")).toBeNull();
|