@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,111 +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: "Projects", icon: <FolderKanban /> },
|
|
17
|
-
{ href: "/clients", label: "Clients", icon: <Users /> },
|
|
18
|
-
];
|
|
19
|
-
|
|
20
17
|
/**
|
|
21
|
-
* The
|
|
22
|
-
*
|
|
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.
|
|
23
24
|
*/
|
|
24
25
|
export function Sidebar({
|
|
25
|
-
|
|
26
|
-
email,
|
|
26
|
+
user,
|
|
27
27
|
pathname,
|
|
28
28
|
counts,
|
|
29
29
|
onOpenCommand,
|
|
30
|
+
onNavigate,
|
|
30
31
|
onSignOut,
|
|
32
|
+
className,
|
|
31
33
|
}: {
|
|
32
|
-
|
|
33
|
-
email: string;
|
|
34
|
+
user: { name: string; email: string };
|
|
34
35
|
pathname: string;
|
|
36
|
+
/** Badge per nav href. Omit a key to show no number. */
|
|
35
37
|
counts?: Record<string, number>;
|
|
36
38
|
onOpenCommand: () => void;
|
|
39
|
+
/** Called after a link is chosen, so the phone drawer can close. */
|
|
40
|
+
onNavigate?: () => void;
|
|
37
41
|
onSignOut: () => void;
|
|
42
|
+
className?: string;
|
|
38
43
|
}) {
|
|
39
44
|
return (
|
|
40
|
-
<
|
|
41
|
-
<div className="flex h-
|
|
42
|
-
<
|
|
43
|
-
|
|
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}
|
|
44
50
|
</span>
|
|
45
|
-
<span className="truncate text-[13px] font-semibold">{workspace}</span>
|
|
46
51
|
</div>
|
|
47
52
|
|
|
48
|
-
<div className="px-
|
|
53
|
+
<div className="px-3 pb-3">
|
|
49
54
|
<button
|
|
50
55
|
type="button"
|
|
51
56
|
onClick={onOpenCommand}
|
|
52
57
|
className={cn(
|
|
53
|
-
"flex w-full items-center gap-2 rounded-md border border-border bg-background px-2
|
|
54
|
-
"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",
|
|
55
61
|
)}
|
|
56
62
|
>
|
|
57
|
-
<
|
|
58
|
-
<span className="flex-1 text-left">Search
|
|
59
|
-
<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>
|
|
60
66
|
</button>
|
|
61
67
|
</div>
|
|
62
68
|
|
|
63
|
-
<nav className="flex-1 space-y-
|
|
69
|
+
<nav className="flex-1 space-y-px overflow-y-auto px-3" aria-label="Main">
|
|
64
70
|
{NAV.map((item) => {
|
|
65
|
-
const active =
|
|
66
|
-
|
|
71
|
+
const active = isActive(item.href, pathname);
|
|
72
|
+
const count = counts?.[item.href];
|
|
67
73
|
return (
|
|
68
74
|
<Link
|
|
69
75
|
key={item.href}
|
|
70
76
|
href={item.href}
|
|
77
|
+
onClick={onNavigate}
|
|
71
78
|
aria-current={active ? "page" : undefined}
|
|
72
79
|
className={cn(
|
|
73
|
-
"flex h-
|
|
74
|
-
"[&_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",
|
|
75
82
|
active
|
|
76
|
-
? "bg-
|
|
77
|
-
: "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",
|
|
78
85
|
)}
|
|
79
86
|
>
|
|
80
87
|
{item.icon}
|
|
81
88
|
<span className="flex-1 truncate">{item.label}</span>
|
|
82
|
-
{
|
|
83
|
-
<span className="tabular text-[
|
|
84
|
-
{counts[item.href]}
|
|
85
|
-
</span>
|
|
89
|
+
{typeof count === "number" ? (
|
|
90
|
+
<span className="tabular text-[11.5px] text-muted-foreground">{count}</span>
|
|
86
91
|
) : null}
|
|
87
92
|
</Link>
|
|
88
93
|
);
|
|
89
94
|
})}
|
|
90
95
|
</nav>
|
|
91
96
|
|
|
92
|
-
<div className="border-t border-border p-
|
|
93
|
-
<div className="flex items-center gap-2
|
|
94
|
-
<Avatar name={email}
|
|
95
|
-
<
|
|
96
|
-
{
|
|
97
|
-
|
|
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>
|
|
98
104
|
<button
|
|
99
105
|
type="button"
|
|
100
106
|
onClick={onSignOut}
|
|
101
107
|
title="Sign out"
|
|
102
108
|
aria-label="Sign out"
|
|
103
|
-
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"
|
|
104
110
|
>
|
|
105
|
-
<LogOut className="size-
|
|
111
|
+
<LogOut className="size-4" />
|
|
106
112
|
</button>
|
|
107
113
|
</div>
|
|
108
114
|
</div>
|
|
109
|
-
</
|
|
115
|
+
</div>
|
|
110
116
|
);
|
|
111
117
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { duration, money, type Portfolio } from "@/lib/work";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The four numbers across active projects. Derived from the time ledger; see
|
|
6
|
+
* `portfolio` in lib/work.ts, where the arithmetic is unit-tested.
|
|
7
|
+
*/
|
|
8
|
+
export function SummaryBar({ portfolio: p }: { portfolio: Portfolio }) {
|
|
9
|
+
return (
|
|
10
|
+
<dl className="grid shrink-0 grid-cols-2 gap-px border-b border-border bg-border lg:grid-cols-4">
|
|
11
|
+
<Metric label="Active projects" value={String(p.activeCount)} hint="in delivery now" />
|
|
12
|
+
<Metric label="Time logged" value={duration(p.loggedMinutes)} hint="on active projects" />
|
|
13
|
+
<Metric label="Billable" value={money(p.billableCents)} hint="at each project's rate" />
|
|
14
|
+
<Metric
|
|
15
|
+
label="Over budget"
|
|
16
|
+
value={String(p.overBudgetCount)}
|
|
17
|
+
hint={p.overBudgetCount === 1 ? "project past its hours" : "projects past their hours"}
|
|
18
|
+
alarming={p.overBudgetCount > 0}
|
|
19
|
+
/>
|
|
20
|
+
</dl>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Metric({
|
|
25
|
+
label,
|
|
26
|
+
value,
|
|
27
|
+
hint,
|
|
28
|
+
alarming,
|
|
29
|
+
}: {
|
|
30
|
+
label: string;
|
|
31
|
+
value: string;
|
|
32
|
+
hint: string;
|
|
33
|
+
alarming?: boolean;
|
|
34
|
+
}) {
|
|
35
|
+
return (
|
|
36
|
+
<div className="min-w-0 bg-background px-4 py-2.5 md:px-5 md:py-3.5">
|
|
37
|
+
<dt className="truncate text-[12px] text-muted-foreground">{label}</dt>
|
|
38
|
+
<dd
|
|
39
|
+
className={
|
|
40
|
+
"tabular mt-1 text-[18px] font-semibold leading-none tracking-[-0.02em] md:text-[22px]" +
|
|
41
|
+
(alarming ? " text-destructive" : "")
|
|
42
|
+
}
|
|
43
|
+
>
|
|
44
|
+
{value}
|
|
45
|
+
</dd>
|
|
46
|
+
<p className="mt-1 truncate text-[11.5px] text-muted-foreground md:mt-1.5">{hint}</p>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -29,7 +29,7 @@ export function TaskBoard({
|
|
|
29
29
|
const columns = groupByStatus(tasks);
|
|
30
30
|
|
|
31
31
|
return (
|
|
32
|
-
<div className="flex h-full gap-3 overflow-x-auto p-4">
|
|
32
|
+
<div className="flex h-full snap-x snap-mandatory scroll-px-4 gap-3 overflow-x-auto p-4 md:snap-none md:px-5">
|
|
33
33
|
{columns.map((column) => {
|
|
34
34
|
const isTarget = over === column.status.id;
|
|
35
35
|
const logged = column.tasks.reduce(
|
|
@@ -62,18 +62,19 @@ export function TaskBoard({
|
|
|
62
62
|
onMove(id, column.status.id);
|
|
63
63
|
}}
|
|
64
64
|
className={cn(
|
|
65
|
-
"flex w-[
|
|
66
|
-
|
|
65
|
+
"flex w-[84vw] shrink-0 snap-start flex-col rounded-xl border transition-colors",
|
|
66
|
+
"md:w-auto md:min-w-[220px] md:max-w-[360px] md:flex-1",
|
|
67
|
+
isTarget ? "border-ring/50 bg-primary/[0.04]" : "border-border/70 bg-surface-1",
|
|
67
68
|
)}
|
|
68
69
|
aria-label={column.status.label}
|
|
69
70
|
>
|
|
70
|
-
<header className="flex items-center gap-2 px-3
|
|
71
|
-
<h2 className="text-[
|
|
72
|
-
<span className="tabular text-[
|
|
71
|
+
<header className="flex items-center gap-2 px-3 pb-2 pt-3">
|
|
72
|
+
<h2 className="text-[12.5px] font-semibold">{column.status.label}</h2>
|
|
73
|
+
<span className="tabular text-[12px] text-muted-foreground">
|
|
73
74
|
{column.tasks.length}
|
|
74
75
|
</span>
|
|
75
76
|
{logged > 0 ? (
|
|
76
|
-
<span className="tabular ml-auto text-[
|
|
77
|
+
<span className="tabular ml-auto text-[12px] text-muted-foreground">
|
|
77
78
|
{duration(logged)}
|
|
78
79
|
</span>
|
|
79
80
|
) : null}
|
|
@@ -96,8 +97,8 @@ export function TaskBoard({
|
|
|
96
97
|
/>
|
|
97
98
|
))}
|
|
98
99
|
{column.tasks.length === 0 ? (
|
|
99
|
-
<p className="px-
|
|
100
|
-
{isTarget ? "Drop here" :
|
|
100
|
+
<p className="rounded-lg border border-dashed border-border px-3 py-6 text-center text-[12px] text-muted-foreground">
|
|
101
|
+
{isTarget ? "Drop to move here" : `No tasks in ${column.status.label}`}
|
|
101
102
|
</p>
|
|
102
103
|
) : null}
|
|
103
104
|
</div>
|
|
@@ -9,23 +9,30 @@ import {
|
|
|
9
9
|
import { Button } from "@/components/ui/button";
|
|
10
10
|
import { Input } from "@/components/ui/input";
|
|
11
11
|
import { Label } from "@/components/ui/label";
|
|
12
|
-
import {
|
|
12
|
+
import { Select } from "@/components/ui/select";
|
|
13
|
+
import { TASK_STATUSES, duration, parseDuration } from "@/lib/work";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* Log time against a task.
|
|
16
|
+
* Log time against a task, and move it between columns. The status select is
|
|
17
|
+
* how a task changes column on a touch screen, where cards can't be dragged.
|
|
16
18
|
*
|
|
17
19
|
* The field accepts however people write time — "90", "1.5h", "1h30", "45m".
|
|
18
|
-
* Rejecting "1h30" because it isn
|
|
20
|
+
* Rejecting "1h30" because it isn't "90" is the kind of friction that stops
|
|
19
21
|
* time being logged at all, which costs far more than a lenient parser.
|
|
20
22
|
*/
|
|
21
23
|
export function TimeDialog({
|
|
22
24
|
open,
|
|
23
25
|
taskTitle,
|
|
26
|
+
status,
|
|
27
|
+
onMove,
|
|
24
28
|
onOpenChange,
|
|
25
29
|
onLog,
|
|
26
30
|
}: {
|
|
27
31
|
open: boolean;
|
|
28
32
|
taskTitle: string;
|
|
33
|
+
/** The task's current column. With `onMove`, shows a status select. */
|
|
34
|
+
status?: string;
|
|
35
|
+
onMove?: (status: string) => void;
|
|
29
36
|
onOpenChange: (open: boolean) => void;
|
|
30
37
|
onLog: (minutes: number, note: string) => void | Promise<void>;
|
|
31
38
|
}) {
|
|
@@ -63,7 +70,24 @@ export function TimeDialog({
|
|
|
63
70
|
<DialogTitle>Log time</DialogTitle>
|
|
64
71
|
</DialogHeader>
|
|
65
72
|
<form onSubmit={submit} className="grid gap-4">
|
|
66
|
-
<p className="truncate text-[
|
|
73
|
+
<p className="truncate text-[13px] font-medium">{taskTitle}</p>
|
|
74
|
+
|
|
75
|
+
{status && onMove ? (
|
|
76
|
+
<div className="space-y-1.5">
|
|
77
|
+
<Label htmlFor="task-status">Status</Label>
|
|
78
|
+
<Select
|
|
79
|
+
id="task-status"
|
|
80
|
+
value={status}
|
|
81
|
+
onChange={(event) => onMove(event.target.value)}
|
|
82
|
+
>
|
|
83
|
+
{TASK_STATUSES.map((s) => (
|
|
84
|
+
<option key={s.id} value={s.id}>
|
|
85
|
+
{s.label}
|
|
86
|
+
</option>
|
|
87
|
+
))}
|
|
88
|
+
</Select>
|
|
89
|
+
</div>
|
|
90
|
+
) : null}
|
|
67
91
|
|
|
68
92
|
<div className="space-y-1.5">
|
|
69
93
|
<Label htmlFor="time-value">Time</Label>
|
|
@@ -79,7 +103,7 @@ export function TimeDialog({
|
|
|
79
103
|
) : null}
|
|
80
104
|
{tooMuch ? (
|
|
81
105
|
<p className="text-[11px] text-destructive">
|
|
82
|
-
That
|
|
106
|
+
That's more than a day — check the value.
|
|
83
107
|
</p>
|
|
84
108
|
) : null}
|
|
85
109
|
{minutes === null && value.trim() ? (
|
|
@@ -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,25 +1,37 @@
|
|
|
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 delivery workspace once.
|
|
6
7
|
*
|
|
7
|
-
* Seeds time as ENTRIES rather than totals, because logged time here is the
|
|
8
|
-
* of the ledger
|
|
9
|
-
* One project lands over its budget so that state is visible on first
|
|
8
|
+
* Seeds time as ENTRIES rather than totals, because logged time here is the
|
|
9
|
+
* sum of the ledger; a seed that wrote totals would contradict the app's own
|
|
10
|
+
* model. One project lands over its budget so that state is visible on first
|
|
11
|
+
* load.
|
|
10
12
|
*
|
|
11
|
-
* Returns immediately if any project exists, so it is safe on every load.
|
|
13
|
+
* Returns immediately if any project exists, so it is safe on every load. An
|
|
14
|
+
* advisory lock stops two first loads from seeding twice. Tasks are assigned
|
|
15
|
+
* across the person who signed in and four demo teammates (User rows with no
|
|
16
|
+
* password, see SEED_TEAM), and each entry is logged by its task's assignee.
|
|
17
|
+
* Delete this function, lib/seed.ts, and the `seedWorkspace` call in
|
|
18
|
+
* app/workspace.tsx once you have real projects, then delete the demo users.
|
|
12
19
|
*/
|
|
13
20
|
export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
14
21
|
auth: "user",
|
|
15
22
|
args: {},
|
|
16
23
|
async handler(ctx) {
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
if ((await ctx.db.query("Project", { $limit: 1 })).length > 0) return { seeded: false };
|
|
25
|
+
await ctx.db.advisoryLock("projects_seed_workspace");
|
|
26
|
+
if ((await ctx.db.query("Project", { $limit: 1 })).length > 0) return { seeded: false };
|
|
19
27
|
|
|
20
28
|
const seed = shapeSeed();
|
|
21
29
|
const me = ctx.auth.userId;
|
|
22
30
|
|
|
31
|
+
// Position 0 is the person who signed in; SEED_TEAM follows.
|
|
32
|
+
const team: Array<string | null> = [me];
|
|
33
|
+
for (const person of SEED_TEAM) team.push(await ensureDemoUser(ctx.db, person));
|
|
34
|
+
|
|
23
35
|
const clientIds = new Map<string, string>();
|
|
24
36
|
for (const client of seed.clients) {
|
|
25
37
|
const id = await ctx.db.insert("Client", client.row);
|
|
@@ -35,22 +47,24 @@ export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
|
35
47
|
projectIds.set(project.key, id as string);
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
const
|
|
50
|
+
const tasks = new Map<string, { id: string; assignee: string | null }>();
|
|
39
51
|
for (const task of seed.tasks) {
|
|
52
|
+
const assignee = task.assignee === null ? null : (team[task.assignee] ?? me);
|
|
40
53
|
const id = await ctx.db.insert("Task", {
|
|
41
54
|
...task.row,
|
|
42
55
|
projectId: projectIds.get(task.project) ?? null,
|
|
43
|
-
assigneeId:
|
|
56
|
+
assigneeId: assignee,
|
|
44
57
|
});
|
|
45
|
-
|
|
58
|
+
tasks.set(task.key, { id: id as string, assignee });
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
for (const entry of seed.entries) {
|
|
62
|
+
const task = tasks.get(entry.task);
|
|
49
63
|
await ctx.db.insert("TimeEntry", {
|
|
50
64
|
...entry.row,
|
|
51
|
-
taskId:
|
|
65
|
+
taskId: task?.id ?? null,
|
|
52
66
|
projectId: projectIds.get(entry.project) ?? null,
|
|
53
|
-
userId: me,
|
|
67
|
+
userId: task?.assignee ?? me,
|
|
54
68
|
});
|
|
55
69
|
}
|
|
56
70
|
|
|
@@ -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: "Workbench",
|
|
7
|
+
/** One line on the sign-in screen: what the app holds and who sees it. */
|
|
8
|
+
description: "Your team's client projects, task boards, and timesheets. Everyone sees the same work.",
|
|
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
|
+
}
|
|
@@ -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. */
|
|
@@ -134,10 +134,43 @@ export const SEED_TASKS: SeedTask[] = [
|
|
|
134
134
|
{ key: "rf-reports", project: "referrals", title: "Turnaround reports", status: "done", estimateHours: 8, loggedHours: [4, 5] },
|
|
135
135
|
];
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The demo delivery team. Each becomes a User row with no
|
|
139
|
+
* password (see lib/demo-team.ts); they exist so tasks and timesheets have realistic
|
|
140
|
+
* owners. The `.invalid` domain is reserved and can never receive mail.
|
|
141
|
+
*/
|
|
142
|
+
export interface SeedTeammate {
|
|
143
|
+
name: string;
|
|
144
|
+
email: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const SEED_TEAM: SeedTeammate[] = [
|
|
148
|
+
{ name: "Maya Castillo", email: "maya.castillo@demo.invalid" },
|
|
149
|
+
{ name: "Jordan Ellis", email: "jordan.ellis@demo.invalid" },
|
|
150
|
+
{ name: "Aisha Karim", email: "aisha.karim@demo.invalid" },
|
|
151
|
+
{ name: "Noah Brandt", email: "noah.brandt@demo.invalid" },
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Who the seeded task at `index` is assigned to, as a position in
|
|
156
|
+
* `[you, ...SEED_TEAM]`, or null. A task still to do whose index in SEED_TASKS
|
|
157
|
+
* is a multiple of 3 is unassigned, so the board has work to pick up; the rest spread across the team,
|
|
158
|
+
* including the person who signed up.
|
|
159
|
+
*/
|
|
160
|
+
export function seedAssignee(
|
|
161
|
+
index: number,
|
|
162
|
+
status: string,
|
|
163
|
+
teamSize: number = SEED_TEAM.length + 1,
|
|
164
|
+
): number | null {
|
|
165
|
+
if (status === "todo" && index % 3 === 0) return null;
|
|
166
|
+
return (index * 3 + 1) % teamSize;
|
|
167
|
+
}
|
|
168
|
+
|
|
137
169
|
export interface ShapedSeed {
|
|
138
170
|
clients: Array<{ key: string; row: Record<string, unknown> }>;
|
|
139
171
|
projects: Array<{ key: string; client: string; row: Record<string, unknown> }>;
|
|
140
|
-
|
|
172
|
+
/** `assignee` is a position in `[you, ...SEED_TEAM]`, or null; see seedAssignee. */
|
|
173
|
+
tasks: Array<{ key: string; project: string; assignee: number | null; row: Record<string, unknown> }>;
|
|
141
174
|
entries: Array<{ task: string; project: string; row: Record<string, unknown> }>;
|
|
142
175
|
}
|
|
143
176
|
|
|
@@ -162,6 +195,7 @@ export function shapeSeed(now: number = Date.now()): ShapedSeed {
|
|
|
162
195
|
tasks.push({
|
|
163
196
|
key: task.key,
|
|
164
197
|
project: task.project,
|
|
198
|
+
assignee: seedAssignee(index, task.status),
|
|
165
199
|
row: {
|
|
166
200
|
title: task.title,
|
|
167
201
|
status: task.status,
|