@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,13 +1,13 @@
|
|
|
1
1
|
import React, { use } from "react";
|
|
2
2
|
import type { PageAuth, ServerData, SsrResponse } from "@pylonsync/react";
|
|
3
|
-
import { DashboardShell, NAV } from "@/components/dashboard-shell";
|
|
3
|
+
import { DashboardShell, NAV, type ShellProject } from "@/components/dashboard-shell";
|
|
4
|
+
import { personName } from "@/lib/names";
|
|
4
5
|
|
|
5
|
-
// `app/dashboard/layout.tsx` wraps every /dashboard page in the sidebar
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// component) its props.
|
|
6
|
+
// `app/dashboard/layout.tsx` wraps every /dashboard page in the sidebar shell,
|
|
7
|
+
// so no page repeats the chrome. The layout gates auth, resolves the chrome's
|
|
8
|
+
// data on the server (the person's name, the workspace name, the project
|
|
9
|
+
// list), derives the active nav item and title from the request URL, and hands
|
|
10
|
+
// the interactive shell (a `"use client"` component) its props.
|
|
11
11
|
interface LayoutProps {
|
|
12
12
|
children: React.ReactNode;
|
|
13
13
|
url: string;
|
|
@@ -30,34 +30,48 @@ export default function DashboardLayout({
|
|
|
30
30
|
response.redirect("/login");
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
-
// No active workspace: render the page bare
|
|
34
|
-
//
|
|
35
|
-
// /dashboard themselves. The shell's org switcher has nothing to show yet.
|
|
33
|
+
// No active workspace: render the page bare. /dashboard shows the
|
|
34
|
+
// ProvisionWorkspace flow, and the subpages redirect back to /dashboard.
|
|
36
35
|
if (!auth.tenant_id) {
|
|
37
36
|
return <>{children}</>;
|
|
38
37
|
}
|
|
38
|
+
const path = (url ?? "").split("?")[0];
|
|
39
|
+
// `/dashboard/projects/<id>` shows that project's board; its name becomes
|
|
40
|
+
// the title with Projects as the breadcrumb parent.
|
|
41
|
+
const projectId = path.match(/^\/dashboard\/projects\/([^/]+)$/)?.[1];
|
|
42
|
+
|
|
39
43
|
// Every read is started before the first use(): calling them one at a time
|
|
40
44
|
// makes each wait for the last, since use() suspends on the first pending
|
|
41
45
|
// thenable. Never Promise.all them — a new pending promise each render means
|
|
42
46
|
// the page never returns (React error #482).
|
|
43
|
-
const mePromise = serverData.get<{ email?: string }>("User", auth.user_id);
|
|
47
|
+
const mePromise = serverData.get<{ email?: string; displayName?: string | null }>("User", auth.user_id);
|
|
44
48
|
const orgPromise = serverData.get<{ name?: string }>("Org", auth.tenant_id);
|
|
49
|
+
const projectsPromise = serverData.list<ShellProject>("Project");
|
|
45
50
|
|
|
46
51
|
const me = use(mePromise);
|
|
47
52
|
const org = use(orgPromise);
|
|
53
|
+
const projects = use(projectsPromise).filter((p) => p.orgId === auth.tenant_id);
|
|
54
|
+
|
|
48
55
|
// Longest matching NAV href wins so /dashboard/projects highlights
|
|
49
56
|
// Projects, not Overview.
|
|
50
|
-
const path = (url ?? "").split("?")[0];
|
|
51
57
|
const nav =
|
|
52
58
|
NAV.filter((n) => path === n.href || path.startsWith(n.href + "/")).sort(
|
|
53
59
|
(a, b) => b.href.length - a.href.length,
|
|
54
60
|
)[0] ?? NAV[0];
|
|
61
|
+
const project = projectId ? projects.find((p) => p.id === projectId) : undefined;
|
|
62
|
+
|
|
55
63
|
return (
|
|
56
64
|
<DashboardShell
|
|
57
65
|
active={nav.key}
|
|
58
|
-
title={nav.label}
|
|
66
|
+
title={project ? project.name : nav.label}
|
|
67
|
+
parent={project ? { label: "Projects", href: "/dashboard/projects" } : undefined}
|
|
68
|
+
activeProjectId={project?.id}
|
|
69
|
+
tenantId={auth.tenant_id}
|
|
70
|
+
userId={auth.user_id}
|
|
71
|
+
userName={personName({ displayName: me?.displayName, email: me?.email })}
|
|
59
72
|
userEmail={me?.email ?? ""}
|
|
60
73
|
orgName={org?.name}
|
|
74
|
+
projects={projects.map((p) => ({ id: p.id, orgId: p.orgId, name: p.name, status: p.status }))}
|
|
61
75
|
>
|
|
62
76
|
{children}
|
|
63
77
|
</DashboardShell>
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import { callFn, Link } from "@pylonsync/react";
|
|
5
|
+
import { ArrowRight, FolderPlus, Sparkles } from "lucide-react";
|
|
6
|
+
import { SetupChecklist, type SetupState } from "@/components/setup-checklist";
|
|
7
|
+
import {
|
|
8
|
+
Avatar,
|
|
9
|
+
DueLabel,
|
|
10
|
+
EmptyAvatar,
|
|
11
|
+
PriorityIcon,
|
|
12
|
+
Progress,
|
|
13
|
+
ProjectMark,
|
|
14
|
+
StatusIcon,
|
|
15
|
+
shortDay,
|
|
16
|
+
} from "@/components/task-ui";
|
|
17
|
+
import { FREE_PROJECT_LIMIT } from "@/lib/plans";
|
|
18
|
+
import { compareTasks, daysUntil, progressOf, type TaskRow } from "@/lib/tasks";
|
|
19
|
+
import { useLiveRows } from "@/lib/use-live";
|
|
20
|
+
import type { Project } from "./projects-client";
|
|
21
|
+
|
|
22
|
+
// The Overview. Projects and tasks start from the server-rendered rows and
|
|
23
|
+
// stay live through sync, so a task finished in another tab or by a teammate
|
|
24
|
+
// updates the counts here without a reload.
|
|
25
|
+
export function Overview({
|
|
26
|
+
tenantId,
|
|
27
|
+
userId,
|
|
28
|
+
firstName,
|
|
29
|
+
projects: initialProjects,
|
|
30
|
+
tasks: initialTasks,
|
|
31
|
+
memberCount,
|
|
32
|
+
plan,
|
|
33
|
+
today,
|
|
34
|
+
canSeed,
|
|
35
|
+
setup,
|
|
36
|
+
}: {
|
|
37
|
+
tenantId: string;
|
|
38
|
+
userId: string;
|
|
39
|
+
firstName: string;
|
|
40
|
+
projects: Project[];
|
|
41
|
+
tasks: TaskRow[];
|
|
42
|
+
memberCount: number;
|
|
43
|
+
plan: string;
|
|
44
|
+
/** `YYYY-MM-DD` from the server render. */
|
|
45
|
+
today: string;
|
|
46
|
+
/** Owners and admins may add the sample projects to an empty workspace. */
|
|
47
|
+
canSeed: boolean;
|
|
48
|
+
/** Getting-started state; null once the checklist is dismissed. */
|
|
49
|
+
setup?: SetupState | null;
|
|
50
|
+
}) {
|
|
51
|
+
const projects = useLiveRows<Project>("Project", initialProjects, tenantId);
|
|
52
|
+
const tasks = useLiveRows<TaskRow>("Task", initialTasks, tenantId);
|
|
53
|
+
|
|
54
|
+
const active = projects.filter((p) => (p.status ?? "active") !== "archived");
|
|
55
|
+
const activeIds = new Set(active.map((p) => p.id));
|
|
56
|
+
const liveTasks = tasks.filter((t) => activeIds.has(t.projectId));
|
|
57
|
+
const open = liveTasks.filter((t) => t.status !== "done");
|
|
58
|
+
const dueThisWeek = open.filter((t) => {
|
|
59
|
+
if (!t.dueDate) return false;
|
|
60
|
+
const d = daysUntil(t.dueDate, today);
|
|
61
|
+
return d >= 0 && d <= 7;
|
|
62
|
+
}).length;
|
|
63
|
+
const overdue = open.filter((t) => t.dueDate && daysUntil(t.dueDate, today) < 0).length;
|
|
64
|
+
const doneThisWeek = liveTasks.filter(
|
|
65
|
+
(t) => t.status === "done" && t.completedAt && daysUntil(t.completedAt.slice(0, 10), today) >= -7,
|
|
66
|
+
).length;
|
|
67
|
+
const [scope, setScope] = useState<"everyone" | "mine">("everyone");
|
|
68
|
+
const mineCount = open.filter((t) => t.assigneeId === userId).length;
|
|
69
|
+
// Open tasks by due date, earliest first; undated tasks last.
|
|
70
|
+
const upNext = open
|
|
71
|
+
.filter((t) => scope === "everyone" || t.assigneeId === userId)
|
|
72
|
+
.sort((a, b) => (a.dueDate ?? "9999") < (b.dueDate ?? "9999") ? -1 : (a.dueDate ?? "9999") > (b.dueDate ?? "9999") ? 1 : compareTasks(a, b))
|
|
73
|
+
.slice(0, 8);
|
|
74
|
+
const recent = liveTasks
|
|
75
|
+
.filter((t) => t.status === "done" && t.completedAt)
|
|
76
|
+
.sort((a, b) => (a.completedAt! < b.completedAt! ? 1 : -1))
|
|
77
|
+
.slice(0, 4);
|
|
78
|
+
const projectName = new Map(projects.map((p) => [p.id, p.name]));
|
|
79
|
+
|
|
80
|
+
const greeting = firstName ? `Welcome back, ${firstName}` : "Welcome back";
|
|
81
|
+
const summary =
|
|
82
|
+
active.length === 0
|
|
83
|
+
? "Create a project to start tracking work."
|
|
84
|
+
: `${open.length} open ${open.length === 1 ? "task" : "tasks"} across ${active.length} active ${
|
|
85
|
+
active.length === 1 ? "project" : "projects"
|
|
86
|
+
}.`;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="mx-auto max-w-[1080px] space-y-8">
|
|
90
|
+
<div className="flex flex-wrap items-end justify-between gap-4">
|
|
91
|
+
<div>
|
|
92
|
+
<h1 className="text-[22px] font-semibold tracking-[-0.015em] text-zinc-900">{greeting}</h1>
|
|
93
|
+
<p className="mt-1 text-[14px] text-zinc-500">{summary}</p>
|
|
94
|
+
</div>
|
|
95
|
+
{active.length > 0 ? (
|
|
96
|
+
<Link
|
|
97
|
+
href="/dashboard/projects"
|
|
98
|
+
className="inline-flex h-8 items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-3 text-[13px] font-medium text-zinc-700 shadow-[0_1px_1px_rgba(0,0,0,0.03)] transition-colors hover:bg-zinc-50"
|
|
99
|
+
>
|
|
100
|
+
All projects <ArrowRight className="size-3.5" />
|
|
101
|
+
</Link>
|
|
102
|
+
) : null}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{setup ? <SetupChecklist state={setup} /> : null}
|
|
106
|
+
|
|
107
|
+
{projects.length === 0 ? (
|
|
108
|
+
<EmptyWorkspace orgId={tenantId} canSeed={canSeed} />
|
|
109
|
+
) : (
|
|
110
|
+
<>
|
|
111
|
+
<div className="grid grid-cols-2 gap-px overflow-hidden rounded-xl border border-zinc-200/80 bg-zinc-200/60 lg:grid-cols-4">
|
|
112
|
+
<Stat label="Open tasks" value={open.length} note={`${liveTasks.length - open.length} done`} />
|
|
113
|
+
<Stat
|
|
114
|
+
label="Due in 7 days"
|
|
115
|
+
value={dueThisWeek}
|
|
116
|
+
note={overdue > 0 ? `${overdue} overdue` : "none overdue"}
|
|
117
|
+
alert={overdue > 0}
|
|
118
|
+
/>
|
|
119
|
+
<Stat label="Completed this week" value={doneThisWeek} note="last 7 days" />
|
|
120
|
+
<Stat
|
|
121
|
+
label="Plan"
|
|
122
|
+
value={<span className="capitalize">{plan}</span>}
|
|
123
|
+
note={
|
|
124
|
+
plan === "free" ? (
|
|
125
|
+
<Link href="/dashboard/billing" className="text-brand hover:underline">
|
|
126
|
+
{active.length} of {FREE_PROJECT_LIMIT} projects used
|
|
127
|
+
</Link>
|
|
128
|
+
) : (
|
|
129
|
+
`${memberCount} ${memberCount === 1 ? "member" : "members"}`
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<div className="grid gap-6 lg:grid-cols-[minmax(0,1.55fr)_minmax(0,1fr)]">
|
|
136
|
+
<section className="min-w-0 self-start rounded-xl border border-zinc-200/80 bg-white">
|
|
137
|
+
<header className="flex items-center justify-between border-b border-zinc-100 px-4 py-3">
|
|
138
|
+
<h2 className="text-[13px] font-semibold text-zinc-900">Up next</h2>
|
|
139
|
+
<div className="inline-flex rounded-md bg-zinc-100 p-0.5 text-[12px]">
|
|
140
|
+
{(
|
|
141
|
+
[
|
|
142
|
+
["everyone", "Everyone"],
|
|
143
|
+
["mine", `Assigned to me · ${mineCount}`],
|
|
144
|
+
] as const
|
|
145
|
+
).map(([key, label]) => (
|
|
146
|
+
<button
|
|
147
|
+
key={key}
|
|
148
|
+
type="button"
|
|
149
|
+
onClick={() => setScope(key)}
|
|
150
|
+
className={
|
|
151
|
+
"rounded px-2 py-0.5 font-medium transition-colors " +
|
|
152
|
+
(scope === key ? "bg-white text-zinc-900 shadow-[0_1px_2px_rgba(0,0,0,0.08)]" : "text-zinc-500 hover:text-zinc-800")
|
|
153
|
+
}
|
|
154
|
+
>
|
|
155
|
+
{label}
|
|
156
|
+
</button>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
</header>
|
|
160
|
+
{upNext.length === 0 ? (
|
|
161
|
+
<p className="px-4 py-10 text-center text-[13px] text-zinc-500">
|
|
162
|
+
{scope === "mine" ? "Nothing assigned to you." : "No open tasks."}
|
|
163
|
+
</p>
|
|
164
|
+
) : (
|
|
165
|
+
<ul className="divide-y divide-zinc-100">
|
|
166
|
+
{upNext.map((t) => (
|
|
167
|
+
<li key={t.id}>
|
|
168
|
+
<Link
|
|
169
|
+
href={`/dashboard/projects/${t.projectId}`}
|
|
170
|
+
className="flex items-center gap-3 px-4 py-2.5 transition-colors hover:bg-zinc-50"
|
|
171
|
+
>
|
|
172
|
+
<PriorityIcon priority={t.priority} />
|
|
173
|
+
<StatusIcon status={t.status} />
|
|
174
|
+
<span className="min-w-0 flex-1 truncate text-[13.5px] text-zinc-800">{t.title}</span>
|
|
175
|
+
<span className="hidden max-w-[10rem] items-center gap-1.5 truncate text-[12px] text-zinc-500 sm:inline-flex">
|
|
176
|
+
<ProjectMark
|
|
177
|
+
id={t.projectId}
|
|
178
|
+
name={projectName.get(t.projectId) ?? "?"}
|
|
179
|
+
className="size-4 text-[7.5px]"
|
|
180
|
+
/>
|
|
181
|
+
<span className="truncate">{projectName.get(t.projectId)}</span>
|
|
182
|
+
</span>
|
|
183
|
+
<span className="w-16 text-right">
|
|
184
|
+
<DueLabel due={t.dueDate} today={today} />
|
|
185
|
+
</span>
|
|
186
|
+
{t.assigneeId ? (
|
|
187
|
+
<Avatar id={t.assigneeId} name={t.assigneeName || "Member"} size="xs" />
|
|
188
|
+
) : (
|
|
189
|
+
<EmptyAvatar size="xs" />
|
|
190
|
+
)}
|
|
191
|
+
</Link>
|
|
192
|
+
</li>
|
|
193
|
+
))}
|
|
194
|
+
</ul>
|
|
195
|
+
)}
|
|
196
|
+
</section>
|
|
197
|
+
|
|
198
|
+
<div className="min-w-0 space-y-6">
|
|
199
|
+
<section className="rounded-xl border border-zinc-200/80 bg-white">
|
|
200
|
+
<header className="flex items-center justify-between border-b border-zinc-100 px-4 py-3">
|
|
201
|
+
<h2 className="text-[13px] font-semibold text-zinc-900">Projects</h2>
|
|
202
|
+
<Link href="/dashboard/projects" className="text-[12px] font-medium text-zinc-500 hover:text-zinc-900">
|
|
203
|
+
View all
|
|
204
|
+
</Link>
|
|
205
|
+
</header>
|
|
206
|
+
{active.length === 0 ? (
|
|
207
|
+
<p className="px-4 py-10 text-center text-[13px] text-zinc-500">No active projects.</p>
|
|
208
|
+
) : (
|
|
209
|
+
<ul className="divide-y divide-zinc-100">
|
|
210
|
+
{active.map((p) => {
|
|
211
|
+
const pr = progressOf(tasks.filter((t) => t.projectId === p.id));
|
|
212
|
+
return (
|
|
213
|
+
<li key={p.id}>
|
|
214
|
+
<Link
|
|
215
|
+
href={`/dashboard/projects/${p.id}`}
|
|
216
|
+
className="block px-4 py-3 transition-colors hover:bg-zinc-50"
|
|
217
|
+
>
|
|
218
|
+
<div className="flex items-center gap-2.5">
|
|
219
|
+
<ProjectMark id={p.id} name={p.name} className="size-6 text-[9.5px]" />
|
|
220
|
+
<span className="min-w-0 flex-1 truncate text-[13.5px] font-medium text-zinc-900">
|
|
221
|
+
{p.name}
|
|
222
|
+
</span>
|
|
223
|
+
<span className="font-mono text-[11.5px] tabular-nums text-zinc-400">
|
|
224
|
+
{pr.done}/{pr.total}
|
|
225
|
+
</span>
|
|
226
|
+
</div>
|
|
227
|
+
<Progress percent={pr.percent} className="mt-2.5" />
|
|
228
|
+
</Link>
|
|
229
|
+
</li>
|
|
230
|
+
);
|
|
231
|
+
})}
|
|
232
|
+
</ul>
|
|
233
|
+
)}
|
|
234
|
+
</section>
|
|
235
|
+
|
|
236
|
+
{recent.length > 0 ? (
|
|
237
|
+
<section className="rounded-xl border border-zinc-200/80 bg-white">
|
|
238
|
+
<header className="border-b border-zinc-100 px-4 py-3">
|
|
239
|
+
<h2 className="text-[13px] font-semibold text-zinc-900">Recently completed</h2>
|
|
240
|
+
</header>
|
|
241
|
+
<ul className="divide-y divide-zinc-100">
|
|
242
|
+
{recent.map((t) => (
|
|
243
|
+
<li key={t.id} className="flex items-center gap-2.5 px-4 py-2.5">
|
|
244
|
+
<StatusIcon status="done" />
|
|
245
|
+
<span className="min-w-0 flex-1 truncate text-[13px] text-zinc-600">{t.title}</span>
|
|
246
|
+
{t.assigneeId ? (
|
|
247
|
+
<Avatar id={t.assigneeId} name={t.assigneeName || "Member"} size="xs" />
|
|
248
|
+
) : null}
|
|
249
|
+
<span className="w-12 text-right text-[12px] tabular-nums text-zinc-400">
|
|
250
|
+
{shortDay(t.completedAt!.slice(0, 10))}
|
|
251
|
+
</span>
|
|
252
|
+
</li>
|
|
253
|
+
))}
|
|
254
|
+
</ul>
|
|
255
|
+
</section>
|
|
256
|
+
) : null}
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function Stat({
|
|
266
|
+
label,
|
|
267
|
+
value,
|
|
268
|
+
note,
|
|
269
|
+
alert,
|
|
270
|
+
}: {
|
|
271
|
+
label: string;
|
|
272
|
+
value: React.ReactNode;
|
|
273
|
+
note: React.ReactNode;
|
|
274
|
+
alert?: boolean;
|
|
275
|
+
}) {
|
|
276
|
+
return (
|
|
277
|
+
<div className="bg-white p-4">
|
|
278
|
+
<div className="text-[12.5px] text-zinc-500">{label}</div>
|
|
279
|
+
<div className="mt-1.5 text-[26px] font-semibold leading-none tracking-[-0.02em] tabular-nums text-zinc-900">
|
|
280
|
+
{value}
|
|
281
|
+
</div>
|
|
282
|
+
<div className={"mt-2 text-[12px] " + (alert ? "text-red-600" : "text-zinc-400")}>{note}</div>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function EmptyWorkspace({ orgId, canSeed }: { orgId: string; canSeed: boolean }) {
|
|
288
|
+
const [busy, setBusy] = useState(false);
|
|
289
|
+
const [error, setError] = useState<string | null>(null);
|
|
290
|
+
async function seed() {
|
|
291
|
+
setBusy(true);
|
|
292
|
+
setError(null);
|
|
293
|
+
try {
|
|
294
|
+
await callFn("seedWorkspace", { orgId });
|
|
295
|
+
} catch (e) {
|
|
296
|
+
setError(e instanceof Error ? e.message.replace(/^[A-Z_]+:\s*/, "") : String(e));
|
|
297
|
+
} finally {
|
|
298
|
+
setBusy(false);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return (
|
|
302
|
+
<div className="rounded-xl border border-dashed border-zinc-300 bg-zinc-50/60 px-6 py-14 text-center">
|
|
303
|
+
<span className="mx-auto flex size-10 items-center justify-center rounded-xl bg-white text-zinc-500 shadow-[0_0_0_1px_rgba(0,0,0,0.06)]">
|
|
304
|
+
<FolderPlus className="size-5" strokeWidth={1.75} />
|
|
305
|
+
</span>
|
|
306
|
+
<h2 className="mt-4 text-[15px] font-semibold text-zinc-900">This workspace has no projects</h2>
|
|
307
|
+
<p className="mx-auto mt-1 max-w-sm text-[13.5px] text-zinc-500">
|
|
308
|
+
Create your first project, or add two sample projects with tasks to try the board.
|
|
309
|
+
</p>
|
|
310
|
+
<div className="mt-5 flex flex-wrap items-center justify-center gap-2">
|
|
311
|
+
<Link
|
|
312
|
+
href="/dashboard/projects?new=1"
|
|
313
|
+
className="inline-flex h-9 items-center rounded-lg bg-zinc-900 px-4 text-[13px] font-medium text-white transition-colors hover:bg-zinc-700"
|
|
314
|
+
>
|
|
315
|
+
Create a project
|
|
316
|
+
</Link>
|
|
317
|
+
{canSeed ? (
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
onClick={() => void seed()}
|
|
321
|
+
disabled={busy}
|
|
322
|
+
className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-zinc-200 bg-white px-4 text-[13px] font-medium text-zinc-800 transition-colors hover:bg-zinc-50 disabled:opacity-60"
|
|
323
|
+
>
|
|
324
|
+
<Sparkles className="size-3.5 text-zinc-500" strokeWidth={1.75} />
|
|
325
|
+
{busy ? "Adding…" : "Add sample projects"}
|
|
326
|
+
</button>
|
|
327
|
+
) : null}
|
|
328
|
+
</div>
|
|
329
|
+
{error ? <p className="mt-3 text-[12.5px] text-red-600">{error}</p> : null}
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
@@ -1,54 +1,46 @@
|
|
|
1
1
|
import React, { use } from "react";
|
|
2
2
|
import { type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
3
|
import { ProvisionWorkspace } from "./provision-workspace";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type OrgMemberRow,
|
|
8
|
-
type Subscription,
|
|
9
|
-
} from "./dashboard-client";
|
|
4
|
+
import { Overview } from "./overview-client";
|
|
5
|
+
import type { Project } from "./projects-client";
|
|
6
|
+
import type { OrgMemberRow, Subscription } from "./dashboard-client";
|
|
10
7
|
import type { SetupState } from "@/components/setup-checklist";
|
|
8
|
+
import { firstName } from "@/lib/names";
|
|
9
|
+
import { isoDay, type TaskRow } from "@/lib/tasks";
|
|
11
10
|
|
|
12
11
|
export const metadata: Metadata = {
|
|
13
|
-
title: "
|
|
12
|
+
title: "Overview — Acme",
|
|
14
13
|
robots: "noindex",
|
|
15
14
|
};
|
|
16
15
|
|
|
17
16
|
// `app/dashboard/page.tsx` → `/dashboard`. The dashboard layout owns the auth
|
|
18
|
-
// gate and the shell chrome; this page reads the active
|
|
19
|
-
// render via `serverData` + React 19 `use()
|
|
20
|
-
//
|
|
21
|
-
//
|
|
17
|
+
// gate and the shell chrome; this page reads the active workspace's rows
|
|
18
|
+
// during the render via `serverData` + React 19 `use()`, so the Overview
|
|
19
|
+
// paints with real data on the first byte. The client then keeps projects and
|
|
20
|
+
// tasks live.
|
|
22
21
|
export default function DashboardPage({ auth, response, serverData }: PageProps) {
|
|
23
|
-
// No active workspace (
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
// The layout renders this bare (no shell) when there's no tenant.
|
|
22
|
+
// No active workspace (the user left or deleted their last org, or quit the
|
|
23
|
+
// wizard before step 1). Every read below is tenant-scoped, so select an
|
|
24
|
+
// existing workspace or go to onboarding instead of rendering an empty shell.
|
|
27
25
|
if (!auth.tenant_id) {
|
|
28
26
|
return <ProvisionWorkspace />;
|
|
29
27
|
}
|
|
30
28
|
// Every read is STARTED here, before the first use(). Each serverData call
|
|
31
29
|
// returns a thenable the handle caches by key, and use() suspends on the
|
|
32
|
-
// first one still pending
|
|
33
|
-
// for the last.
|
|
34
|
-
// is one, and the replayed render finds each already cached.
|
|
30
|
+
// first one still pending, so reading them one at a time makes each wait
|
|
31
|
+
// for the last. Issued together they overlap.
|
|
35
32
|
//
|
|
36
33
|
// Do NOT reach for Promise.all. It builds a new, pending, uncached promise
|
|
37
34
|
// on every render, so use() suspends, re-renders, builds another, and the
|
|
38
|
-
// page never returns
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
// The onboarding redirect below only needs `org`, so on that path the other
|
|
42
|
-
// reads are issued and thrown away. A redirect is the rare case; paying one
|
|
43
|
-
// round trip on every normal load to save four on a rare one is the wrong
|
|
44
|
-
// way round.
|
|
45
|
-
const mePromise = serverData.get<{ email?: string }>("User", auth.user_id!);
|
|
35
|
+
// page never returns (React error #482).
|
|
36
|
+
const mePromise = serverData.get<{ email?: string; displayName?: string | null }>("User", auth.user_id!);
|
|
46
37
|
const orgPromise = serverData.get<{
|
|
47
38
|
name?: string;
|
|
48
39
|
onboardedAt?: string | null;
|
|
49
40
|
setupDismissedAt?: string | null;
|
|
50
41
|
}>("Org", auth.tenant_id);
|
|
51
42
|
const projectsPromise = serverData.list<Project>("Project");
|
|
43
|
+
const tasksPromise = serverData.list<TaskRow>("Task");
|
|
52
44
|
const membersPromise = serverData.list<OrgMemberRow>("OrgMember");
|
|
53
45
|
const subsPromise = serverData.list<Subscription>("StripeSubscription");
|
|
54
46
|
const invitesPromise =
|
|
@@ -65,14 +57,13 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
|
|
|
65
57
|
response.redirect("/onboarding");
|
|
66
58
|
return null;
|
|
67
59
|
}
|
|
68
|
-
const projects = use(projectsPromise);
|
|
60
|
+
const projects = use(projectsPromise).filter((p) => p.orgId === auth.tenant_id);
|
|
61
|
+
const tasks = use(tasksPromise).filter((t) => t.orgId === auth.tenant_id);
|
|
69
62
|
// The OrgMember read policy returns this user's memberships across every org,
|
|
70
63
|
// so scope the count to the active workspace.
|
|
71
|
-
const memberCount = use(membersPromise).filter(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Active-plan badge from the workspace's Stripe subscription (Free until one
|
|
75
|
-
// exists). Scoped to the active tenant by the plugin's read policy.
|
|
64
|
+
const memberCount = use(membersPromise).filter((m) => m.orgId === auth.tenant_id).length;
|
|
65
|
+
// Plan from the workspace's Stripe subscription (Free until one exists).
|
|
66
|
+
// Scoped to the active tenant by the plugin's read policy.
|
|
76
67
|
const active = use(subsPromise).find((s) =>
|
|
77
68
|
["active", "trialing", "past_due"].includes(s.status),
|
|
78
69
|
);
|
|
@@ -92,13 +83,16 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
|
|
|
92
83
|
};
|
|
93
84
|
return (
|
|
94
85
|
<Overview
|
|
95
|
-
setup={setup}
|
|
96
86
|
tenantId={auth.tenant_id}
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
userId={auth.user_id!}
|
|
88
|
+
firstName={firstName({ displayName: me?.displayName, email: me?.email })}
|
|
99
89
|
projects={projects}
|
|
90
|
+
tasks={tasks}
|
|
100
91
|
memberCount={memberCount}
|
|
101
92
|
plan={plan}
|
|
93
|
+
today={isoDay(new Date())}
|
|
94
|
+
canSeed={manages}
|
|
95
|
+
setup={setup}
|
|
102
96
|
/>
|
|
103
97
|
);
|
|
104
98
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React, { use } from "react";
|
|
2
|
+
import { type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
+
import { Board } from "../../board-client";
|
|
4
|
+
import type { Project } from "../../projects-client";
|
|
5
|
+
import { isoDay, type TaskRow } from "@/lib/tasks";
|
|
6
|
+
|
|
7
|
+
export const metadata: Metadata = {
|
|
8
|
+
title: "Project — Acme",
|
|
9
|
+
robots: "noindex",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// `/dashboard/projects/:id` — one project's task board. The Project read
|
|
13
|
+
// policy only returns rows from the active workspace, so an id from another
|
|
14
|
+
// workspace resolves to null and becomes a real 404.
|
|
15
|
+
export default function ProjectBoardPage({ auth, params, response, serverData }: PageProps<{ id: string }>) {
|
|
16
|
+
if (!auth.tenant_id) {
|
|
17
|
+
response.redirect("/dashboard");
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const projectPromise = serverData.get<Project>("Project", params.id);
|
|
21
|
+
const tasksPromise = serverData.query<TaskRow>("Task", { projectId: params.id });
|
|
22
|
+
const project = use(projectPromise);
|
|
23
|
+
if (!project || project.orgId !== auth.tenant_id) {
|
|
24
|
+
response.notFound();
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const tasks = use(tasksPromise).filter((t) => t.orgId === auth.tenant_id);
|
|
28
|
+
return <Board tenantId={auth.tenant_id} project={project} tasks={tasks} today={isoDay(new Date())} />;
|
|
29
|
+
}
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import React, { use } from "react";
|
|
2
2
|
import { type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
-
import {
|
|
3
|
+
import { ProjectsPage, type Project } from "../projects-client";
|
|
4
|
+
import type { TaskRow } from "@/lib/tasks";
|
|
4
5
|
|
|
5
6
|
export const metadata: Metadata = {
|
|
6
7
|
title: "Projects — Acme",
|
|
7
8
|
robots: "noindex",
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
// `/dashboard/projects` —
|
|
11
|
-
//
|
|
12
|
-
// from the dashboard layout.
|
|
13
|
-
export default function
|
|
11
|
+
// `/dashboard/projects` — the workspace's projects with task progress,
|
|
12
|
+
// resolved on the server and kept live on the client. Auth gate and shell
|
|
13
|
+
// chrome come from the dashboard layout.
|
|
14
|
+
export default function Page({ auth, response, serverData }: PageProps) {
|
|
14
15
|
if (!auth.tenant_id) {
|
|
15
16
|
response.redirect("/dashboard");
|
|
16
17
|
return null;
|
|
17
18
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
19
|
+
const projectsPromise = serverData.list<Project>("Project");
|
|
20
|
+
const tasksPromise = serverData.list<TaskRow>("Task");
|
|
21
|
+
const projects = use(projectsPromise).filter((p) => p.orgId === auth.tenant_id);
|
|
22
|
+
const tasks = use(tasksPromise).filter((t) => t.orgId === auth.tenant_id);
|
|
23
|
+
return <ProjectsPage tenantId={auth.tenant_id} projects={projects} tasks={tasks} />;
|
|
20
24
|
}
|