@pylonsync/create-pylon 0.19.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/create-pylon.js +30 -7
- package/package.json +1 -1
- package/templates/_root/AGENTS.md +1 -1
- package/templates/agency/.env.example +7 -0
- package/templates/agency/AGENTS.md +1 -1
- package/templates/agency/README.md +28 -3
- package/templates/agency/app/(marketing)/contact-form.tsx +20 -18
- package/templates/agency/app/(marketing)/page.tsx +3 -3
- package/templates/agency/app/(marketing)/work/page.tsx +3 -3
- package/templates/agency/app/auth-form.tsx +4 -4
- package/templates/agency/app/dashboard/dashboard-client.tsx +110 -64
- package/templates/agency/app/dashboard/page.tsx +14 -16
- package/templates/agency/app/icon.svg +3 -0
- package/templates/agency/app/login/page.tsx +12 -14
- package/templates/agency/components/marketing.tsx +12 -5
- package/templates/agency/functions/seedStudioBackoffice.ts +42 -11
- package/templates/agency/lib/agency.ts +22 -0
- package/templates/agency/lib/demo.ts +118 -0
- package/templates/agency/lib/site.config.ts +19 -13
- package/templates/agency/public/favicon.ico +0 -0
- package/templates/agency/tests/agency.test.ts +57 -0
- package/templates/ai-chat/.env.example +13 -27
- package/templates/ai-chat/AGENTS.md +1 -1
- package/templates/ai-chat/README.md +53 -60
- package/templates/ai-chat/app/(chat)/c/[id]/page.tsx +19 -0
- package/templates/ai-chat/app/(chat)/chat-client.tsx +657 -622
- package/templates/ai-chat/app/(chat)/layout.tsx +6 -43
- package/templates/ai-chat/app/(chat)/not-found.tsx +6 -10
- package/templates/ai-chat/app/(chat)/page.tsx +7 -11
- package/templates/ai-chat/app/auth-form.tsx +45 -38
- package/templates/ai-chat/app/error.tsx +9 -12
- package/templates/ai-chat/app/globals.css +464 -102
- package/templates/ai-chat/app/layout.tsx +11 -19
- package/templates/ai-chat/app/login/page.tsx +39 -24
- package/templates/ai-chat/app.ts +118 -43
- package/templates/ai-chat/components/chat/composer.tsx +193 -0
- package/templates/ai-chat/components/chat/empty-state.tsx +65 -0
- package/templates/ai-chat/components/chat/icons.tsx +198 -0
- package/templates/ai-chat/components/chat/markdown.tsx +164 -0
- package/templates/ai-chat/components/chat/message.tsx +183 -0
- package/templates/ai-chat/components/chat/setup.tsx +71 -0
- package/templates/ai-chat/components/chat/sidebar.tsx +288 -0
- package/templates/ai-chat/functions/_beginTurn.ts +145 -0
- package/templates/ai-chat/functions/_finishTurn.ts +55 -0
- package/templates/ai-chat/functions/assistantStatus.ts +13 -0
- package/templates/ai-chat/functions/claimGuestHistory.ts +39 -0
- package/templates/ai-chat/functions/createConversation.ts +28 -0
- package/templates/ai-chat/functions/deleteConversation.ts +15 -20
- package/templates/ai-chat/functions/prepareGuestClaim.ts +32 -0
- package/templates/ai-chat/functions/renameConversation.ts +19 -0
- package/templates/ai-chat/functions/respond.ts +88 -0
- package/templates/ai-chat/functions/seedExamples.ts +46 -0
- package/templates/ai-chat/functions/stopResponse.ts +30 -0
- package/templates/ai-chat/lib/brand-icon.ts +7 -0
- package/templates/ai-chat/lib/chat.ts +277 -0
- package/templates/ai-chat/lib/claim.ts +15 -0
- package/templates/ai-chat/lib/examples.ts +179 -0
- package/templates/ai-chat/lib/highlight.ts +107 -0
- package/templates/ai-chat/lib/markdown.ts +317 -0
- package/templates/ai-chat/lib/provider.ts +38 -0
- package/templates/ai-chat/lib/site.config.ts +55 -39
- package/templates/ai-chat/tests/chat.test.ts +174 -0
- package/templates/ai-chat/tests/components.test.tsx +57 -0
- package/templates/ai-chat/tests/markdown.test.ts +88 -0
- package/templates/ai-chat/tests/provider.test.ts +32 -0
- package/templates/ai-studio/AGENTS.md +1 -1
- package/templates/backend/consumer/apps/api/app/layout.tsx +33 -0
- package/templates/backend/consumer/apps/api/app/page.tsx +54 -0
- package/templates/backend/consumer/apps/api/app.ts +166 -0
- package/templates/backend/consumer/apps/api/functions/addComment.ts +24 -0
- package/templates/backend/consumer/apps/api/functions/createPost.ts +19 -36
- package/templates/backend/consumer/apps/api/functions/deletePost.ts +15 -13
- package/templates/backend/consumer/apps/api/functions/myProfile.ts +5 -8
- package/templates/backend/consumer/apps/api/functions/seedDemo.ts +95 -0
- package/templates/backend/consumer/apps/api/functions/setFollow.ts +36 -0
- package/templates/backend/consumer/apps/api/functions/setLike.ts +36 -0
- package/templates/backend/consumer/apps/api/functions/upsertProfile.ts +43 -37
- package/templates/backend/consumer/apps/api/lib/seed.ts +89 -0
- package/templates/backend/consumer/apps/api/lib/social.ts +50 -0
- package/templates/backend/consumer/apps/api/package.json +8 -2
- package/templates/backend/consumer/apps/api/public/images/posts/arch.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/coffee.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/dog.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/lisbon.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/ramen.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/run.jpg +0 -0
- package/templates/backend/consumer/apps/api/public/images/posts/sunset.jpg +0 -0
- package/templates/backend/consumer/apps/api/tsconfig.json +12 -2
- package/templates/barebones/AGENTS.md +1 -1
- package/templates/chat/AGENTS.md +1 -1
- package/templates/chat/README.md +43 -21
- package/templates/chat/app/chat-client.tsx +495 -85
- package/templates/chat/app/error.tsx +1 -1
- package/templates/chat/app/globals.css +170 -54
- package/templates/chat/app/layout.tsx +12 -9
- package/templates/chat/app/not-found.tsx +4 -2
- package/templates/chat/app/page.tsx +9 -19
- package/templates/chat/app.ts +96 -17
- package/templates/chat/components/chat/avatar.tsx +45 -0
- package/templates/chat/components/chat/composer.tsx +110 -0
- package/templates/chat/components/chat/message-list.tsx +285 -0
- package/templates/chat/components/chat/sidebar.tsx +190 -0
- package/templates/chat/functions/joinChat.ts +83 -0
- package/templates/chat/functions/renameMember.ts +25 -0
- package/templates/chat/lib/chat.ts +188 -0
- package/templates/chat/lib/seed.ts +78 -0
- package/templates/chat/tests/chat.test.ts +159 -0
- package/templates/chat/tests/composer.test.tsx +54 -0
- package/templates/consumer/AGENTS.md +1 -1
- package/templates/consumer/README.md +3 -3
- package/templates/consumer/app/globals.css +10 -3
- package/templates/consumer/app/icon.svg +1 -0
- package/templates/consumer/components/social/avatar.tsx +25 -11
- package/templates/consumer/functions/seedFeed.ts +3 -3
- package/templates/consumer/lib/seed.ts +43 -85
- package/templates/consumer/public/favicon.ico +0 -0
- package/templates/consumer/public/images/posts/arch.jpg +0 -0
- package/templates/consumer/public/images/posts/coffee.jpg +0 -0
- package/templates/consumer/public/images/posts/dog.jpg +0 -0
- package/templates/consumer/public/images/posts/lisbon.jpg +0 -0
- package/templates/consumer/public/images/posts/ramen.jpg +0 -0
- package/templates/consumer/public/images/posts/run.jpg +0 -0
- package/templates/consumer/public/images/posts/sunset.jpg +0 -0
- package/templates/consumer/tests/seed.test.ts +39 -0
- package/templates/creator/.env.example +7 -0
- package/templates/creator/AGENTS.md +1 -1
- package/templates/creator/README.md +24 -5
- package/templates/creator/app/(marketing)/newsletter-signup.tsx +33 -12
- package/templates/creator/app/auth-form.tsx +7 -7
- package/templates/creator/app/dashboard/dashboard-client.tsx +137 -100
- package/templates/creator/app/dashboard/page.tsx +18 -18
- package/templates/creator/app/icon.svg +2 -0
- package/templates/creator/app/login/page.tsx +14 -14
- package/templates/creator/app.ts +4 -4
- package/templates/creator/functions/seedDemo.ts +32 -0
- package/templates/creator/functions/subscribe.ts +4 -15
- package/templates/creator/lib/demo.ts +94 -0
- package/templates/creator/lib/site.config.ts +15 -10
- package/templates/creator/lib/stats.ts +35 -0
- package/templates/creator/public/favicon.ico +0 -0
- package/templates/creator/tests/counter.test.ts +92 -0
- package/templates/creator/tests/live-count.test.tsx +45 -0
- package/templates/crm/AGENTS.md +1 -1
- package/templates/crm/README.md +22 -1
- package/templates/crm/app/apple-icon.png +0 -0
- package/templates/crm/app/companies/companies-view.tsx +32 -14
- package/templates/crm/app/companies/page.tsx +2 -1
- package/templates/crm/app/contacts/contacts-view.tsx +34 -10
- package/templates/crm/app/contacts/page.tsx +2 -1
- package/templates/crm/app/deals/[id]/deal-view.tsx +137 -98
- package/templates/crm/app/deals/[id]/page.tsx +2 -1
- package/templates/crm/app/globals.css +29 -14
- package/templates/crm/app/icon.svg +1 -0
- package/templates/crm/app/layout.tsx +7 -5
- package/templates/crm/app/page.tsx +2 -1
- package/templates/crm/app/pipeline-view.tsx +5 -5
- package/templates/crm/app/workspace.tsx +29 -17
- package/templates/crm/app.ts +5 -5
- package/templates/crm/components/activity-timeline.tsx +14 -4
- package/templates/crm/components/app-shell.tsx +94 -0
- package/templates/crm/components/auth-form.tsx +35 -5
- package/templates/crm/components/avatar.tsx +5 -1
- package/templates/crm/components/board-skeleton.tsx +5 -5
- package/templates/crm/components/brand-mark.tsx +25 -0
- package/templates/crm/components/command-palette.tsx +17 -17
- package/templates/crm/components/data-table.tsx +97 -36
- package/templates/crm/components/deal-card.tsx +23 -27
- package/templates/crm/components/filter-tabs.tsx +101 -0
- package/templates/crm/components/metrics-bar.tsx +6 -6
- package/templates/crm/components/nav.tsx +15 -0
- package/templates/crm/components/page-header.tsx +43 -7
- package/templates/crm/components/pipeline-board.tsx +16 -10
- package/templates/crm/components/require-auth.tsx +24 -26
- package/templates/crm/components/sidebar.tsx +54 -49
- package/templates/crm/components/stage-badge.tsx +4 -2
- package/templates/crm/components/ui/button.tsx +5 -4
- package/templates/crm/components/ui/dialog.tsx +1 -1
- package/templates/crm/components/ui/input.tsx +1 -1
- package/templates/crm/components/ui/select.tsx +1 -1
- package/templates/crm/components/ui/textarea.tsx +1 -1
- package/templates/crm/functions/seedWorkspace.ts +32 -17
- package/templates/crm/lib/brand.ts +9 -0
- package/templates/crm/lib/demo-team.ts +51 -0
- package/templates/crm/lib/pipeline.ts +37 -0
- package/templates/crm/lib/search.ts +4 -3
- package/templates/crm/lib/seed.ts +53 -8
- package/templates/crm/public/favicon.ico +0 -0
- package/templates/crm/tests/components.test.tsx +5 -5
- package/templates/crm/tests/demo-team.test.ts +18 -0
- package/templates/crm/tests/pipeline.test.ts +59 -3
- package/templates/directory/.env.example +7 -0
- package/templates/directory/AGENTS.md +1 -1
- package/templates/directory/README.md +22 -3
- package/templates/directory/app/(marketing)/directory-browse.tsx +30 -12
- package/templates/directory/app/(marketing)/page.tsx +6 -4
- package/templates/directory/app/auth-form.tsx +4 -4
- package/templates/directory/app/dashboard/dashboard-client.tsx +177 -90
- package/templates/directory/app/dashboard/page.tsx +16 -14
- package/templates/directory/app/icon.svg +4 -0
- package/templates/directory/app/login/page.tsx +16 -15
- package/templates/directory/functions/seedDemo.ts +43 -0
- package/templates/directory/lib/demo.ts +145 -0
- package/templates/directory/lib/directory.ts +27 -0
- package/templates/directory/lib/site.config.ts +21 -16
- package/templates/directory/public/favicon.ico +0 -0
- package/templates/directory/tests/directory.test.ts +46 -0
- package/templates/expo/consumer/apps/expo/App.tsx +360 -288
- package/templates/expo/consumer/apps/expo/app.json +9 -1
- package/templates/expo/consumer/apps/expo/package.json +2 -1
- package/templates/helpdesk/AGENTS.md +1 -1
- package/templates/helpdesk/README.md +21 -0
- package/templates/helpdesk/app/apple-icon.png +0 -0
- package/templates/helpdesk/app/customers/customers-view.tsx +23 -7
- package/templates/helpdesk/app/customers/page.tsx +2 -1
- package/templates/helpdesk/app/globals.css +29 -14
- package/templates/helpdesk/app/icon.svg +1 -0
- package/templates/helpdesk/app/inbox-view.tsx +66 -47
- package/templates/helpdesk/app/layout.tsx +7 -5
- package/templates/helpdesk/app/page.tsx +4 -7
- package/templates/helpdesk/app/tickets/[id]/page.tsx +2 -1
- package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +134 -97
- package/templates/helpdesk/app/workspace.tsx +33 -22
- package/templates/helpdesk/app.ts +2 -2
- package/templates/helpdesk/components/app-shell.tsx +94 -0
- package/templates/helpdesk/components/auth-form.tsx +35 -5
- package/templates/helpdesk/components/avatar.tsx +5 -1
- package/templates/helpdesk/components/brand-mark.tsx +28 -0
- package/templates/helpdesk/components/command-palette.tsx +17 -17
- package/templates/helpdesk/components/data-table.tsx +97 -36
- package/templates/helpdesk/components/filter-tabs.tsx +101 -0
- package/templates/helpdesk/components/nav.tsx +14 -0
- package/templates/helpdesk/components/page-header.tsx +43 -7
- package/templates/helpdesk/components/priority-badge.tsx +49 -6
- package/templates/helpdesk/components/require-auth.tsx +24 -26
- package/templates/helpdesk/components/sidebar.tsx +54 -48
- package/templates/helpdesk/components/sla-indicator.tsx +5 -8
- package/templates/helpdesk/components/thread.tsx +7 -7
- package/templates/helpdesk/components/ticket-list.tsx +79 -19
- package/templates/helpdesk/components/ui/button.tsx +5 -4
- package/templates/helpdesk/components/ui/dialog.tsx +1 -1
- package/templates/helpdesk/components/ui/input.tsx +1 -1
- package/templates/helpdesk/components/ui/select.tsx +1 -1
- package/templates/helpdesk/components/ui/textarea.tsx +1 -1
- package/templates/helpdesk/functions/seedWorkspace.ts +25 -15
- package/templates/helpdesk/lib/brand.ts +9 -0
- package/templates/helpdesk/lib/demo-team.ts +51 -0
- package/templates/helpdesk/lib/search.ts +4 -3
- package/templates/helpdesk/lib/seed.ts +43 -2
- package/templates/helpdesk/lib/tickets.ts +68 -20
- package/templates/helpdesk/public/favicon.ico +0 -0
- package/templates/helpdesk/tests/components.test.tsx +10 -0
- package/templates/helpdesk/tests/demo-team.test.ts +18 -0
- package/templates/helpdesk/tests/tickets.test.ts +70 -0
- package/templates/imessage/.env.example +49 -0
- package/templates/imessage/AGENTS.md +153 -0
- package/templates/imessage/CLAUDE.md +7 -0
- package/templates/imessage/README.md +187 -0
- package/templates/imessage/app/console.tsx +382 -0
- package/templates/imessage/app/contacts/page.tsx +13 -0
- package/templates/imessage/app/error.tsx +16 -0
- package/templates/imessage/app/gate.tsx +31 -0
- package/templates/imessage/app/globals.css +183 -0
- package/templates/imessage/app/layout.tsx +17 -0
- package/templates/imessage/app/login/login-form.tsx +115 -0
- package/templates/imessage/app/login/page.tsx +30 -0
- package/templates/imessage/app/not-found.tsx +13 -0
- package/templates/imessage/app/page.tsx +14 -0
- package/templates/imessage/app/robots.ts +6 -0
- package/templates/imessage/app/setup/page.tsx +13 -0
- package/templates/imessage/app/sign-out.tsx +20 -0
- package/templates/imessage/app.ts +299 -0
- package/templates/imessage/bunfig +4 -0
- package/templates/imessage/components/avatar.tsx +46 -0
- package/templates/imessage/components/contact-panel.tsx +263 -0
- package/templates/imessage/components/contacts-view.tsx +172 -0
- package/templates/imessage/components/conversation-list.tsx +129 -0
- package/templates/imessage/components/nav-rail.tsx +67 -0
- package/templates/imessage/components/setup-view.tsx +408 -0
- package/templates/imessage/components/thread.tsx +229 -0
- package/templates/imessage/components/ui/badge.tsx +37 -0
- package/templates/imessage/components/ui/button.tsx +56 -0
- package/templates/imessage/components/ui/card.tsx +90 -0
- package/templates/imessage/components/ui/dialog.tsx +110 -0
- package/templates/imessage/components/ui/input.tsx +30 -0
- package/templates/imessage/components/ui/label.tsx +21 -0
- package/templates/imessage/components/ui/select.tsx +39 -0
- package/templates/imessage/components/ui/table.tsx +124 -0
- package/templates/imessage/components/ui/textarea.tsx +24 -0
- package/templates/imessage/components.json +20 -0
- package/templates/imessage/functions/answerNow.ts +34 -0
- package/templates/imessage/functions/assistant.ts +96 -0
- package/templates/imessage/functions/beginTurn.ts +101 -0
- package/templates/imessage/functions/cancelReminder.ts +13 -0
- package/templates/imessage/functions/claimOutbound.ts +44 -0
- package/templates/imessage/functions/clearDemo.ts +18 -0
- package/templates/imessage/functions/deleteNote.ts +11 -0
- package/templates/imessage/functions/deliverOutbound.ts +55 -0
- package/templates/imessage/functions/finishTurn.ts +84 -0
- package/templates/imessage/functions/fireReminder.ts +42 -0
- package/templates/imessage/functions/ingestInbound.ts +22 -0
- package/templates/imessage/functions/ownerAccount.ts +13 -0
- package/templates/imessage/functions/processTurn.ts +74 -0
- package/templates/imessage/functions/relayApply.ts +102 -0
- package/templates/imessage/functions/relaySync.ts +63 -0
- package/templates/imessage/functions/releaseOutbound.ts +18 -0
- package/templates/imessage/functions/resolveTurn.ts +25 -0
- package/templates/imessage/functions/saveContact.ts +27 -0
- package/templates/imessage/functions/seedDemo.ts +70 -0
- package/templates/imessage/functions/sendOwnerMessage.ts +28 -0
- package/templates/imessage/functions/sendblueWebhook.ts +49 -0
- package/templates/imessage/functions/setContactStatus.ts +17 -0
- package/templates/imessage/functions/settleOutbound.ts +35 -0
- package/templates/imessage/functions/setupStatus.ts +33 -0
- package/templates/imessage/functions/sweep.ts +58 -0
- package/templates/imessage/functions/toolCancelReminder.ts +17 -0
- package/templates/imessage/functions/toolForgetNote.ts +15 -0
- package/templates/imessage/functions/toolListNotes.ts +16 -0
- package/templates/imessage/functions/toolListReminders.ts +17 -0
- package/templates/imessage/functions/toolSaveNote.ts +22 -0
- package/templates/imessage/functions/toolSetReminder.ts +55 -0
- package/templates/imessage/functions/updateSettings.ts +48 -0
- package/templates/imessage/gitignore +11 -0
- package/templates/imessage/lib/agent-call.ts +251 -0
- package/templates/imessage/lib/assistant.ts +74 -0
- package/templates/imessage/lib/chunk.ts +135 -0
- package/templates/imessage/lib/demo.ts +68 -0
- package/templates/imessage/lib/format.ts +170 -0
- package/templates/imessage/lib/gate.ts +104 -0
- package/templates/imessage/lib/handles.ts +67 -0
- package/templates/imessage/lib/provider.ts +38 -0
- package/templates/imessage/lib/relay-protocol.ts +95 -0
- package/templates/imessage/lib/reminders.ts +41 -0
- package/templates/imessage/lib/secrets.ts +59 -0
- package/templates/imessage/lib/sendblue.ts +173 -0
- package/templates/imessage/lib/store.ts +407 -0
- package/templates/imessage/lib/transport.ts +135 -0
- package/templates/imessage/lib/utils.ts +10 -0
- package/templates/imessage/package.json +40 -0
- package/templates/imessage/relay/.env.example +14 -0
- package/templates/imessage/relay/applescript.ts +70 -0
- package/templates/imessage/relay/chatdb.ts +165 -0
- package/templates/imessage/relay/client.ts +41 -0
- package/templates/imessage/relay/main.ts +137 -0
- package/templates/imessage/relay/state.ts +35 -0
- package/templates/imessage/tests/agent-call.test.ts +112 -0
- package/templates/imessage/tests/applescript.test.ts +88 -0
- package/templates/imessage/tests/chatdb.test.ts +139 -0
- package/templates/imessage/tests/chunk.test.ts +61 -0
- package/templates/imessage/tests/fake-ctx.ts +88 -0
- package/templates/imessage/tests/ingest.test.ts +141 -0
- package/templates/imessage/tests/lib.test.ts +99 -0
- package/templates/imessage/tests/review-fixes.test.ts +138 -0
- package/templates/imessage/tests/secrets.test.ts +36 -0
- package/templates/imessage/tests/sendblue.test.ts +143 -0
- package/templates/imessage/tests/setup.ts +5 -0
- package/templates/imessage/tsconfig.json +18 -0
- package/templates/inventory/AGENTS.md +1 -1
- package/templates/inventory/README.md +21 -0
- package/templates/inventory/app/apple-icon.png +0 -0
- package/templates/inventory/app/globals.css +29 -14
- package/templates/inventory/app/icon.svg +1 -0
- package/templates/inventory/app/layout.tsx +7 -5
- package/templates/inventory/app/movements/movements-view.tsx +32 -10
- package/templates/inventory/app/movements/page.tsx +2 -1
- package/templates/inventory/app/page.tsx +4 -6
- package/templates/inventory/app/products/[id]/page.tsx +2 -1
- package/templates/inventory/app/products/[id]/product-view.tsx +55 -25
- package/templates/inventory/app/products-view.tsx +74 -33
- package/templates/inventory/app/workspace.tsx +34 -16
- package/templates/inventory/app.ts +10 -2
- package/templates/inventory/components/app-shell.tsx +94 -0
- package/templates/inventory/components/auth-form.tsx +35 -5
- package/templates/inventory/components/avatar.tsx +5 -1
- package/templates/inventory/components/brand-mark.tsx +25 -0
- package/templates/inventory/components/category-tile.tsx +49 -0
- package/templates/inventory/components/command-palette.tsx +17 -17
- package/templates/inventory/components/data-table.tsx +97 -36
- package/templates/inventory/components/filter-tabs.tsx +101 -0
- package/templates/inventory/components/nav.tsx +14 -0
- package/templates/inventory/components/page-header.tsx +43 -7
- package/templates/inventory/components/require-auth.tsx +24 -26
- package/templates/inventory/components/sidebar.tsx +54 -48
- package/templates/inventory/components/stock-level.tsx +30 -14
- package/templates/inventory/components/summary-bar.tsx +8 -15
- package/templates/inventory/components/ui/button.tsx +5 -4
- package/templates/inventory/components/ui/dialog.tsx +1 -1
- package/templates/inventory/components/ui/input.tsx +1 -1
- package/templates/inventory/components/ui/select.tsx +1 -1
- package/templates/inventory/components/ui/textarea.tsx +1 -1
- package/templates/inventory/functions/seedWorkspace.ts +18 -8
- package/templates/inventory/lib/brand.ts +9 -0
- package/templates/inventory/lib/demo-team.ts +51 -0
- package/templates/inventory/lib/search.ts +4 -3
- package/templates/inventory/lib/seed.ts +29 -2
- package/templates/inventory/lib/stock.ts +32 -0
- package/templates/inventory/public/favicon.ico +0 -0
- package/templates/inventory/tests/demo-team.test.ts +18 -0
- package/templates/inventory/tests/stock.test.ts +29 -0
- package/templates/invoices/AGENTS.md +1 -1
- package/templates/invoices/README.md +16 -0
- package/templates/invoices/app/apple-icon.png +0 -0
- package/templates/invoices/app/clients/clients-view.tsx +18 -6
- package/templates/invoices/app/clients/page.tsx +2 -1
- package/templates/invoices/app/globals.css +29 -14
- package/templates/invoices/app/icon.svg +1 -0
- package/templates/invoices/app/invoices/[id]/invoice-view.tsx +72 -48
- package/templates/invoices/app/invoices/[id]/page.tsx +2 -1
- package/templates/invoices/app/invoices-view.tsx +81 -32
- package/templates/invoices/app/layout.tsx +7 -5
- package/templates/invoices/app/page.tsx +4 -6
- package/templates/invoices/app/workspace.tsx +26 -16
- package/templates/invoices/app.ts +2 -2
- package/templates/invoices/components/app-shell.tsx +94 -0
- package/templates/invoices/components/auth-form.tsx +35 -5
- package/templates/invoices/components/avatar.tsx +5 -1
- package/templates/invoices/components/brand-mark.tsx +26 -0
- package/templates/invoices/components/command-palette.tsx +17 -17
- package/templates/invoices/components/data-table.tsx +97 -36
- package/templates/invoices/components/filter-tabs.tsx +101 -0
- package/templates/invoices/components/line-items.tsx +19 -14
- package/templates/invoices/components/nav.tsx +14 -0
- package/templates/invoices/components/page-header.tsx +43 -7
- package/templates/invoices/components/require-auth.tsx +24 -26
- package/templates/invoices/components/sidebar.tsx +54 -48
- package/templates/invoices/components/status-badge.tsx +7 -7
- package/templates/invoices/components/summary-bar.tsx +5 -5
- package/templates/invoices/components/ui/button.tsx +5 -4
- package/templates/invoices/components/ui/dialog.tsx +1 -1
- package/templates/invoices/components/ui/input.tsx +1 -1
- package/templates/invoices/components/ui/select.tsx +1 -1
- package/templates/invoices/components/ui/textarea.tsx +1 -1
- package/templates/invoices/functions/recordPayment.ts +3 -0
- package/templates/invoices/lib/billing.ts +40 -0
- package/templates/invoices/lib/brand.ts +9 -0
- package/templates/invoices/lib/search.ts +4 -3
- package/templates/invoices/public/favicon.ico +0 -0
- package/templates/invoices/tests/billing.test.ts +27 -0
- package/templates/ios/consumer/apps/ios/Package.swift +4 -3
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ActivityView.swift +107 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AppModel.swift +171 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/AuthView.swift +182 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/CommentsView.swift +136 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ComposeView.swift +152 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ExploreView.swift +117 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/FeedView.swift +175 -159
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/MainTabView.swift +54 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Media.swift +271 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Models.swift +101 -4
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/PostCard.swift +245 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileEditorView.swift +208 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileView.swift +183 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/RootView.swift +25 -31
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/SocialStore.swift +285 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/StoryViewer.swift +155 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/Theme.swift +82 -0
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/__APP_NAME_PASCAL__App.swift +4 -47
- package/templates/ios/consumer/apps/ios/project.yml +13 -9
- package/templates/local-service/.env.example +7 -0
- package/templates/local-service/AGENTS.md +1 -1
- package/templates/local-service/README.md +26 -2
- package/templates/local-service/app/(marketing)/booking-widget.tsx +48 -9
- package/templates/local-service/app/(marketing)/page.tsx +66 -42
- package/templates/local-service/app/auth-form.tsx +8 -8
- package/templates/local-service/app/dashboard/dashboard-client.tsx +119 -92
- package/templates/local-service/app/dashboard/page.tsx +14 -14
- package/templates/local-service/app/icon.svg +2 -0
- package/templates/local-service/app/login/page.tsx +15 -15
- package/templates/local-service/functions/seedDemo.ts +49 -0
- package/templates/local-service/lib/demo.ts +135 -0
- package/templates/local-service/lib/hours.ts +44 -0
- package/templates/local-service/lib/site.config.ts +8 -3
- package/templates/local-service/lib/slots.ts +3 -0
- package/templates/local-service/public/favicon.ico +0 -0
- package/templates/local-service/tests/booking.test.ts +104 -0
- package/templates/marketplace/AGENTS.md +1 -1
- package/templates/marketplace/README.md +23 -15
- package/templates/marketplace/app/globals.css +172 -114
- package/templates/marketplace/app/layout.tsx +75 -30
- package/templates/marketplace/app/listing/[id]/page.tsx +210 -123
- package/templates/marketplace/app/me/page.tsx +4 -4
- package/templates/marketplace/app/page.tsx +221 -298
- package/templates/marketplace/app/sell/page.tsx +8 -11
- package/templates/marketplace/app.ts +32 -7
- package/templates/marketplace/client/ActivityFeed.tsx +311 -0
- package/templates/marketplace/client/AuthNav.tsx +24 -16
- package/templates/marketplace/client/ListingCard.tsx +118 -0
- package/templates/marketplace/client/LiveGrid.tsx +92 -0
- package/templates/marketplace/client/LiveListingStatus.tsx +32 -24
- package/templates/marketplace/client/LoginCard.tsx +114 -136
- package/templates/marketplace/client/MarketProvider.tsx +3 -5
- package/templates/marketplace/client/MyMarket.tsx +371 -185
- package/templates/marketplace/client/OfferPanel.tsx +421 -287
- package/templates/marketplace/client/SellForm.tsx +271 -192
- package/templates/marketplace/client/ThemeToggle.tsx +1 -0
- package/templates/marketplace/client/WatchButton.tsx +16 -16
- package/templates/marketplace/client/market.ts +61 -14
- package/templates/marketplace/functions/seedMarket.ts +143 -36
- package/templates/marketplace/functions/seedOffers.ts +101 -0
- package/templates/marketplace/lib/catalog.ts +72 -1
- package/templates/marketplace/public/images/listings/ceramic-lamp-640.webp +0 -0
- package/templates/marketplace/public/images/listings/ceramic-lamp.webp +0 -0
- package/templates/marketplace/public/images/listings/duffel-640.webp +0 -0
- package/templates/marketplace/public/images/listings/duffel.webp +0 -0
- package/templates/marketplace/public/images/listings/film-camera-640.webp +0 -0
- package/templates/marketplace/public/images/listings/film-camera.webp +0 -0
- package/templates/marketplace/public/images/listings/lounge-chair-640.webp +0 -0
- package/templates/marketplace/public/images/listings/lounge-chair.webp +0 -0
- package/templates/marketplace/public/images/listings/road-bike-640.webp +0 -0
- package/templates/marketplace/public/images/listings/road-bike.webp +0 -0
- package/templates/marketplace/public/images/listings/slipper-chair-640.webp +0 -0
- package/templates/marketplace/public/images/listings/slipper-chair.webp +0 -0
- package/templates/marketplace/public/images/listings/turntable-640.webp +0 -0
- package/templates/marketplace/public/images/listings/turntable.webp +0 -0
- package/templates/marketplace/public/images/listings/wardrobe-640.webp +0 -0
- package/templates/marketplace/public/images/listings/wardrobe.webp +0 -0
- package/templates/marketplace/public/images/listings/watch-640.webp +0 -0
- package/templates/marketplace/public/images/listings/watch.webp +0 -0
- package/templates/marketplace/public/images/listings/weekender-bag-640.webp +0 -0
- package/templates/marketplace/public/images/listings/weekender-bag.webp +0 -0
- package/templates/marketplace/tests/example.test.ts +64 -2
- package/templates/marketplace/tests/listing-card.test.tsx +61 -0
- package/templates/projects/AGENTS.md +1 -1
- package/templates/projects/README.md +24 -3
- package/templates/projects/app/apple-icon.png +0 -0
- package/templates/projects/app/clients/clients-view.tsx +20 -6
- package/templates/projects/app/clients/page.tsx +2 -1
- package/templates/projects/app/globals.css +29 -14
- package/templates/projects/app/icon.svg +1 -0
- package/templates/projects/app/layout.tsx +7 -5
- package/templates/projects/app/page.tsx +3 -5
- package/templates/projects/app/projects/[id]/page.tsx +2 -1
- package/templates/projects/app/projects/[id]/project-view.tsx +45 -30
- package/templates/projects/app/projects-view.tsx +105 -52
- package/templates/projects/app/workspace.tsx +41 -20
- package/templates/projects/app.ts +2 -2
- package/templates/projects/components/app-shell.tsx +94 -0
- package/templates/projects/components/auth-form.tsx +35 -5
- package/templates/projects/components/avatar.tsx +5 -1
- package/templates/projects/components/board-skeleton.tsx +3 -3
- package/templates/projects/components/brand-mark.tsx +25 -0
- package/templates/projects/components/command-palette.tsx +17 -17
- package/templates/projects/components/data-table.tsx +97 -36
- package/templates/projects/components/filter-tabs.tsx +101 -0
- package/templates/projects/components/meter.tsx +48 -0
- package/templates/projects/components/nav.tsx +14 -0
- package/templates/projects/components/page-header.tsx +43 -7
- package/templates/projects/components/require-auth.tsx +24 -26
- package/templates/projects/components/sidebar.tsx +54 -48
- package/templates/projects/components/summary-bar.tsx +49 -0
- package/templates/projects/components/task-board.tsx +10 -9
- package/templates/projects/components/time-dialog.tsx +29 -5
- package/templates/projects/components/ui/button.tsx +5 -4
- package/templates/projects/components/ui/dialog.tsx +1 -1
- package/templates/projects/components/ui/input.tsx +1 -1
- package/templates/projects/components/ui/select.tsx +1 -1
- package/templates/projects/components/ui/textarea.tsx +1 -1
- package/templates/projects/functions/seedWorkspace.ts +26 -12
- package/templates/projects/lib/brand.ts +9 -0
- package/templates/projects/lib/demo-team.ts +51 -0
- package/templates/projects/lib/search.ts +4 -3
- package/templates/projects/lib/seed.ts +35 -1
- package/templates/projects/lib/work.ts +63 -0
- package/templates/projects/public/favicon.ico +0 -0
- package/templates/projects/tests/demo-team.test.ts +18 -0
- package/templates/projects/tests/work.test.ts +65 -0
- package/templates/restaurant/.env.example +7 -0
- package/templates/restaurant/AGENTS.md +1 -1
- package/templates/restaurant/README.md +29 -3
- package/templates/restaurant/app/(marketing)/layout.tsx +4 -2
- package/templates/restaurant/app/(marketing)/page.tsx +47 -23
- package/templates/restaurant/app/(marketing)/reservation-widget.tsx +37 -7
- package/templates/restaurant/app/auth-form.tsx +8 -8
- package/templates/restaurant/app/dashboard/dashboard-client.tsx +241 -109
- package/templates/restaurant/app/dashboard/page.tsx +14 -14
- package/templates/restaurant/app/icon.svg +4 -0
- package/templates/restaurant/app/login/page.tsx +15 -15
- package/templates/restaurant/functions/seedDemo.ts +44 -0
- package/templates/restaurant/lib/demo.ts +144 -0
- package/templates/restaurant/lib/hours.ts +45 -0
- package/templates/restaurant/lib/site.config.ts +3 -3
- package/templates/restaurant/public/favicon.ico +0 -0
- package/templates/restaurant/tests/reservations.test.ts +67 -0
- package/templates/saas/AGENTS.md +1 -1
- package/templates/saas/README.md +54 -35
- package/templates/saas/app/(auth)/auth-form.tsx +25 -23
- package/templates/saas/app/(auth)/forgot-password/page.tsx +1 -1
- package/templates/saas/app/(auth)/layout.tsx +27 -33
- package/templates/saas/app/(auth)/login/page.tsx +1 -1
- package/templates/saas/app/(auth)/reset-password/page.tsx +1 -1
- package/templates/saas/app/(auth)/signup/page.tsx +1 -1
- package/templates/saas/app/(marketing)/layout.tsx +127 -253
- package/templates/saas/app/(marketing)/page.tsx +133 -372
- package/templates/saas/app/(marketing)/pricing/page.tsx +21 -19
- package/templates/saas/app/(marketing)/products/[slug]/page.tsx +40 -95
- package/templates/saas/app/dashboard/board-client.tsx +416 -0
- package/templates/saas/app/dashboard/dashboard-client.tsx +30 -615
- package/templates/saas/app/dashboard/layout.tsx +27 -13
- package/templates/saas/app/dashboard/overview-client.tsx +332 -0
- package/templates/saas/app/dashboard/page.tsx +29 -35
- package/templates/saas/app/dashboard/projects/[id]/page.tsx +29 -0
- package/templates/saas/app/dashboard/projects/page.tsx +11 -7
- package/templates/saas/app/dashboard/projects-client.tsx +398 -0
- package/templates/saas/app/globals.css +5 -4
- package/templates/saas/app/icon.svg +1 -0
- package/templates/saas/app/onboarding/onboarding-client.tsx +93 -26
- package/templates/saas/app/onboarding/page.tsx +6 -2
- package/templates/saas/app.ts +68 -13
- package/templates/saas/components/dashboard-shell.tsx +212 -132
- package/templates/saas/components/marketing.tsx +137 -274
- package/templates/saas/components/pricing-table.tsx +12 -11
- package/templates/saas/components/setup-checklist.tsx +31 -29
- package/templates/saas/components/task-ui.tsx +199 -0
- package/templates/saas/functions/createProject.ts +7 -13
- package/templates/saas/functions/createTask.ts +67 -0
- package/templates/saas/functions/deleteProject.ts +17 -0
- package/templates/saas/functions/deleteTask.ts +14 -0
- package/templates/saas/functions/seedWorkspace.ts +46 -0
- package/templates/saas/functions/setProjectStatus.ts +41 -0
- package/templates/saas/functions/updateProfile.ts +13 -0
- package/templates/saas/functions/updateProject.ts +4 -5
- package/templates/saas/functions/updateTask.ts +88 -0
- package/templates/saas/lib/members.ts +53 -0
- package/templates/saas/lib/names.ts +53 -0
- package/templates/saas/lib/plans.ts +11 -0
- package/templates/saas/lib/sample-workspace.ts +117 -0
- package/templates/saas/lib/site.config.ts +200 -463
- package/templates/saas/lib/tasks.ts +101 -0
- package/templates/saas/lib/use-live.ts +25 -0
- package/templates/saas/public/favicon.ico +0 -0
- package/templates/saas/public/screenshots/board.png +0 -0
- package/templates/saas/public/screenshots/members.png +0 -0
- package/templates/saas/public/screenshots/overview.png +0 -0
- package/templates/saas/public/screenshots/projects.png +0 -0
- package/templates/saas/scripts/capture-screenshots.mjs +156 -0
- package/templates/saas/tests/names.test.ts +35 -0
- package/templates/saas/tests/plans.test.ts +12 -0
- package/templates/saas/tests/sample-workspace.test.ts +46 -0
- package/templates/saas/tests/tasks.test.ts +81 -0
- package/templates/shop/AGENTS.md +1 -1
- package/templates/shop/README.md +25 -13
- package/templates/shop/app/(marketing)/layout.tsx +94 -67
- package/templates/shop/app/(marketing)/not-found.tsx +13 -12
- package/templates/shop/app/(marketing)/page.tsx +120 -59
- package/templates/shop/app/(marketing)/shop-client.tsx +364 -237
- package/templates/shop/app/(marketing)/success/page.tsx +75 -22
- package/templates/shop/app/globals.css +94 -1
- package/templates/shop/app/layout.tsx +4 -4
- package/templates/shop/app/login/page.tsx +4 -4
- package/templates/shop/app.ts +18 -6
- package/templates/shop/components/cart-button.tsx +33 -0
- package/templates/shop/components/marketing.tsx +17 -87
- package/templates/shop/components/shelf-count.tsx +36 -0
- package/templates/shop/functions/checkout.ts +17 -1
- package/templates/shop/lib/cart-store.ts +57 -0
- package/templates/shop/lib/shelf-store.ts +35 -0
- package/templates/shop/lib/shop.ts +27 -0
- package/templates/shop/lib/site.config.ts +108 -98
- package/templates/shop/public/images/hero.jpg +0 -0
- package/templates/shop/public/images/products/cedar-smoke.jpg +0 -0
- package/templates/shop/public/images/products/fig-leaf.jpg +0 -0
- package/templates/shop/public/images/products/linen.jpg +0 -0
- package/templates/shop/public/images/products/sea-salt.jpg +0 -0
- package/templates/shop/public/images/products/tobacco-oak.jpg +0 -0
- package/templates/shop/tests/shop.test.ts +64 -0
- package/templates/todo/AGENTS.md +1 -1
- package/templates/waitlist/.env.example +7 -0
- package/templates/waitlist/AGENTS.md +1 -1
- package/templates/waitlist/README.md +29 -13
- package/templates/waitlist/app/(marketing)/page.tsx +9 -7
- package/templates/waitlist/app/(marketing)/waitlist-hero.tsx +64 -31
- package/templates/waitlist/app/auth-form.tsx +8 -8
- package/templates/waitlist/app/dashboard/dashboard-client.tsx +132 -95
- package/templates/waitlist/app/dashboard/page.tsx +17 -16
- package/templates/waitlist/app/icon.svg +2 -0
- package/templates/waitlist/app/login/page.tsx +14 -14
- package/templates/waitlist/app.ts +6 -6
- package/templates/waitlist/functions/joinWaitlist.ts +6 -18
- package/templates/waitlist/functions/seedDemo.ts +32 -0
- package/templates/waitlist/lib/demo.ts +94 -0
- package/templates/waitlist/lib/launch.ts +19 -0
- package/templates/waitlist/lib/site.config.ts +14 -9
- package/templates/waitlist/lib/stats.ts +35 -0
- package/templates/waitlist/public/favicon.ico +0 -0
- package/templates/waitlist/tests/counter.test.ts +107 -0
- package/templates/waitlist/tests/live-count.test.tsx +48 -0
- package/templates/ai-chat/tests/example.test.ts +0 -12
- package/templates/backend/consumer/apps/api/functions/feed.ts +0 -57
- package/templates/backend/consumer/apps/api/functions/profilePosts.ts +0 -17
- package/templates/backend/consumer/apps/api/functions/toggleLike.ts +0 -48
- package/templates/backend/consumer/apps/api/schema.ts +0 -143
- package/templates/chat/functions/_keep.ts +0 -13
- package/templates/chat/tests/example.test.ts +0 -12
- package/templates/ios/consumer/apps/ios/Sources/__APP_NAME_PASCAL__/ProfileSetupView.swift +0 -68
- package/templates/marketplace/client/LiveTicker.tsx +0 -116
- package/templates/marketplace/client/ScrollToListingsLink.tsx +0 -29
- package/templates/marketplace/public/images/marketplace-hero.webp +0 -0
|
@@ -3,13 +3,27 @@
|
|
|
3
3
|
import React, { useEffect, useState } from "react";
|
|
4
4
|
import { db, callFn } from "@pylonsync/react";
|
|
5
5
|
import { useAuth } from "@pylonsync/client";
|
|
6
|
-
import {
|
|
6
|
+
import { ArrowUpRight, Check, LogOut, X } from "lucide-react";
|
|
7
|
+
import {
|
|
8
|
+
monogram,
|
|
9
|
+
parseTags,
|
|
10
|
+
type SubmissionRow,
|
|
11
|
+
type OwnerSubmissionsResult,
|
|
12
|
+
} from "@/lib/directory";
|
|
7
13
|
|
|
8
14
|
// The curator's moderation queue. Liveness rides the public Listing table:
|
|
9
15
|
// `db.useQuery("Listing")` re-renders when listings change (approving a
|
|
10
16
|
// submission creates one), and that same signal re-fetches the owner-gated
|
|
11
|
-
// `submissionsForOwner
|
|
17
|
+
// `submissionsForOwner`, so the queue stays current without a reload, while
|
|
12
18
|
// submitter PII only ever travels through the gated call.
|
|
19
|
+
type Filter = "new" | "approved" | "rejected";
|
|
20
|
+
|
|
21
|
+
const FILTERS: { id: Filter; label: string }[] = [
|
|
22
|
+
{ id: "new", label: "Pending" },
|
|
23
|
+
{ id: "approved", label: "Approved" },
|
|
24
|
+
{ id: "rejected", label: "Rejected" },
|
|
25
|
+
];
|
|
26
|
+
|
|
13
27
|
export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
|
|
14
28
|
const { data: listings } = db.useQuery<{ id: string }>("Listing");
|
|
15
29
|
const liveCount = listings.length;
|
|
@@ -18,6 +32,8 @@ export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
|
|
|
18
32
|
const [denied, setDenied] = useState(false);
|
|
19
33
|
const [error, setError] = useState<string | null>(null);
|
|
20
34
|
const [busyId, setBusyId] = useState<string | null>(null);
|
|
35
|
+
const [filter, setFilter] = useState<Filter>("new");
|
|
36
|
+
const [seedDone, setSeedDone] = useState(false);
|
|
21
37
|
|
|
22
38
|
async function load() {
|
|
23
39
|
try {
|
|
@@ -33,10 +49,20 @@ export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
|
|
|
33
49
|
}
|
|
34
50
|
}
|
|
35
51
|
|
|
52
|
+
// In `pylon dev` an empty queue gets demo submissions (functions/seedDemo.ts)
|
|
53
|
+
// before the first load. On a deploy, or for a non-owner, the call writes
|
|
54
|
+
// nothing.
|
|
36
55
|
useEffect(() => {
|
|
56
|
+
void callFn("seedDemo", {})
|
|
57
|
+
.catch(() => {})
|
|
58
|
+
.finally(() => setSeedDone(true));
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!seedDone) return;
|
|
37
63
|
void load();
|
|
38
64
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
39
|
-
}, [liveCount]);
|
|
65
|
+
}, [liveCount, seedDone]);
|
|
40
66
|
|
|
41
67
|
async function act(id: string, fn: "approveSubmission" | "rejectSubmission") {
|
|
42
68
|
setBusyId(id);
|
|
@@ -50,113 +76,170 @@ export function DirectoryDashboard({ userEmail }: { userEmail: string }) {
|
|
|
50
76
|
|
|
51
77
|
if (denied) return <OwnerOnly email={userEmail} />;
|
|
52
78
|
if (error) {
|
|
53
|
-
return <div className="
|
|
79
|
+
return <div className="border border-red-200 bg-red-50 px-5 py-4 text-sm text-red-700">{error}</div>;
|
|
54
80
|
}
|
|
55
81
|
if (!submissions) return <Skeleton />;
|
|
56
82
|
|
|
57
|
-
const
|
|
58
|
-
const
|
|
83
|
+
const counts: Record<Filter, number> = { new: 0, approved: 0, rejected: 0 };
|
|
84
|
+
for (const s of submissions) {
|
|
85
|
+
if (s.status === "new" || s.status === "approved" || s.status === "rejected") counts[s.status]++;
|
|
86
|
+
}
|
|
87
|
+
const shown = submissions.filter((s) => s.status === filter);
|
|
59
88
|
|
|
60
89
|
return (
|
|
61
90
|
<div className="space-y-8">
|
|
62
91
|
<div>
|
|
63
|
-
<h1 className="text-
|
|
64
|
-
|
|
65
|
-
|
|
92
|
+
<h1 className="text-[1.75rem] font-semibold leading-tight tracking-[-0.02em] text-zinc-900 sm:text-[2rem]">
|
|
93
|
+
Review queue
|
|
94
|
+
</h1>
|
|
95
|
+
<p className="mt-2 max-w-xl text-[14px] leading-relaxed text-zinc-600">
|
|
96
|
+
New submissions land here. Approving one publishes it to the directory right away.
|
|
66
97
|
</p>
|
|
67
98
|
</div>
|
|
68
99
|
|
|
69
|
-
<
|
|
70
|
-
<Stat label="Pending" value={
|
|
71
|
-
<Stat label="Approved" value={
|
|
72
|
-
<Stat label="Live listings" value={
|
|
73
|
-
</
|
|
100
|
+
<dl className="grid grid-cols-3 divide-x divide-zinc-200 border-y border-zinc-200">
|
|
101
|
+
<Stat label="Pending" value={counts.new} accent={counts.new > 0} />
|
|
102
|
+
<Stat label="Approved" value={counts.approved} />
|
|
103
|
+
<Stat label="Live listings" value={liveCount} />
|
|
104
|
+
</dl>
|
|
74
105
|
|
|
75
|
-
<
|
|
76
|
-
<div className="
|
|
77
|
-
|
|
106
|
+
<section>
|
|
107
|
+
<div className="flex items-center gap-4 border-b border-zinc-200">
|
|
108
|
+
{FILTERS.map((f) => (
|
|
109
|
+
<button
|
|
110
|
+
key={f.id}
|
|
111
|
+
type="button"
|
|
112
|
+
onClick={() => setFilter(f.id)}
|
|
113
|
+
aria-pressed={filter === f.id}
|
|
114
|
+
className={
|
|
115
|
+
"-mb-px border-b-2 pb-2 text-[14px] transition-colors " +
|
|
116
|
+
(filter === f.id
|
|
117
|
+
? "border-zinc-900 font-medium text-zinc-900"
|
|
118
|
+
: "border-transparent text-zinc-500 hover:text-zinc-900")
|
|
119
|
+
}
|
|
120
|
+
>
|
|
121
|
+
{f.label} <span className="font-mono text-[12px] text-zinc-400">{counts[f.id]}</span>
|
|
122
|
+
</button>
|
|
123
|
+
))}
|
|
78
124
|
</div>
|
|
79
|
-
|
|
80
|
-
|
|
125
|
+
|
|
126
|
+
{shown.length === 0 ? (
|
|
127
|
+
<p className="py-12 text-center text-[14px] text-zinc-500">
|
|
128
|
+
{filter === "new"
|
|
129
|
+
? "Nothing to review. New submissions appear here as they arrive."
|
|
130
|
+
: `No ${filter} submissions.`}
|
|
131
|
+
</p>
|
|
81
132
|
) : (
|
|
82
|
-
<ul
|
|
83
|
-
{
|
|
84
|
-
<
|
|
85
|
-
<div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2">
|
|
86
|
-
<div className="min-w-0 flex-1">
|
|
87
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
88
|
-
<a href={s.url} target="_blank" rel="noopener noreferrer" className="text-[14px] font-medium text-zinc-900 hover:text-brand">
|
|
89
|
-
{s.name}
|
|
90
|
-
</a>
|
|
91
|
-
<span className="rounded-full bg-zinc-100 px-2 py-0.5 text-[11px] font-medium text-zinc-500">{s.category}</span>
|
|
92
|
-
<StatusBadge status={s.status} />
|
|
93
|
-
</div>
|
|
94
|
-
{s.tagline ? <p className="mt-1 text-[13.5px] text-zinc-600">{s.tagline}</p> : null}
|
|
95
|
-
<div className="mt-1 truncate text-[12px] text-zinc-400">
|
|
96
|
-
{s.submitterName} · {s.submitterEmail}
|
|
97
|
-
{parseTags(s.tags).length ? ` · ${parseTags(s.tags).map((t) => "#" + t).join(" ")}` : ""}
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
{s.status === "new" ? (
|
|
101
|
-
<div className="flex items-center gap-2">
|
|
102
|
-
<button
|
|
103
|
-
type="button"
|
|
104
|
-
disabled={busyId === s.id}
|
|
105
|
-
onClick={() => act(s.id, "approveSubmission")}
|
|
106
|
-
className="rounded-md bg-brand px-3 py-1.5 text-[12.5px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-50"
|
|
107
|
-
>
|
|
108
|
-
{busyId === s.id ? "…" : "Approve"}
|
|
109
|
-
</button>
|
|
110
|
-
<button
|
|
111
|
-
type="button"
|
|
112
|
-
disabled={busyId === s.id}
|
|
113
|
-
onClick={() => act(s.id, "rejectSubmission")}
|
|
114
|
-
className="rounded-md border border-zinc-300 px-3 py-1.5 text-[12.5px] font-medium text-zinc-600 transition-colors hover:border-red-300 hover:text-red-600 disabled:opacity-50"
|
|
115
|
-
>
|
|
116
|
-
Reject
|
|
117
|
-
</button>
|
|
118
|
-
</div>
|
|
119
|
-
) : null}
|
|
120
|
-
</div>
|
|
121
|
-
</li>
|
|
133
|
+
<ul>
|
|
134
|
+
{shown.map((s) => (
|
|
135
|
+
<QueueItem key={s.id} s={s} busy={busyId === s.id} onAct={act} />
|
|
122
136
|
))}
|
|
123
137
|
</ul>
|
|
124
138
|
)}
|
|
125
|
-
</
|
|
139
|
+
</section>
|
|
126
140
|
</div>
|
|
127
141
|
);
|
|
128
142
|
}
|
|
129
143
|
|
|
130
|
-
function
|
|
144
|
+
function QueueItem({
|
|
145
|
+
s,
|
|
146
|
+
busy,
|
|
147
|
+
onAct,
|
|
148
|
+
}: {
|
|
149
|
+
s: SubmissionRow;
|
|
150
|
+
busy: boolean;
|
|
151
|
+
onAct: (id: string, fn: "approveSubmission" | "rejectSubmission") => void;
|
|
152
|
+
}) {
|
|
153
|
+
const m = monogram(s.name);
|
|
154
|
+
const tags = parseTags(s.tags);
|
|
131
155
|
return (
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
156
|
+
<li className="grid grid-cols-[36px_minmax(0,1fr)] gap-x-3 gap-y-3 border-b border-zinc-200 py-4 sm:grid-cols-[36px_minmax(0,1fr)_auto]">
|
|
157
|
+
<span
|
|
158
|
+
aria-hidden
|
|
159
|
+
className="flex size-9 items-center justify-center rounded-md font-mono text-[13px] font-semibold"
|
|
160
|
+
style={{ backgroundColor: m.bg, color: m.fg }}
|
|
161
|
+
>
|
|
162
|
+
{m.letters}
|
|
163
|
+
</span>
|
|
164
|
+
<div className="min-w-0">
|
|
165
|
+
<div className="flex flex-wrap items-baseline gap-x-2">
|
|
166
|
+
<a
|
|
167
|
+
href={s.url}
|
|
168
|
+
target="_blank"
|
|
169
|
+
rel="noopener noreferrer"
|
|
170
|
+
className="inline-flex items-center gap-1 text-[15px] font-semibold text-zinc-900 hover:text-brand"
|
|
171
|
+
>
|
|
172
|
+
{s.name}
|
|
173
|
+
<ArrowUpRight aria-hidden className="size-3.5 text-zinc-400" />
|
|
174
|
+
</a>
|
|
175
|
+
<span className="font-mono text-[12px] text-zinc-400">{s.category}</span>
|
|
176
|
+
</div>
|
|
177
|
+
<p className="mt-0.5 text-[14px] leading-snug text-zinc-700">{s.tagline}</p>
|
|
178
|
+
{s.description ? (
|
|
179
|
+
<p className="mt-1 max-w-2xl text-[13px] leading-relaxed text-zinc-500">{s.description}</p>
|
|
180
|
+
) : null}
|
|
181
|
+
<p className="mt-1.5 truncate font-mono text-[12px] text-zinc-400">
|
|
182
|
+
{s.submitterName} · {s.submitterEmail} · {ago(s.createdAt)}
|
|
183
|
+
{tags.length ? ` · ${tags.join(", ")}` : ""}
|
|
184
|
+
</p>
|
|
185
|
+
</div>
|
|
186
|
+
{s.status === "new" ? (
|
|
187
|
+
<div className="col-start-2 flex items-center gap-2 sm:col-start-3 sm:self-start">
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
disabled={busy}
|
|
191
|
+
onClick={() => onAct(s.id, "approveSubmission")}
|
|
192
|
+
className="inline-flex h-8 items-center gap-1.5 bg-brand px-3 text-[13px] font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-50"
|
|
193
|
+
>
|
|
194
|
+
<Check aria-hidden className="size-3.5" />
|
|
195
|
+
Approve
|
|
196
|
+
</button>
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
disabled={busy}
|
|
200
|
+
onClick={() => onAct(s.id, "rejectSubmission")}
|
|
201
|
+
className="inline-flex h-8 items-center gap-1.5 border border-zinc-300 px-3 text-[13px] font-medium text-zinc-600 transition-colors hover:border-zinc-500 hover:text-zinc-900 disabled:opacity-50"
|
|
202
|
+
>
|
|
203
|
+
<X aria-hidden className="size-3.5" />
|
|
204
|
+
Reject
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
) : null}
|
|
208
|
+
</li>
|
|
136
209
|
);
|
|
137
210
|
}
|
|
138
211
|
|
|
139
|
-
function
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
212
|
+
function ago(iso: string): string {
|
|
213
|
+
const mins = Math.max(0, Math.round((Date.now() - Date.parse(iso)) / 60_000));
|
|
214
|
+
if (mins < 60) return `${mins}m ago`;
|
|
215
|
+
const hours = Math.round(mins / 60);
|
|
216
|
+
if (hours < 48) return `${hours}h ago`;
|
|
217
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function Stat({ label, value, accent }: { label: string; value: number; accent?: boolean }) {
|
|
221
|
+
return (
|
|
222
|
+
<div className="px-4 py-4 first:pl-0">
|
|
223
|
+
<dt className="text-[12px] font-medium uppercase tracking-wide text-zinc-500">{label}</dt>
|
|
224
|
+
<dd className={"mt-1 font-mono text-[1.75rem] leading-none tabular-nums " + (accent ? "text-brand" : "text-zinc-900")}>
|
|
225
|
+
{value}
|
|
226
|
+
</dd>
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
148
229
|
}
|
|
149
230
|
|
|
150
231
|
function OwnerOnly({ email }: { email: string }) {
|
|
151
232
|
return (
|
|
152
|
-
<div className="
|
|
233
|
+
<div className="border border-dashed border-zinc-300 px-6 py-12 text-center">
|
|
153
234
|
<h1 className="text-lg font-semibold">This dashboard is owner-only</h1>
|
|
154
235
|
<p className="mx-auto mt-2 max-w-md text-sm text-zinc-500">
|
|
155
|
-
You
|
|
156
|
-
Only the
|
|
157
|
-
<code className="
|
|
158
|
-
|
|
159
|
-
|
|
236
|
+
You are signed in as <span className="font-medium text-zinc-700">{email || "this account"}</span>.
|
|
237
|
+
Only the directory owner can review submissions. Set{" "}
|
|
238
|
+
<code className="bg-zinc-100 px-1.5 py-0.5 font-mono text-[12px]">
|
|
239
|
+
PYLON_OWNER_EMAIL={email || "you@yourdirectory.com"}
|
|
240
|
+
</code>{" "}
|
|
241
|
+
in <code className="bg-zinc-100 px-1.5 py-0.5 font-mono text-[12px]">.env</code>, restart, and
|
|
242
|
+
reload. Or sign in with the owner account.
|
|
160
243
|
</p>
|
|
161
244
|
</div>
|
|
162
245
|
);
|
|
@@ -171,18 +254,22 @@ export function UserMenu({ email }: { email: string }) {
|
|
|
171
254
|
}
|
|
172
255
|
return (
|
|
173
256
|
<details className="group relative">
|
|
174
|
-
<summary
|
|
257
|
+
<summary
|
|
258
|
+
aria-label="Account"
|
|
259
|
+
className="flex size-7 cursor-pointer select-none list-none items-center justify-center bg-zinc-900 font-mono text-[12px] font-semibold text-white marker:hidden [&::-webkit-details-marker]:hidden"
|
|
260
|
+
>
|
|
175
261
|
{initial}
|
|
176
262
|
</summary>
|
|
177
|
-
<div className="absolute right-0 top-full z-40 mt-2 w-
|
|
178
|
-
<div className="border-b border-zinc-100 px-3 py-2">
|
|
179
|
-
|
|
263
|
+
<div className="absolute right-0 top-full z-40 mt-2 w-60 border border-zinc-200 bg-white py-1 shadow-[0_16px_40px_-20px_rgba(0,0,0,0.3)]">
|
|
264
|
+
<div className="truncate border-b border-zinc-100 px-3 py-2 font-mono text-[12px] text-zinc-700">
|
|
265
|
+
{email || "Signed in"}
|
|
180
266
|
</div>
|
|
181
267
|
<button
|
|
182
268
|
type="button"
|
|
183
269
|
onClick={onSignOut}
|
|
184
|
-
className="flex w-full items-center px-3 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
|
|
270
|
+
className="flex w-full items-center gap-2 px-3 py-2 text-left text-[13px] text-zinc-700 transition-colors hover:bg-zinc-50"
|
|
185
271
|
>
|
|
272
|
+
<LogOut aria-hidden className="size-3.5" />
|
|
186
273
|
Sign out
|
|
187
274
|
</button>
|
|
188
275
|
</div>
|
|
@@ -193,13 +280,13 @@ export function UserMenu({ email }: { email: string }) {
|
|
|
193
280
|
function Skeleton() {
|
|
194
281
|
return (
|
|
195
282
|
<div className="space-y-8">
|
|
196
|
-
<div className="h-
|
|
197
|
-
<div className="
|
|
198
|
-
|
|
199
|
-
|
|
283
|
+
<div className="h-8 w-48 animate-pulse bg-zinc-100" />
|
|
284
|
+
<div className="h-20 animate-pulse bg-zinc-50" />
|
|
285
|
+
<div className="space-y-3">
|
|
286
|
+
{[0, 1, 2, 3].map((i) => (
|
|
287
|
+
<div key={i} className="h-16 animate-pulse bg-zinc-50" />
|
|
200
288
|
))}
|
|
201
289
|
</div>
|
|
202
|
-
<div className="h-48 animate-pulse rounded-xl bg-zinc-100" />
|
|
203
290
|
</div>
|
|
204
291
|
);
|
|
205
292
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { use } from "react";
|
|
2
2
|
import { Link, type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
|
+
import { ArrowUpRight } from "lucide-react";
|
|
3
4
|
import { siteConfig } from "@/lib/site.config";
|
|
5
|
+
import { WRAP } from "@/components/marketing";
|
|
4
6
|
import { UserMenu, DirectoryDashboard } from "./dashboard-client";
|
|
5
7
|
|
|
6
8
|
export const metadata: Metadata = {
|
|
@@ -35,35 +37,35 @@ export default function DashboardPage({ auth, response, serverData }: PageProps)
|
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
// Dashboard chrome
|
|
39
|
-
// site, and the account menu (a client
|
|
40
|
+
// Dashboard chrome in the site's own look: the same slim white nav with the
|
|
41
|
+
// directory name, a link back to the site, and the account menu (a client
|
|
42
|
+
// island for sign-out).
|
|
40
43
|
function Shell({ email, children }: { email: string; children: React.ReactNode }) {
|
|
41
44
|
const { brand } = siteConfig;
|
|
42
45
|
return (
|
|
43
46
|
<div className="flex min-h-screen flex-col bg-white text-zinc-900">
|
|
44
47
|
<header className="border-b border-zinc-200">
|
|
45
|
-
<div className=
|
|
46
|
-
<div className="flex items-
|
|
47
|
-
<
|
|
48
|
-
{brand.
|
|
49
|
-
</
|
|
50
|
-
<span className="text-[
|
|
51
|
-
{brand.name} <span className="text-zinc-400">/ curator</span>
|
|
52
|
-
</span>
|
|
48
|
+
<div className={`${WRAP} flex h-12 items-center justify-between`}>
|
|
49
|
+
<div className="flex items-baseline gap-2.5">
|
|
50
|
+
<Link href="/" className="text-[15px] font-semibold text-zinc-900">
|
|
51
|
+
{brand.name}
|
|
52
|
+
</Link>
|
|
53
|
+
<span className="font-mono text-[12px] text-zinc-400">curator</span>
|
|
53
54
|
</div>
|
|
54
|
-
<div className="flex items-center gap-
|
|
55
|
+
<div className="flex items-center gap-5">
|
|
55
56
|
<Link
|
|
56
57
|
href="/"
|
|
57
|
-
className="text-[
|
|
58
|
+
className="hidden items-center gap-1 text-[14px] text-zinc-600 hover:text-zinc-900 sm:inline-flex"
|
|
58
59
|
>
|
|
59
|
-
View site
|
|
60
|
+
View site
|
|
61
|
+
<ArrowUpRight aria-hidden className="size-3.5" />
|
|
60
62
|
</Link>
|
|
61
63
|
<UserMenu email={email} />
|
|
62
64
|
</div>
|
|
63
65
|
</div>
|
|
64
66
|
</header>
|
|
65
67
|
<main className="flex-1">
|
|
66
|
-
<div className=
|
|
68
|
+
<div className={`${WRAP} py-10`}>{children}</div>
|
|
67
69
|
</main>
|
|
68
70
|
</div>
|
|
69
71
|
);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="12" fill="#1d4ed8"/>
|
|
2
|
+
<rect x="14" y="14" width="36" height="9" rx="2" fill="#ffffff"/>
|
|
3
|
+
<rect x="14" y="27.5" width="28" height="9" rx="2" fill="#ffffff" opacity="0.8"/>
|
|
4
|
+
<rect x="14" y="41" width="20" height="9" rx="2" fill="#ffffff" opacity="0.6"/></svg>
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { Link, type Metadata, type PageProps } from "@pylonsync/react";
|
|
3
3
|
import { AuthForm } from "../auth-form";
|
|
4
4
|
import { siteConfig } from "@/lib/site.config";
|
|
5
|
+
import { WRAP } from "@/components/marketing";
|
|
5
6
|
|
|
6
7
|
export const metadata: Metadata = {
|
|
7
8
|
title: `Sign in — ${siteConfig.brand.name}`,
|
|
@@ -18,21 +19,21 @@ export default function LoginPage({ auth, response }: PageProps) {
|
|
|
18
19
|
if (auth.user_id && !auth.user_id.startsWith("guest_")) response.redirect("/dashboard");
|
|
19
20
|
const { brand } = siteConfig;
|
|
20
21
|
return (
|
|
21
|
-
<div className="flex min-h-screen
|
|
22
|
-
<div className="
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
{brand.
|
|
26
|
-
</
|
|
27
|
-
</
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
<div className="flex min-h-screen flex-col bg-white text-zinc-900">
|
|
23
|
+
<div className="border-b border-zinc-200">
|
|
24
|
+
<div className={`${WRAP} flex h-12 items-center`}>
|
|
25
|
+
<Link href="/" className="text-[15px] font-semibold">
|
|
26
|
+
{brand.name}
|
|
27
|
+
</Link>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div className="flex flex-1 items-center justify-center px-5 py-16">
|
|
31
|
+
<div className="w-full max-w-[380px]">
|
|
32
|
+
<h1 className="text-[1.75rem] font-semibold tracking-[-0.02em]">Curator sign in</h1>
|
|
33
|
+
<p className="mt-2 text-[14px] text-zinc-600">Review submissions and publish them to {brand.name}.</p>
|
|
34
|
+
<div className="mt-8 border border-zinc-200 p-6">
|
|
35
|
+
<AuthForm />
|
|
36
|
+
</div>
|
|
36
37
|
</div>
|
|
37
38
|
</div>
|
|
38
39
|
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { mutation } from "@pylonsync/functions";
|
|
2
|
+
import { emailMatchesOwner } from "../lib/owner";
|
|
3
|
+
import { DEMO_SUBMISSIONS, demoDataEnabled } from "../lib/demo";
|
|
4
|
+
|
|
5
|
+
// seedDemo — owner-only, development-only. Fills an empty review queue with the
|
|
6
|
+
// fictional submissions in lib/demo.ts, so the curator dashboard has entries.
|
|
7
|
+
//
|
|
8
|
+
// It writes nothing unless demo data is on (lib/demo.ts: PYLON_DEMO_DATA, else
|
|
9
|
+
// a `pylon dev` process), so `pylon start`, Docker, and Pylon Cloud deploys do
|
|
10
|
+
// not get demo rows. It also writes nothing once any Submission exists.
|
|
11
|
+
// Submissions carry submitter emails (PII), so only the signed-in owner can run
|
|
12
|
+
// it, like the rest of the curator surface.
|
|
13
|
+
export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
14
|
+
auth: "user",
|
|
15
|
+
async handler(ctx) {
|
|
16
|
+
const me = await ctx.db.get("User", ctx.auth.userId);
|
|
17
|
+
if (!emailMatchesOwner(me?.email as string | undefined, ctx.env.PYLON_OWNER_EMAIL)) {
|
|
18
|
+
throw ctx.error("POLICY_DENIED", "Only the owner can seed the review queue.");
|
|
19
|
+
}
|
|
20
|
+
if (!demoDataEnabled(ctx.env)) return { seeded: false };
|
|
21
|
+
|
|
22
|
+
await ctx.db.advisoryLock("directory_seed_demo");
|
|
23
|
+
const existing = await ctx.db.unsafe.list("Submission");
|
|
24
|
+
if (existing.length > 0) return { seeded: false };
|
|
25
|
+
|
|
26
|
+
const nowMs = Date.now();
|
|
27
|
+
for (const s of DEMO_SUBMISSIONS) {
|
|
28
|
+
await ctx.db.unsafe.insert("Submission", {
|
|
29
|
+
submitterName: s.submitterName,
|
|
30
|
+
submitterEmail: s.submitterEmail,
|
|
31
|
+
name: s.name,
|
|
32
|
+
tagline: s.tagline,
|
|
33
|
+
url: s.url,
|
|
34
|
+
category: s.category,
|
|
35
|
+
tags: s.tags,
|
|
36
|
+
description: s.description || null,
|
|
37
|
+
status: s.status,
|
|
38
|
+
createdAt: new Date(nowMs - s.hoursAgo * 3_600_000).toISOString(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return { seeded: true };
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Demo review queue for local development.
|
|
2
|
+
//
|
|
3
|
+
// The curator's first dashboard load in `pylon dev` fills an empty Submission
|
|
4
|
+
// table with fictional tool submissions (functions/seedDemo.ts), so the queue
|
|
5
|
+
// has entries to approve or reject. A deploy does not seed: `demoDataEnabled`
|
|
6
|
+
// is true only when PYLON_DEMO_DATA is on, or when it is unset and the process
|
|
7
|
+
// runs under `pylon dev`. Set PYLON_DEMO_DATA=0 to turn it off in development too.
|
|
8
|
+
//
|
|
9
|
+
// The starter listings (functions/seedListings.ts) are different: they are the
|
|
10
|
+
// directory's own content from lib/site.config.ts, so they seed everywhere.
|
|
11
|
+
|
|
12
|
+
const ON = new Set(["1", "true", "yes", "on"]);
|
|
13
|
+
const OFF = new Set(["0", "false", "no", "off"]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* True when demo rows may be written. An explicit PYLON_DEMO_DATA wins.
|
|
17
|
+
* Otherwise it needs both PYLON_DEV_MODE on and PYLON_DEV_WATCH_DIR set. Only
|
|
18
|
+
* `pylon dev` sets PYLON_DEV_WATCH_DIR; `pylon start` and the Docker image do
|
|
19
|
+
* not, and the image turns PYLON_DEV_MODE on by default, so dev mode alone is
|
|
20
|
+
* not proof of a local machine.
|
|
21
|
+
*/
|
|
22
|
+
export function demoDataEnabled(env: Record<string, string | undefined>): boolean {
|
|
23
|
+
const explicit = env.PYLON_DEMO_DATA?.trim().toLowerCase();
|
|
24
|
+
if (explicit && ON.has(explicit)) return true;
|
|
25
|
+
if (explicit && OFF.has(explicit)) return false;
|
|
26
|
+
const dev = env.PYLON_DEV_MODE?.trim().toLowerCase();
|
|
27
|
+
const devMode = dev === "1" || dev === "true";
|
|
28
|
+
return devMode && Boolean(env.PYLON_DEV_WATCH_DIR?.trim());
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DemoSubmission {
|
|
32
|
+
submitterName: string;
|
|
33
|
+
submitterEmail: string;
|
|
34
|
+
name: string;
|
|
35
|
+
tagline: string;
|
|
36
|
+
url: string;
|
|
37
|
+
category: string;
|
|
38
|
+
tags: string;
|
|
39
|
+
description: string;
|
|
40
|
+
status: "new" | "approved" | "rejected";
|
|
41
|
+
/** Hours before the seed runs. */
|
|
42
|
+
hoursAgo: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Fictional tools and submitters. The two "approved" rows name starter
|
|
46
|
+
// listings from lib/site.config.ts, so every approved submission is live.
|
|
47
|
+
// Starter listings added from config have no submission row.
|
|
48
|
+
export const DEMO_SUBMISSIONS: DemoSubmission[] = [
|
|
49
|
+
{
|
|
50
|
+
submitterName: "Nadia Haddad",
|
|
51
|
+
submitterEmail: "nadia@example.com",
|
|
52
|
+
name: "Querybird",
|
|
53
|
+
tagline: "Explain plans for Postgres, drawn as a tree you can click through.",
|
|
54
|
+
url: "https://example.com/querybird",
|
|
55
|
+
category: "Database",
|
|
56
|
+
tags: "postgres, performance",
|
|
57
|
+
description: "Paste a query, get the plan with the slow node marked. Runs in the browser; nothing leaves your machine.",
|
|
58
|
+
status: "new",
|
|
59
|
+
hoursAgo: 2,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
submitterName: "Owen Reilly",
|
|
63
|
+
submitterEmail: "owen@example.com",
|
|
64
|
+
name: "Flagpost",
|
|
65
|
+
tagline: "Feature flags stored in your own database, with a small admin UI.",
|
|
66
|
+
url: "https://example.com/flagpost",
|
|
67
|
+
category: "DevOps",
|
|
68
|
+
tags: "feature flags, self-hosted",
|
|
69
|
+
description: "One table, one SDK call, and an admin page you mount in your app.",
|
|
70
|
+
status: "new",
|
|
71
|
+
hoursAgo: 9,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
submitterName: "Hana Sato",
|
|
75
|
+
submitterEmail: "hana@example.com",
|
|
76
|
+
name: "Palettier",
|
|
77
|
+
tagline: "Generate accessible color scales from one brand color.",
|
|
78
|
+
url: "https://example.com/palettier",
|
|
79
|
+
category: "Design",
|
|
80
|
+
tags: "color, accessibility, tokens",
|
|
81
|
+
description: "Every step is checked for WCAG contrast against white and black. Exports CSS variables and a Figma file.",
|
|
82
|
+
status: "new",
|
|
83
|
+
hoursAgo: 26,
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
submitterName: "Felix Wagner",
|
|
87
|
+
submitterEmail: "felix@example.com",
|
|
88
|
+
name: "Logline",
|
|
89
|
+
tagline: "Structured logs you can search from the terminal.",
|
|
90
|
+
url: "https://example.com/logline",
|
|
91
|
+
category: "DevOps",
|
|
92
|
+
tags: "logging, cli",
|
|
93
|
+
description: "Pipe JSON logs in, then filter with a short query language. Keeps a week of history on disk.",
|
|
94
|
+
status: "new",
|
|
95
|
+
hoursAgo: 40,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
submitterName: "Isabel Cruz",
|
|
99
|
+
submitterEmail: "isabel@example.com",
|
|
100
|
+
name: "Promptdeck",
|
|
101
|
+
tagline: "Version and diff the prompts your app sends to a model.",
|
|
102
|
+
url: "https://example.com/promptdeck",
|
|
103
|
+
category: "AI",
|
|
104
|
+
tags: "llm, prompts, versioning",
|
|
105
|
+
description: "Each prompt change gets a version and a side-by-side diff of outputs on your saved test inputs.",
|
|
106
|
+
status: "new",
|
|
107
|
+
hoursAgo: 55,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
submitterName: "Ruth Castillo",
|
|
111
|
+
submitterEmail: "ruth@example.com",
|
|
112
|
+
name: "Brineway",
|
|
113
|
+
tagline: "S3-compatible object storage at the edge.",
|
|
114
|
+
url: "https://example.com/brineway",
|
|
115
|
+
category: "Hosting",
|
|
116
|
+
tags: "storage, edge, s3",
|
|
117
|
+
description: "",
|
|
118
|
+
status: "approved",
|
|
119
|
+
hoursAgo: 150,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
submitterName: "Daniel Park",
|
|
123
|
+
submitterEmail: "daniel@example.com",
|
|
124
|
+
name: "Countwell",
|
|
125
|
+
tagline: "Privacy-first product analytics you can self-host.",
|
|
126
|
+
url: "https://example.com/countwell",
|
|
127
|
+
category: "Analytics",
|
|
128
|
+
tags: "analytics, privacy",
|
|
129
|
+
description: "",
|
|
130
|
+
status: "approved",
|
|
131
|
+
hoursAgo: 210,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
submitterName: "Luca Romano",
|
|
135
|
+
submitterEmail: "luca@example.com",
|
|
136
|
+
name: "Best AI Tools 2026",
|
|
137
|
+
tagline: "A list of 500 AI tools.",
|
|
138
|
+
url: "https://example.com/best-ai-tools",
|
|
139
|
+
category: "AI",
|
|
140
|
+
tags: "list",
|
|
141
|
+
description: "",
|
|
142
|
+
status: "rejected",
|
|
143
|
+
hoursAgo: 96,
|
|
144
|
+
},
|
|
145
|
+
];
|