@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,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useState } from "react";
|
|
4
|
-
import {
|
|
4
|
+
import { callFn } from "@pylonsync/react";
|
|
5
5
|
import {
|
|
6
6
|
createInvite,
|
|
7
7
|
deleteOrg,
|
|
@@ -15,16 +15,10 @@ import {
|
|
|
15
15
|
type OrgMember,
|
|
16
16
|
type PendingInvite,
|
|
17
17
|
} from "@pylonsync/client";
|
|
18
|
+
import { Mail } from "lucide-react";
|
|
18
19
|
import { Button } from "@/components/ui/button";
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
DialogContent,
|
|
22
|
-
DialogDescription,
|
|
23
|
-
DialogFooter,
|
|
24
|
-
DialogHeader,
|
|
25
|
-
DialogTitle,
|
|
26
|
-
} from "@/components/ui/dialog";
|
|
27
|
-
import { SetupChecklist, type SetupState } from "@/components/setup-checklist";
|
|
20
|
+
import { Avatar } from "@/components/task-ui";
|
|
21
|
+
import { personName } from "@/lib/names";
|
|
28
22
|
import {
|
|
29
23
|
FREE_PROJECT_LIMIT,
|
|
30
24
|
TRIAL_DAYS,
|
|
@@ -32,28 +26,7 @@ import {
|
|
|
32
26
|
formatPrice,
|
|
33
27
|
planById,
|
|
34
28
|
} from "@/lib/plans";
|
|
35
|
-
import {
|
|
36
|
-
FolderKanban,
|
|
37
|
-
Users2,
|
|
38
|
-
CreditCard,
|
|
39
|
-
ArrowRight,
|
|
40
|
-
FolderPlus,
|
|
41
|
-
Plus,
|
|
42
|
-
Pencil,
|
|
43
|
-
Archive,
|
|
44
|
-
ArchiveRestore,
|
|
45
|
-
Trash2,
|
|
46
|
-
Check,
|
|
47
|
-
} from "lucide-react";
|
|
48
|
-
|
|
49
|
-
export interface Project {
|
|
50
|
-
id: string;
|
|
51
|
-
orgId: string;
|
|
52
|
-
name: string;
|
|
53
|
-
description?: string;
|
|
54
|
-
status?: string;
|
|
55
|
-
createdAt: string;
|
|
56
|
-
}
|
|
29
|
+
import { inputCls } from "./projects-client";
|
|
57
30
|
|
|
58
31
|
// OrgMember rows as returned by `serverData.list("OrgMember")` (the entity
|
|
59
32
|
// shape — camelCase fields). The read policy returns the caller's memberships
|
|
@@ -101,566 +74,13 @@ function Card({
|
|
|
101
74
|
children: React.ReactNode;
|
|
102
75
|
}) {
|
|
103
76
|
return (
|
|
104
|
-
<
|
|
105
|
-
<
|
|
106
|
-
<h2 className="text-
|
|
77
|
+
<section className="rounded-xl border border-zinc-200/80 bg-white">
|
|
78
|
+
<header className="flex items-center justify-between gap-3 border-b border-zinc-100 px-5 py-3">
|
|
79
|
+
<h2 className="text-[13.5px] font-semibold text-zinc-900">{title}</h2>
|
|
107
80
|
{action}
|
|
108
|
-
</
|
|
109
|
-
<div className="
|
|
110
|
-
</
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// A stat tile: icon + label, a big value, and a small context line.
|
|
115
|
-
function StatCard({
|
|
116
|
-
icon,
|
|
117
|
-
label,
|
|
118
|
-
value,
|
|
119
|
-
hint,
|
|
120
|
-
}: {
|
|
121
|
-
icon: React.ReactNode;
|
|
122
|
-
label: string;
|
|
123
|
-
value: React.ReactNode;
|
|
124
|
-
hint?: React.ReactNode;
|
|
125
|
-
}) {
|
|
126
|
-
return (
|
|
127
|
-
<div className="rounded-xl border border-zinc-200 bg-white p-4">
|
|
128
|
-
<div className="flex items-center gap-2 text-zinc-400">
|
|
129
|
-
{icon}
|
|
130
|
-
<span className="text-[11px] font-medium uppercase tracking-wide">
|
|
131
|
-
{label}
|
|
132
|
-
</span>
|
|
133
|
-
</div>
|
|
134
|
-
<div className="mt-2 text-2xl font-semibold text-zinc-900">{value}</div>
|
|
135
|
-
{hint != null && <div className="mt-0.5 text-xs text-zinc-400">{hint}</div>}
|
|
136
|
-
</div>
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Deterministic, timezone-independent date (UTC parts) so the SSR render and
|
|
141
|
-
// client hydration always agree — a locale/tz-dependent format would mismatch
|
|
142
|
-
// and throw a React #418 hydration error.
|
|
143
|
-
const MONTHS = [
|
|
144
|
-
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
145
|
-
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
146
|
-
];
|
|
147
|
-
function fmtDate(iso: string) {
|
|
148
|
-
const d = new Date(iso);
|
|
149
|
-
if (Number.isNaN(d.getTime())) return "";
|
|
150
|
-
return `${MONTHS[d.getUTCMonth()]} ${d.getUTCDate()}, ${d.getUTCFullYear()}`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// A stable accent per project (hashed from its id) so each card/monogram gets a
|
|
154
|
-
// consistent color without persisting one.
|
|
155
|
-
const ACCENTS = [
|
|
156
|
-
"bg-blue-50 text-blue-600",
|
|
157
|
-
"bg-violet-50 text-violet-600",
|
|
158
|
-
"bg-emerald-50 text-emerald-600",
|
|
159
|
-
"bg-amber-50 text-amber-600",
|
|
160
|
-
"bg-rose-50 text-rose-600",
|
|
161
|
-
"bg-cyan-50 text-cyan-600",
|
|
162
|
-
];
|
|
163
|
-
function accentFor(id: string) {
|
|
164
|
-
let h = 0;
|
|
165
|
-
for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) >>> 0;
|
|
166
|
-
return ACCENTS[h % ACCENTS.length];
|
|
167
|
-
}
|
|
168
|
-
function monogram(name: string) {
|
|
169
|
-
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
170
|
-
const s =
|
|
171
|
-
parts.length >= 2 ? parts[0][0] + parts[1][0] : name.trim()[0] ?? "?";
|
|
172
|
-
return s.toUpperCase();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function StatusPill({ status }: { status: string }) {
|
|
176
|
-
const archived = status === "archived";
|
|
177
|
-
return (
|
|
178
|
-
<span
|
|
179
|
-
className={
|
|
180
|
-
"inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-[11px] font-medium " +
|
|
181
|
-
(archived
|
|
182
|
-
? "bg-zinc-100 text-zinc-500"
|
|
183
|
-
: "bg-emerald-50 text-emerald-600")
|
|
184
|
-
}
|
|
185
|
-
>
|
|
186
|
-
<span
|
|
187
|
-
className={
|
|
188
|
-
"size-1.5 rounded-full " +
|
|
189
|
-
(archived ? "bg-zinc-400" : "bg-emerald-500")
|
|
190
|
-
}
|
|
191
|
-
/>
|
|
192
|
-
{archived ? "Archived" : "Active"}
|
|
193
|
-
</span>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const inputCls =
|
|
198
|
-
"h-9 w-full rounded-md border border-zinc-300 bg-white px-3 text-sm text-zinc-900 outline-none transition placeholder:text-zinc-400 focus:border-zinc-900 focus:ring-2 focus:ring-zinc-900/10";
|
|
199
|
-
|
|
200
|
-
/* ============================ Overview ============================ */
|
|
201
|
-
|
|
202
|
-
export function Overview({
|
|
203
|
-
tenantId,
|
|
204
|
-
orgName,
|
|
205
|
-
userEmail,
|
|
206
|
-
projects,
|
|
207
|
-
memberCount,
|
|
208
|
-
plan,
|
|
209
|
-
setup,
|
|
210
|
-
}: {
|
|
211
|
-
tenantId: string | null;
|
|
212
|
-
orgName?: string;
|
|
213
|
-
userEmail?: string;
|
|
214
|
-
projects: Project[];
|
|
215
|
-
memberCount: number;
|
|
216
|
-
plan: string;
|
|
217
|
-
/** Getting-started state; null once the checklist is dismissed. */
|
|
218
|
-
setup?: SetupState | null;
|
|
219
|
-
}) {
|
|
220
|
-
if (!tenantId) return <NoOrg />;
|
|
221
|
-
const activeCount = projects.filter(
|
|
222
|
-
(p) => (p.status ?? "active") !== "archived",
|
|
223
|
-
).length;
|
|
224
|
-
const archivedCount = projects.length - activeCount;
|
|
225
|
-
const recent = [...projects]
|
|
226
|
-
.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))
|
|
227
|
-
.slice(0, 5);
|
|
228
|
-
const firstName = (userEmail ?? "").split("@")[0];
|
|
229
|
-
|
|
230
|
-
return (
|
|
231
|
-
<div className="space-y-6">
|
|
232
|
-
<div>
|
|
233
|
-
<h1 className="text-xl font-semibold tracking-tight text-zinc-900">
|
|
234
|
-
{orgName ? `${orgName} overview` : "Overview"}
|
|
235
|
-
</h1>
|
|
236
|
-
<p className="mt-1 text-sm text-zinc-500">
|
|
237
|
-
{firstName ? `Welcome back, ${firstName}. ` : ""}Here's what's
|
|
238
|
-
happening in your workspace.
|
|
239
|
-
</p>
|
|
240
|
-
</div>
|
|
241
|
-
|
|
242
|
-
{setup ? <SetupChecklist state={setup} /> : null}
|
|
243
|
-
|
|
244
|
-
<div className="grid gap-4 sm:grid-cols-3">
|
|
245
|
-
<StatCard
|
|
246
|
-
icon={<FolderKanban className="size-4" />}
|
|
247
|
-
label="Projects"
|
|
248
|
-
value={activeCount}
|
|
249
|
-
hint={archivedCount > 0 ? `${archivedCount} archived` : "active"}
|
|
250
|
-
/>
|
|
251
|
-
<StatCard
|
|
252
|
-
icon={<Users2 className="size-4" />}
|
|
253
|
-
label="Members"
|
|
254
|
-
value={memberCount}
|
|
255
|
-
hint={memberCount === 1 ? "just you" : "in this workspace"}
|
|
256
|
-
/>
|
|
257
|
-
<StatCard
|
|
258
|
-
icon={<CreditCard className="size-4" />}
|
|
259
|
-
label="Plan"
|
|
260
|
-
value={<span className="capitalize">{plan}</span>}
|
|
261
|
-
hint={
|
|
262
|
-
plan === "free" ? (
|
|
263
|
-
<a
|
|
264
|
-
href="/dashboard/billing"
|
|
265
|
-
className="text-brand hover:underline"
|
|
266
|
-
>
|
|
267
|
-
Upgrade to Pro →
|
|
268
|
-
</a>
|
|
269
|
-
) : (
|
|
270
|
-
"thanks for the support"
|
|
271
|
-
)
|
|
272
|
-
}
|
|
273
|
-
/>
|
|
274
|
-
</div>
|
|
275
|
-
|
|
276
|
-
<Card
|
|
277
|
-
title="Recent projects"
|
|
278
|
-
action={
|
|
279
|
-
<a
|
|
280
|
-
href="/dashboard/projects"
|
|
281
|
-
className="inline-flex items-center gap-1 text-[13px] font-medium text-brand hover:underline"
|
|
282
|
-
>
|
|
283
|
-
View all <ArrowRight className="size-3.5" />
|
|
284
|
-
</a>
|
|
285
|
-
}
|
|
286
|
-
>
|
|
287
|
-
{recent.length === 0 ? (
|
|
288
|
-
<div className="flex flex-col items-center gap-2 py-8 text-center">
|
|
289
|
-
<span className="flex size-10 items-center justify-center rounded-xl bg-zinc-50 text-zinc-400">
|
|
290
|
-
<FolderPlus className="size-5" />
|
|
291
|
-
</span>
|
|
292
|
-
<p className="text-sm text-zinc-500">No projects yet.</p>
|
|
293
|
-
<a
|
|
294
|
-
href="/dashboard/projects"
|
|
295
|
-
className="text-[13px] font-medium text-brand hover:underline"
|
|
296
|
-
>
|
|
297
|
-
Create your first project →
|
|
298
|
-
</a>
|
|
299
|
-
</div>
|
|
300
|
-
) : (
|
|
301
|
-
<ul className="-my-1 divide-y divide-zinc-100">
|
|
302
|
-
{recent.map((p) => (
|
|
303
|
-
<li key={p.id} className="flex items-center gap-3 py-2.5">
|
|
304
|
-
<span
|
|
305
|
-
className={
|
|
306
|
-
"flex size-8 shrink-0 items-center justify-center rounded-lg text-[12px] font-semibold " +
|
|
307
|
-
accentFor(p.id)
|
|
308
|
-
}
|
|
309
|
-
>
|
|
310
|
-
{monogram(p.name)}
|
|
311
|
-
</span>
|
|
312
|
-
<div className="min-w-0 flex-1">
|
|
313
|
-
<div className="truncate text-sm font-medium text-zinc-900">
|
|
314
|
-
{p.name}
|
|
315
|
-
</div>
|
|
316
|
-
<div className="truncate text-xs text-zinc-400">
|
|
317
|
-
Created {fmtDate(p.createdAt)}
|
|
318
|
-
</div>
|
|
319
|
-
</div>
|
|
320
|
-
<StatusPill status={p.status ?? "active"} />
|
|
321
|
-
</li>
|
|
322
|
-
))}
|
|
323
|
-
</ul>
|
|
324
|
-
)}
|
|
325
|
-
</Card>
|
|
326
|
-
</div>
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/* ============================ Projects ============================ */
|
|
331
|
-
|
|
332
|
-
export function Projects({
|
|
333
|
-
tenantId,
|
|
334
|
-
initial,
|
|
335
|
-
}: {
|
|
336
|
-
tenantId: string | null;
|
|
337
|
-
initial: Project[];
|
|
338
|
-
}) {
|
|
339
|
-
if (!tenantId) return <NoOrg />;
|
|
340
|
-
return <ProjectsList orgId={tenantId} initial={initial} />;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Live + optimistic, seeded from the server. `db.useQuery` is reactive
|
|
344
|
-
// (db.insert/db.delete update it instantly across tabs), but until the first
|
|
345
|
-
// server-confirmed sync settles we render the server-passed `initial` rows — so
|
|
346
|
-
// there's no flash of an empty list on load. The policy gates reads on
|
|
347
|
-
// `auth.tenantId == data.orgId`, so this is only ever this org's projects.
|
|
348
|
-
function ProjectsList({
|
|
349
|
-
orgId,
|
|
350
|
-
initial,
|
|
351
|
-
}: {
|
|
352
|
-
orgId: string;
|
|
353
|
-
initial: Project[];
|
|
354
|
-
}) {
|
|
355
|
-
const [name, setName] = useState("");
|
|
356
|
-
const [desc, setDesc] = useState("");
|
|
357
|
-
const [creating, setCreating] = useState(false);
|
|
358
|
-
const [limitHit, setLimitHit] = useState(false);
|
|
359
|
-
const [createError, setCreateError] = useState<string | null>(null);
|
|
360
|
-
// Render the server-seeded `initial` rows on the server AND the client's
|
|
361
|
-
// first paint, then swap to the live reactive query once mounted. Gating the
|
|
362
|
-
// swap on a post-mount flag (rather than `loading`, which is already settled
|
|
363
|
-
// during SSR) keeps the server HTML and the first client render identical —
|
|
364
|
-
// no hydration mismatch, and still no empty-state flash.
|
|
365
|
-
const [hydrated, setHydrated] = useState(false);
|
|
366
|
-
useEffect(() => setHydrated(true), []);
|
|
367
|
-
const { data, loading } = db.useQuery<Project>("Project");
|
|
368
|
-
const rows =
|
|
369
|
-
!hydrated || loading ? initial : data.filter((p) => p.orgId === orgId);
|
|
370
|
-
const projects = rows
|
|
371
|
-
.slice()
|
|
372
|
-
.sort((a, b) => {
|
|
373
|
-
// Active first, then newest first.
|
|
374
|
-
const aa = (a.status ?? "active") === "archived" ? 1 : 0;
|
|
375
|
-
const bb = (b.status ?? "active") === "archived" ? 1 : 0;
|
|
376
|
-
if (aa !== bb) return aa - bb;
|
|
377
|
-
return a.createdAt < b.createdAt ? 1 : -1;
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
// Creates go through the `createProject` function: it enforces the free
|
|
381
|
-
// plan's cap on the server and answers LIMIT_REACHED at the cap, which
|
|
382
|
-
// opens the upgrade dialog. The new row arrives through sync.
|
|
383
|
-
async function add(e: React.FormEvent) {
|
|
384
|
-
e.preventDefault();
|
|
385
|
-
const value = name.trim();
|
|
386
|
-
if (!value) return;
|
|
387
|
-
const description = desc.trim();
|
|
388
|
-
setCreating(true);
|
|
389
|
-
setCreateError(null);
|
|
390
|
-
try {
|
|
391
|
-
await callFn("createProject", {
|
|
392
|
-
orgId,
|
|
393
|
-
name: value,
|
|
394
|
-
...(description ? { description } : {}),
|
|
395
|
-
});
|
|
396
|
-
setName("");
|
|
397
|
-
setDesc("");
|
|
398
|
-
} catch (err) {
|
|
399
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
400
|
-
if (/LIMIT_REACHED/.test(msg) || (err as { code?: string })?.code === "LIMIT_REACHED") {
|
|
401
|
-
setLimitHit(true);
|
|
402
|
-
} else {
|
|
403
|
-
setCreateError(msg.replace(/^[A-Z_]+:\s*/, ""));
|
|
404
|
-
}
|
|
405
|
-
} finally {
|
|
406
|
-
setCreating(false);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return (
|
|
411
|
-
<div className="space-y-5">
|
|
412
|
-
<div className="rounded-xl border border-zinc-200 bg-white p-4">
|
|
413
|
-
<form
|
|
414
|
-
onSubmit={add}
|
|
415
|
-
className="flex flex-col gap-2 sm:flex-row sm:items-center"
|
|
416
|
-
>
|
|
417
|
-
<input
|
|
418
|
-
value={name}
|
|
419
|
-
onChange={(e) => setName(e.target.value)}
|
|
420
|
-
placeholder="Project name"
|
|
421
|
-
aria-label="Project name"
|
|
422
|
-
className={inputCls + " sm:flex-1"}
|
|
423
|
-
/>
|
|
424
|
-
<input
|
|
425
|
-
value={desc}
|
|
426
|
-
onChange={(e) => setDesc(e.target.value)}
|
|
427
|
-
placeholder="Short description (optional)"
|
|
428
|
-
aria-label="Project description"
|
|
429
|
-
className={inputCls + " sm:flex-1"}
|
|
430
|
-
/>
|
|
431
|
-
<Button type="submit" size="sm" className="shrink-0" disabled={creating}>
|
|
432
|
-
<Plus className="size-4" /> {creating ? "Creating…" : "New project"}
|
|
433
|
-
</Button>
|
|
434
|
-
</form>
|
|
435
|
-
{createError ? (
|
|
436
|
-
<p className="mt-2 text-xs text-red-600">{createError}</p>
|
|
437
|
-
) : (
|
|
438
|
-
<p className="mt-2 text-xs text-zinc-400">
|
|
439
|
-
Tenant-scoped + live — only this workspace's projects (enforced by
|
|
440
|
-
policy), and every change syncs across tabs instantly.
|
|
441
|
-
</p>
|
|
442
|
-
)}
|
|
443
|
-
</div>
|
|
444
|
-
|
|
445
|
-
<UpgradeDialog open={limitHit} onClose={() => setLimitHit(false)} />
|
|
446
|
-
|
|
447
|
-
{projects.length === 0 ? (
|
|
448
|
-
<div className="flex flex-col items-center gap-2 rounded-xl border border-dashed border-zinc-300 py-14 text-center">
|
|
449
|
-
<span className="flex size-11 items-center justify-center rounded-xl bg-zinc-50 text-zinc-400">
|
|
450
|
-
<FolderPlus className="size-5" />
|
|
451
|
-
</span>
|
|
452
|
-
<p className="text-sm font-medium text-zinc-700">No projects yet</p>
|
|
453
|
-
<p className="text-xs text-zinc-400">
|
|
454
|
-
Create your first project above to get started.
|
|
455
|
-
</p>
|
|
456
|
-
</div>
|
|
457
|
-
) : (
|
|
458
|
-
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
|
459
|
-
{projects.map((p) => (
|
|
460
|
-
<ProjectCard key={p.id} p={p} />
|
|
461
|
-
))}
|
|
462
|
-
</div>
|
|
463
|
-
)}
|
|
464
|
-
</div>
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Shown when `createProject` answers LIMIT_REACHED. One action: go to
|
|
469
|
-
// Billing, where the trial starts.
|
|
470
|
-
function UpgradeDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|
471
|
-
const pro = planById("pro")!;
|
|
472
|
-
return (
|
|
473
|
-
<Dialog open={open} onOpenChange={(o) => !o && onClose()}>
|
|
474
|
-
<DialogContent>
|
|
475
|
-
<DialogHeader>
|
|
476
|
-
<DialogTitle>You've reached the free plan's limit</DialogTitle>
|
|
477
|
-
<DialogDescription>
|
|
478
|
-
Free workspaces can have {FREE_PROJECT_LIMIT} active projects. Archive one, or
|
|
479
|
-
upgrade to Pro for unlimited projects. Pro starts with a {TRIAL_DAYS}-day free
|
|
480
|
-
trial.
|
|
481
|
-
</DialogDescription>
|
|
482
|
-
</DialogHeader>
|
|
483
|
-
<ul className="space-y-1.5 text-sm text-zinc-700">
|
|
484
|
-
{pro.features.map((f) => (
|
|
485
|
-
<li key={f} className="flex gap-2">
|
|
486
|
-
<span className="text-zinc-900">✓</span> {f}
|
|
487
|
-
</li>
|
|
488
|
-
))}
|
|
489
|
-
</ul>
|
|
490
|
-
<DialogFooter>
|
|
491
|
-
<Button type="button" variant="outline" onClick={onClose}>
|
|
492
|
-
Not now
|
|
493
|
-
</Button>
|
|
494
|
-
<a
|
|
495
|
-
href="/dashboard/billing?upgrade=pro&interval=annual"
|
|
496
|
-
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"
|
|
497
|
-
>
|
|
498
|
-
{pro.cta}
|
|
499
|
-
</a>
|
|
500
|
-
</DialogFooter>
|
|
501
|
-
</DialogContent>
|
|
502
|
-
</Dialog>
|
|
503
|
-
);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function IconBtn({
|
|
507
|
-
label,
|
|
508
|
-
danger,
|
|
509
|
-
onClick,
|
|
510
|
-
children,
|
|
511
|
-
}: {
|
|
512
|
-
label: string;
|
|
513
|
-
danger?: boolean;
|
|
514
|
-
onClick: () => void;
|
|
515
|
-
children: React.ReactNode;
|
|
516
|
-
}) {
|
|
517
|
-
return (
|
|
518
|
-
<button
|
|
519
|
-
type="button"
|
|
520
|
-
aria-label={label}
|
|
521
|
-
title={label}
|
|
522
|
-
onClick={onClick}
|
|
523
|
-
className={
|
|
524
|
-
"flex size-7 items-center justify-center rounded-md text-zinc-400 transition-colors hover:bg-zinc-100 " +
|
|
525
|
-
(danger ? "hover:text-red-600" : "hover:text-zinc-700")
|
|
526
|
-
}
|
|
527
|
-
>
|
|
528
|
-
{children}
|
|
529
|
-
</button>
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// One project card. Archive + delete are optimistic client `db` writes (gated
|
|
534
|
-
// by the tenant policy) that sync across tabs; editing details saves through the
|
|
535
|
-
// updateProject server function. Editing swaps the card for an inline name +
|
|
536
|
-
// description form.
|
|
537
|
-
function ProjectCard({ p }: { p: Project }) {
|
|
538
|
-
const archived = (p.status ?? "active") === "archived";
|
|
539
|
-
const [editing, setEditing] = useState(false);
|
|
540
|
-
const [name, setName] = useState(p.name);
|
|
541
|
-
const [desc, setDesc] = useState(p.description ?? "");
|
|
542
|
-
|
|
543
|
-
async function save(e: React.FormEvent) {
|
|
544
|
-
e.preventDefault();
|
|
545
|
-
const n = name.trim();
|
|
546
|
-
if (!n) return;
|
|
547
|
-
setEditing(false);
|
|
548
|
-
// Saving details goes through the updateProject server function (server-side
|
|
549
|
-
// validation + a workspace-membership re-check) rather than a bare
|
|
550
|
-
// db.update — see functions/updateProject.ts. The reactive `db` still
|
|
551
|
-
// re-renders this card the moment the write lands.
|
|
552
|
-
await callFn("updateProject", {
|
|
553
|
-
projectId: p.id,
|
|
554
|
-
name: n,
|
|
555
|
-
description: desc.trim() || undefined,
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
function cancel() {
|
|
559
|
-
setEditing(false);
|
|
560
|
-
setName(p.name);
|
|
561
|
-
setDesc(p.description ?? "");
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
if (editing) {
|
|
565
|
-
return (
|
|
566
|
-
<form
|
|
567
|
-
onSubmit={save}
|
|
568
|
-
className="flex flex-col gap-2 rounded-xl border border-zinc-300 bg-white p-4"
|
|
569
|
-
>
|
|
570
|
-
<input
|
|
571
|
-
autoFocus
|
|
572
|
-
value={name}
|
|
573
|
-
onChange={(e) => setName(e.target.value)}
|
|
574
|
-
aria-label="Project name"
|
|
575
|
-
className={inputCls}
|
|
576
|
-
/>
|
|
577
|
-
<textarea
|
|
578
|
-
value={desc}
|
|
579
|
-
onChange={(e) => setDesc(e.target.value)}
|
|
580
|
-
rows={2}
|
|
581
|
-
placeholder="Add a description…"
|
|
582
|
-
aria-label="Project description"
|
|
583
|
-
className={inputCls + " h-auto resize-none py-2"}
|
|
584
|
-
/>
|
|
585
|
-
<div className="flex items-center gap-2">
|
|
586
|
-
<Button type="submit" size="sm">
|
|
587
|
-
<Check className="size-3.5" /> Save
|
|
588
|
-
</Button>
|
|
589
|
-
<button
|
|
590
|
-
type="button"
|
|
591
|
-
onClick={cancel}
|
|
592
|
-
className="text-[13px] font-medium text-zinc-500 transition-colors hover:text-zinc-800"
|
|
593
|
-
>
|
|
594
|
-
Cancel
|
|
595
|
-
</button>
|
|
596
|
-
</div>
|
|
597
|
-
</form>
|
|
598
|
-
);
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
return (
|
|
602
|
-
<div
|
|
603
|
-
className={
|
|
604
|
-
"flex flex-col rounded-xl border border-zinc-200 bg-white p-4 transition-colors hover:border-zinc-300 " +
|
|
605
|
-
(archived ? "opacity-60" : "")
|
|
606
|
-
}
|
|
607
|
-
>
|
|
608
|
-
<div className="flex items-start gap-3">
|
|
609
|
-
<span
|
|
610
|
-
className={
|
|
611
|
-
"flex size-9 shrink-0 items-center justify-center rounded-lg text-sm font-semibold " +
|
|
612
|
-
accentFor(p.id)
|
|
613
|
-
}
|
|
614
|
-
>
|
|
615
|
-
{monogram(p.name)}
|
|
616
|
-
</span>
|
|
617
|
-
<div className="min-w-0 flex-1">
|
|
618
|
-
<div className="truncate text-sm font-semibold text-zinc-900">
|
|
619
|
-
{p.name}
|
|
620
|
-
</div>
|
|
621
|
-
<p
|
|
622
|
-
className={
|
|
623
|
-
"mt-0.5 line-clamp-2 text-xs " +
|
|
624
|
-
(p.description ? "text-zinc-500" : "text-zinc-300")
|
|
625
|
-
}
|
|
626
|
-
>
|
|
627
|
-
{p.description || "No description"}
|
|
628
|
-
</p>
|
|
629
|
-
</div>
|
|
630
|
-
<div className="-mr-1 flex shrink-0 items-center">
|
|
631
|
-
<IconBtn label="Edit project" onClick={() => setEditing(true)}>
|
|
632
|
-
<Pencil className="size-3.5" />
|
|
633
|
-
</IconBtn>
|
|
634
|
-
<IconBtn
|
|
635
|
-
label={archived ? "Unarchive project" : "Archive project"}
|
|
636
|
-
onClick={() =>
|
|
637
|
-
db.update("Project", p.id, {
|
|
638
|
-
status: archived ? "active" : "archived",
|
|
639
|
-
})
|
|
640
|
-
}
|
|
641
|
-
>
|
|
642
|
-
{archived ? (
|
|
643
|
-
<ArchiveRestore className="size-3.5" />
|
|
644
|
-
) : (
|
|
645
|
-
<Archive className="size-3.5" />
|
|
646
|
-
)}
|
|
647
|
-
</IconBtn>
|
|
648
|
-
<IconBtn
|
|
649
|
-
label="Delete project"
|
|
650
|
-
danger
|
|
651
|
-
onClick={() => db.delete("Project", p.id)}
|
|
652
|
-
>
|
|
653
|
-
<Trash2 className="size-3.5" />
|
|
654
|
-
</IconBtn>
|
|
655
|
-
</div>
|
|
656
|
-
</div>
|
|
657
|
-
<div className="mt-4 flex items-center justify-between">
|
|
658
|
-
<StatusPill status={p.status ?? "active"} />
|
|
659
|
-
<span className="text-[11px] text-zinc-400">
|
|
660
|
-
Created {fmtDate(p.createdAt)}
|
|
661
|
-
</span>
|
|
662
|
-
</div>
|
|
663
|
-
</div>
|
|
81
|
+
</header>
|
|
82
|
+
<div className="px-5 py-4">{children}</div>
|
|
83
|
+
</section>
|
|
664
84
|
);
|
|
665
85
|
}
|
|
666
86
|
|
|
@@ -730,10 +150,17 @@ function MembersList({
|
|
|
730
150
|
const [inviting, setInviting] = useState(false);
|
|
731
151
|
|
|
732
152
|
async function load() {
|
|
733
|
-
|
|
153
|
+
const roster = await listOrgMembers(orgId);
|
|
154
|
+
setMembers(roster);
|
|
734
155
|
// Pending invites are admin-gated server-side; only fetch when we'd be
|
|
735
|
-
// allowed to see them (avoids a guaranteed 403 for plain members).
|
|
736
|
-
|
|
156
|
+
// allowed to see them (avoids a guaranteed 403 for plain members). The
|
|
157
|
+
// invites endpoint also returns invites that were already accepted, so
|
|
158
|
+
// drop any whose email already belongs to a member.
|
|
159
|
+
if (canManage) {
|
|
160
|
+
const joined = new Set(roster.map((m) => (m.email ?? "").toLowerCase()));
|
|
161
|
+
const list = await listInvites(orgId);
|
|
162
|
+
setInvites(list.filter((i) => !joined.has(i.email.toLowerCase())));
|
|
163
|
+
}
|
|
737
164
|
}
|
|
738
165
|
useEffect(() => {
|
|
739
166
|
void load();
|
|
@@ -793,7 +220,7 @@ function MembersList({
|
|
|
793
220
|
}
|
|
794
221
|
|
|
795
222
|
return (
|
|
796
|
-
<div className="space-y-6">
|
|
223
|
+
<div className="mx-auto max-w-3xl space-y-6">
|
|
797
224
|
<Card title="Members" action={members ? <Count n={members.length} /> : null}>
|
|
798
225
|
{canManage && (
|
|
799
226
|
<form onSubmit={invite} className="flex items-center gap-2">
|
|
@@ -823,21 +250,18 @@ function MembersList({
|
|
|
823
250
|
</li>
|
|
824
251
|
))
|
|
825
252
|
: members.map((m) => {
|
|
826
|
-
const label = m.name
|
|
827
|
-
const initial = (label.trim()[0] || "?").toUpperCase();
|
|
253
|
+
const label = personName({ name: m.name, email: m.email }) || "Unknown member";
|
|
828
254
|
const isMe = m.user_id === currentUserId;
|
|
829
255
|
return (
|
|
830
256
|
<li
|
|
831
257
|
key={m.user_id}
|
|
832
258
|
className="flex items-center gap-3 py-2.5"
|
|
833
259
|
>
|
|
834
|
-
<
|
|
835
|
-
{initial}
|
|
836
|
-
</span>
|
|
260
|
+
<Avatar id={m.user_id} name={label} size="md" />
|
|
837
261
|
<div className="min-w-0 flex-1">
|
|
838
262
|
<div className="flex items-center gap-2">
|
|
839
263
|
<span className="truncate text-sm font-medium text-zinc-900">
|
|
840
|
-
{
|
|
264
|
+
{label}
|
|
841
265
|
</span>
|
|
842
266
|
{isMe && (
|
|
843
267
|
<span className="rounded bg-zinc-100 px-1.5 py-0.5 text-[10px] font-medium text-zinc-500">
|
|
@@ -845,7 +269,7 @@ function MembersList({
|
|
|
845
269
|
</span>
|
|
846
270
|
)}
|
|
847
271
|
</div>
|
|
848
|
-
{m.
|
|
272
|
+
{m.email && label !== m.email && (
|
|
849
273
|
<div className="truncate text-xs text-zinc-500">
|
|
850
274
|
{m.email}
|
|
851
275
|
</div>
|
|
@@ -897,7 +321,7 @@ function MembersList({
|
|
|
897
321
|
{invites.map((inv) => (
|
|
898
322
|
<li key={inv.id} className="flex items-center gap-3 py-2.5">
|
|
899
323
|
<span className="flex size-8 shrink-0 items-center justify-center rounded-full border border-dashed border-zinc-300 text-zinc-400">
|
|
900
|
-
<
|
|
324
|
+
<Mail className="size-3.5" strokeWidth={1.75} />
|
|
901
325
|
</span>
|
|
902
326
|
<div className="min-w-0 flex-1">
|
|
903
327
|
<div className="truncate text-sm font-medium text-zinc-900">
|
|
@@ -924,15 +348,6 @@ function MembersList({
|
|
|
924
348
|
);
|
|
925
349
|
}
|
|
926
350
|
|
|
927
|
-
function MailIcon() {
|
|
928
|
-
return (
|
|
929
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
930
|
-
<rect x="3" y="5" width="18" height="14" rx="2" />
|
|
931
|
-
<path d="m3 7 9 6 9-6" />
|
|
932
|
-
</svg>
|
|
933
|
-
);
|
|
934
|
-
}
|
|
935
|
-
|
|
936
351
|
// PendingInvite.expires_at is unix SECONDS; formatDate() wants an ISO string.
|
|
937
352
|
function unixToIso(sec: number) {
|
|
938
353
|
return new Date(sec * 1000).toISOString();
|
|
@@ -1040,7 +455,7 @@ function AccountSettings({ me }: { me: AccountInfo }) {
|
|
|
1040
455
|
}
|
|
1041
456
|
|
|
1042
457
|
return (
|
|
1043
|
-
<div className="mt-6 max-w-2xl space-y-6">
|
|
458
|
+
<div className="mx-auto mt-6 max-w-2xl space-y-6">
|
|
1044
459
|
<Card title="Your account">
|
|
1045
460
|
<form onSubmit={saveName} className="space-y-3">
|
|
1046
461
|
<label className="block">
|
|
@@ -1166,7 +581,7 @@ function SettingsView({
|
|
|
1166
581
|
}
|
|
1167
582
|
|
|
1168
583
|
return (
|
|
1169
|
-
<div className="max-w-2xl space-y-6">
|
|
584
|
+
<div className="mx-auto max-w-2xl space-y-6">
|
|
1170
585
|
<Card title="Workspace">
|
|
1171
586
|
<form onSubmit={rename} className="space-y-3">
|
|
1172
587
|
<label className="block">
|
|
@@ -1430,7 +845,7 @@ function BillingView({
|
|
|
1430
845
|
}
|
|
1431
846
|
|
|
1432
847
|
return (
|
|
1433
|
-
<div className="max-w-2xl space-y-6">
|
|
848
|
+
<div className="mx-auto max-w-2xl space-y-6">
|
|
1434
849
|
<Card title="Plan">
|
|
1435
850
|
<div className="flex items-center justify-between">
|
|
1436
851
|
<div>
|