@pylonsync/create-pylon 0.3.343 → 0.3.345
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 +35 -0
- package/package.json +1 -1
- package/templates/agency/components/ui/badge.tsx +37 -0
- package/templates/agency/components/ui/dialog.tsx +110 -0
- package/templates/agency/components/ui/input.tsx +30 -0
- package/templates/agency/components/ui/label.tsx +21 -0
- package/templates/agency/components/ui/select.tsx +39 -0
- package/templates/agency/components/ui/table.tsx +124 -0
- package/templates/agency/components/ui/textarea.tsx +24 -0
- package/templates/agency/package.json +1 -0
- package/templates/ai-chat/components/ui/badge.tsx +37 -0
- package/templates/ai-chat/components/ui/button.tsx +56 -0
- package/templates/ai-chat/components/ui/card.tsx +90 -0
- package/templates/ai-chat/components/ui/dialog.tsx +110 -0
- package/templates/ai-chat/components/ui/input.tsx +30 -0
- package/templates/ai-chat/components/ui/label.tsx +21 -0
- package/templates/ai-chat/components/ui/select.tsx +39 -0
- package/templates/ai-chat/components/ui/table.tsx +124 -0
- package/templates/ai-chat/components/ui/textarea.tsx +24 -0
- package/templates/ai-chat/package.json +1 -0
- package/templates/ai-studio/components/ui/badge.tsx +37 -0
- package/templates/ai-studio/components/ui/button.tsx +56 -0
- package/templates/ai-studio/components/ui/card.tsx +90 -0
- package/templates/ai-studio/components/ui/dialog.tsx +110 -0
- package/templates/ai-studio/components/ui/input.tsx +30 -0
- package/templates/ai-studio/components/ui/label.tsx +21 -0
- package/templates/ai-studio/components/ui/select.tsx +39 -0
- package/templates/ai-studio/components/ui/table.tsx +124 -0
- package/templates/ai-studio/components/ui/textarea.tsx +24 -0
- package/templates/ai-studio/package.json +1 -0
- package/templates/barebones/components/ui/badge.tsx +37 -0
- package/templates/barebones/components/ui/dialog.tsx +110 -0
- package/templates/barebones/components/ui/input.tsx +30 -0
- package/templates/barebones/components/ui/label.tsx +21 -0
- package/templates/barebones/components/ui/select.tsx +39 -0
- package/templates/barebones/components/ui/table.tsx +124 -0
- package/templates/barebones/components/ui/textarea.tsx +24 -0
- package/templates/barebones/package.json +1 -0
- package/templates/chat/components/ui/badge.tsx +37 -0
- package/templates/chat/components/ui/dialog.tsx +110 -0
- package/templates/chat/components/ui/input.tsx +30 -0
- package/templates/chat/components/ui/label.tsx +21 -0
- package/templates/chat/components/ui/select.tsx +39 -0
- package/templates/chat/components/ui/table.tsx +124 -0
- package/templates/chat/components/ui/textarea.tsx +24 -0
- package/templates/chat/package.json +1 -0
- package/templates/consumer/components/ui/badge.tsx +37 -0
- package/templates/consumer/components/ui/dialog.tsx +110 -0
- package/templates/consumer/components/ui/input.tsx +30 -0
- package/templates/consumer/components/ui/label.tsx +21 -0
- package/templates/consumer/components/ui/select.tsx +39 -0
- package/templates/consumer/components/ui/table.tsx +124 -0
- package/templates/consumer/components/ui/textarea.tsx +24 -0
- package/templates/consumer/package.json +1 -0
- package/templates/creator/components/ui/badge.tsx +37 -0
- package/templates/creator/components/ui/dialog.tsx +110 -0
- package/templates/creator/components/ui/input.tsx +30 -0
- package/templates/creator/components/ui/label.tsx +21 -0
- package/templates/creator/components/ui/select.tsx +39 -0
- package/templates/creator/components/ui/table.tsx +124 -0
- package/templates/creator/components/ui/textarea.tsx +24 -0
- package/templates/creator/package.json +1 -0
- package/templates/crm/AGENTS.md +116 -0
- package/templates/crm/CLAUDE.md +7 -0
- package/templates/crm/README.md +96 -0
- package/templates/crm/app/companies/companies-view.tsx +129 -0
- package/templates/crm/app/companies/page.tsx +17 -0
- package/templates/crm/app/contacts/contacts-view.tsx +139 -0
- package/templates/crm/app/contacts/page.tsx +17 -0
- package/templates/crm/app/deals/[id]/deal-view.tsx +189 -0
- package/templates/crm/app/deals/[id]/page.tsx +23 -0
- package/templates/crm/app/error.tsx +43 -0
- package/templates/crm/app/globals.css +201 -0
- package/templates/crm/app/layout.tsx +31 -0
- package/templates/crm/app/login/auth-form.tsx +122 -0
- package/templates/crm/app/login/page.tsx +38 -0
- package/templates/crm/app/not-found.tsx +29 -0
- package/templates/crm/app/page.tsx +38 -0
- package/templates/crm/app/pipeline-view.tsx +99 -0
- package/templates/crm/app/robots.ts +12 -0
- package/templates/crm/app/sitemap.ts +27 -0
- package/templates/crm/app/workspace.tsx +231 -0
- package/templates/crm/app.ts +171 -0
- package/templates/crm/bunfig +4 -0
- package/templates/crm/components/activity-timeline.tsx +133 -0
- package/templates/crm/components/avatar.tsx +40 -0
- package/templates/crm/components/board-skeleton.tsx +44 -0
- package/templates/crm/components/command-palette.tsx +162 -0
- package/templates/crm/components/data-table.tsx +90 -0
- package/templates/crm/components/deal-card.tsx +91 -0
- package/templates/crm/components/deal-dialog.tsx +169 -0
- package/templates/crm/components/empty-state.tsx +43 -0
- package/templates/crm/components/kbd.tsx +23 -0
- package/templates/crm/components/metrics-bar.tsx +46 -0
- package/templates/crm/components/page-header.tsx +36 -0
- package/templates/crm/components/pipeline-board.tsx +105 -0
- package/templates/crm/components/record-dialog.tsx +126 -0
- package/templates/crm/components/sidebar.tsx +112 -0
- package/templates/crm/components/stage-badge.tsx +33 -0
- package/templates/crm/components/ui/badge.tsx +37 -0
- package/templates/crm/components/ui/button.tsx +56 -0
- package/templates/crm/components/ui/card.tsx +90 -0
- package/templates/crm/components/ui/dialog.tsx +110 -0
- package/templates/crm/components/ui/input.tsx +30 -0
- package/templates/crm/components/ui/label.tsx +21 -0
- package/templates/crm/components/ui/select.tsx +39 -0
- package/templates/crm/components/ui/table.tsx +124 -0
- package/templates/crm/components/ui/textarea.tsx +24 -0
- package/templates/crm/components.json +20 -0
- package/templates/crm/functions/createDeal.ts +59 -0
- package/templates/crm/functions/logActivity.ts +49 -0
- package/templates/crm/functions/moveDeal.ts +53 -0
- package/templates/crm/functions/seedWorkspace.ts +63 -0
- package/templates/crm/gitignore +10 -0
- package/templates/crm/lib/pipeline.ts +221 -0
- package/templates/crm/lib/search.ts +78 -0
- package/templates/crm/lib/seed.ts +130 -0
- package/templates/crm/lib/utils.ts +10 -0
- package/templates/crm/package.json +39 -0
- package/templates/crm/tests/components.test.tsx +245 -0
- package/templates/crm/tests/pipeline.test.ts +219 -0
- package/templates/crm/tests/search.test.ts +83 -0
- package/templates/crm/tests/setup.ts +5 -0
- package/templates/crm/tsconfig.json +33 -0
- package/templates/default/components/ui/badge.tsx +37 -0
- package/templates/default/components/ui/dialog.tsx +110 -0
- package/templates/default/components/ui/input.tsx +30 -0
- package/templates/default/components/ui/label.tsx +21 -0
- package/templates/default/components/ui/select.tsx +39 -0
- package/templates/default/components/ui/table.tsx +124 -0
- package/templates/default/components/ui/textarea.tsx +24 -0
- package/templates/default/package.json +1 -0
- package/templates/directory/components/ui/badge.tsx +37 -0
- package/templates/directory/components/ui/dialog.tsx +110 -0
- package/templates/directory/components/ui/input.tsx +30 -0
- package/templates/directory/components/ui/label.tsx +21 -0
- package/templates/directory/components/ui/select.tsx +39 -0
- package/templates/directory/components/ui/table.tsx +124 -0
- package/templates/directory/components/ui/textarea.tsx +24 -0
- package/templates/directory/package.json +1 -0
- package/templates/helpdesk/AGENTS.md +116 -0
- package/templates/helpdesk/CLAUDE.md +7 -0
- package/templates/helpdesk/README.md +90 -0
- package/templates/helpdesk/app/customers/customers-view.tsx +140 -0
- package/templates/helpdesk/app/customers/page.tsx +17 -0
- package/templates/helpdesk/app/error.tsx +43 -0
- package/templates/helpdesk/app/globals.css +201 -0
- package/templates/helpdesk/app/inbox-view.tsx +147 -0
- package/templates/helpdesk/app/layout.tsx +31 -0
- package/templates/helpdesk/app/login/auth-form.tsx +122 -0
- package/templates/helpdesk/app/login/page.tsx +38 -0
- package/templates/helpdesk/app/not-found.tsx +29 -0
- package/templates/helpdesk/app/page.tsx +38 -0
- package/templates/helpdesk/app/robots.ts +12 -0
- package/templates/helpdesk/app/sitemap.ts +27 -0
- package/templates/helpdesk/app/tickets/[id]/page.tsx +23 -0
- package/templates/helpdesk/app/tickets/[id]/ticket-view.tsx +164 -0
- package/templates/helpdesk/app/workspace.tsx +210 -0
- package/templates/helpdesk/app.ts +142 -0
- package/templates/helpdesk/bunfig +4 -0
- package/templates/helpdesk/components/avatar.tsx +40 -0
- package/templates/helpdesk/components/command-palette.tsx +162 -0
- package/templates/helpdesk/components/data-table.tsx +90 -0
- package/templates/helpdesk/components/empty-state.tsx +43 -0
- package/templates/helpdesk/components/kbd.tsx +23 -0
- package/templates/helpdesk/components/new-ticket-dialog.tsx +147 -0
- package/templates/helpdesk/components/page-header.tsx +36 -0
- package/templates/helpdesk/components/priority-badge.tsx +61 -0
- package/templates/helpdesk/components/record-dialog.tsx +126 -0
- package/templates/helpdesk/components/sidebar.tsx +111 -0
- package/templates/helpdesk/components/sla-indicator.tsx +47 -0
- package/templates/helpdesk/components/thread.tsx +143 -0
- package/templates/helpdesk/components/ticket-list.tsx +78 -0
- package/templates/helpdesk/components/ui/badge.tsx +37 -0
- package/templates/helpdesk/components/ui/button.tsx +56 -0
- package/templates/helpdesk/components/ui/card.tsx +90 -0
- package/templates/helpdesk/components/ui/dialog.tsx +110 -0
- package/templates/helpdesk/components/ui/input.tsx +30 -0
- package/templates/helpdesk/components/ui/label.tsx +21 -0
- package/templates/helpdesk/components/ui/select.tsx +39 -0
- package/templates/helpdesk/components/ui/table.tsx +124 -0
- package/templates/helpdesk/components/ui/textarea.tsx +24 -0
- package/templates/helpdesk/components.json +20 -0
- package/templates/helpdesk/functions/createTicket.ts +62 -0
- package/templates/helpdesk/functions/replyToTicket.ts +55 -0
- package/templates/helpdesk/functions/seedWorkspace.ts +52 -0
- package/templates/helpdesk/functions/setTicketState.ts +58 -0
- package/templates/helpdesk/gitignore +10 -0
- package/templates/helpdesk/lib/format.ts +53 -0
- package/templates/helpdesk/lib/search.ts +78 -0
- package/templates/helpdesk/lib/seed.ts +185 -0
- package/templates/helpdesk/lib/tickets.ts +184 -0
- package/templates/helpdesk/lib/utils.ts +10 -0
- package/templates/helpdesk/package.json +39 -0
- package/templates/helpdesk/tests/components.test.tsx +202 -0
- package/templates/helpdesk/tests/search.test.ts +83 -0
- package/templates/helpdesk/tests/setup.ts +5 -0
- package/templates/helpdesk/tests/tickets.test.ts +215 -0
- package/templates/helpdesk/tsconfig.json +33 -0
- package/templates/inventory/AGENTS.md +116 -0
- package/templates/inventory/CLAUDE.md +7 -0
- package/templates/inventory/README.md +103 -0
- package/templates/inventory/app/error.tsx +43 -0
- package/templates/inventory/app/globals.css +201 -0
- package/templates/inventory/app/layout.tsx +31 -0
- package/templates/inventory/app/login/auth-form.tsx +122 -0
- package/templates/inventory/app/login/page.tsx +38 -0
- package/templates/inventory/app/movements/movements-view.tsx +103 -0
- package/templates/inventory/app/movements/page.tsx +17 -0
- package/templates/inventory/app/not-found.tsx +29 -0
- package/templates/inventory/app/page.tsx +34 -0
- package/templates/inventory/app/products/[id]/page.tsx +23 -0
- package/templates/inventory/app/products/[id]/product-view.tsx +178 -0
- package/templates/inventory/app/products-view.tsx +213 -0
- package/templates/inventory/app/robots.ts +12 -0
- package/templates/inventory/app/sitemap.ts +27 -0
- package/templates/inventory/app/workspace.tsx +172 -0
- package/templates/inventory/app.ts +154 -0
- package/templates/inventory/bunfig +4 -0
- package/templates/inventory/components/avatar.tsx +40 -0
- package/templates/inventory/components/command-palette.tsx +162 -0
- package/templates/inventory/components/data-table.tsx +90 -0
- package/templates/inventory/components/empty-state.tsx +43 -0
- package/templates/inventory/components/kbd.tsx +23 -0
- package/templates/inventory/components/movement-dialog.tsx +182 -0
- package/templates/inventory/components/page-header.tsx +36 -0
- package/templates/inventory/components/record-dialog.tsx +126 -0
- package/templates/inventory/components/sidebar.tsx +111 -0
- package/templates/inventory/components/stock-level.tsx +47 -0
- package/templates/inventory/components/summary-bar.tsx +56 -0
- package/templates/inventory/components/ui/badge.tsx +37 -0
- package/templates/inventory/components/ui/button.tsx +56 -0
- package/templates/inventory/components/ui/card.tsx +90 -0
- package/templates/inventory/components/ui/dialog.tsx +110 -0
- package/templates/inventory/components/ui/input.tsx +30 -0
- package/templates/inventory/components/ui/label.tsx +21 -0
- package/templates/inventory/components/ui/select.tsx +39 -0
- package/templates/inventory/components/ui/table.tsx +124 -0
- package/templates/inventory/components/ui/textarea.tsx +24 -0
- package/templates/inventory/components.json +20 -0
- package/templates/inventory/functions/recordMovement.ts +72 -0
- package/templates/inventory/functions/seedWorkspace.ts +40 -0
- package/templates/inventory/gitignore +10 -0
- package/templates/inventory/lib/format.ts +53 -0
- package/templates/inventory/lib/search.ts +78 -0
- package/templates/inventory/lib/seed.ts +102 -0
- package/templates/inventory/lib/stock.ts +207 -0
- package/templates/inventory/lib/utils.ts +10 -0
- package/templates/inventory/package.json +39 -0
- package/templates/inventory/tests/components.test.tsx +155 -0
- package/templates/inventory/tests/search.test.ts +83 -0
- package/templates/inventory/tests/setup.ts +5 -0
- package/templates/inventory/tests/stock.test.ts +255 -0
- package/templates/inventory/tsconfig.json +33 -0
- package/templates/invoices/AGENTS.md +116 -0
- package/templates/invoices/CLAUDE.md +7 -0
- package/templates/invoices/README.md +99 -0
- package/templates/invoices/app/clients/clients-view.tsx +130 -0
- package/templates/invoices/app/clients/page.tsx +17 -0
- package/templates/invoices/app/error.tsx +43 -0
- package/templates/invoices/app/globals.css +201 -0
- package/templates/invoices/app/invoices/[id]/invoice-view.tsx +253 -0
- package/templates/invoices/app/invoices/[id]/page.tsx +23 -0
- package/templates/invoices/app/invoices-view.tsx +183 -0
- package/templates/invoices/app/layout.tsx +31 -0
- package/templates/invoices/app/login/auth-form.tsx +122 -0
- package/templates/invoices/app/login/page.tsx +38 -0
- package/templates/invoices/app/not-found.tsx +29 -0
- package/templates/invoices/app/page.tsx +36 -0
- package/templates/invoices/app/robots.ts +12 -0
- package/templates/invoices/app/sitemap.ts +27 -0
- package/templates/invoices/app/workspace.tsx +174 -0
- package/templates/invoices/app.ts +169 -0
- package/templates/invoices/bunfig +4 -0
- package/templates/invoices/components/avatar.tsx +40 -0
- package/templates/invoices/components/command-palette.tsx +162 -0
- package/templates/invoices/components/data-table.tsx +90 -0
- package/templates/invoices/components/empty-state.tsx +43 -0
- package/templates/invoices/components/kbd.tsx +23 -0
- package/templates/invoices/components/line-items.tsx +172 -0
- package/templates/invoices/components/page-header.tsx +36 -0
- package/templates/invoices/components/payment-dialog.tsx +132 -0
- package/templates/invoices/components/record-dialog.tsx +126 -0
- package/templates/invoices/components/sidebar.tsx +111 -0
- package/templates/invoices/components/status-badge.tsx +35 -0
- package/templates/invoices/components/summary-bar.tsx +65 -0
- package/templates/invoices/components/totals-panel.tsx +70 -0
- package/templates/invoices/components/ui/badge.tsx +37 -0
- package/templates/invoices/components/ui/button.tsx +56 -0
- package/templates/invoices/components/ui/card.tsx +90 -0
- package/templates/invoices/components/ui/dialog.tsx +110 -0
- package/templates/invoices/components/ui/input.tsx +30 -0
- package/templates/invoices/components/ui/label.tsx +21 -0
- package/templates/invoices/components/ui/select.tsx +39 -0
- package/templates/invoices/components/ui/table.tsx +124 -0
- package/templates/invoices/components/ui/textarea.tsx +24 -0
- package/templates/invoices/components.json +20 -0
- package/templates/invoices/functions/createInvoice.ts +49 -0
- package/templates/invoices/functions/recordPayment.ts +72 -0
- package/templates/invoices/functions/seedWorkspace.ts +57 -0
- package/templates/invoices/functions/setInvoiceStatus.ts +35 -0
- package/templates/invoices/gitignore +10 -0
- package/templates/invoices/lib/billing.ts +259 -0
- package/templates/invoices/lib/format.ts +53 -0
- package/templates/invoices/lib/search.ts +78 -0
- package/templates/invoices/lib/seed.ts +190 -0
- package/templates/invoices/lib/utils.ts +10 -0
- package/templates/invoices/package.json +39 -0
- package/templates/invoices/tests/billing.test.ts +283 -0
- package/templates/invoices/tests/components.test.tsx +172 -0
- package/templates/invoices/tests/search.test.ts +83 -0
- package/templates/invoices/tests/setup.ts +5 -0
- package/templates/invoices/tsconfig.json +33 -0
- package/templates/local-service/components/ui/badge.tsx +37 -0
- package/templates/local-service/components/ui/dialog.tsx +110 -0
- package/templates/local-service/components/ui/input.tsx +30 -0
- package/templates/local-service/components/ui/label.tsx +21 -0
- package/templates/local-service/components/ui/select.tsx +39 -0
- package/templates/local-service/components/ui/table.tsx +124 -0
- package/templates/local-service/components/ui/textarea.tsx +24 -0
- package/templates/local-service/package.json +1 -0
- package/templates/marketplace/components/ui/badge.tsx +37 -0
- package/templates/marketplace/components/ui/button.tsx +56 -0
- package/templates/marketplace/components/ui/card.tsx +90 -0
- package/templates/marketplace/components/ui/dialog.tsx +110 -0
- package/templates/marketplace/components/ui/input.tsx +30 -0
- package/templates/marketplace/components/ui/label.tsx +21 -0
- package/templates/marketplace/components/ui/select.tsx +39 -0
- package/templates/marketplace/components/ui/table.tsx +124 -0
- package/templates/marketplace/components/ui/textarea.tsx +24 -0
- package/templates/marketplace/lib/utils.ts +10 -0
- package/templates/marketplace/package.json +1 -0
- package/templates/projects/AGENTS.md +116 -0
- package/templates/projects/CLAUDE.md +7 -0
- package/templates/projects/README.md +96 -0
- package/templates/projects/app/clients/clients-view.tsx +126 -0
- package/templates/projects/app/clients/page.tsx +17 -0
- package/templates/projects/app/error.tsx +43 -0
- package/templates/projects/app/globals.css +201 -0
- package/templates/projects/app/layout.tsx +31 -0
- package/templates/projects/app/login/auth-form.tsx +122 -0
- package/templates/projects/app/login/page.tsx +38 -0
- package/templates/projects/app/not-found.tsx +29 -0
- package/templates/projects/app/page.tsx +30 -0
- package/templates/projects/app/projects/[id]/page.tsx +23 -0
- package/templates/projects/app/projects/[id]/project-view.tsx +179 -0
- package/templates/projects/app/projects-view.tsx +181 -0
- package/templates/projects/app/robots.ts +12 -0
- package/templates/projects/app/sitemap.ts +27 -0
- package/templates/projects/app/workspace.tsx +190 -0
- package/templates/projects/app.ts +186 -0
- package/templates/projects/bunfig +4 -0
- package/templates/projects/components/avatar.tsx +40 -0
- package/templates/projects/components/board-skeleton.tsx +37 -0
- package/templates/projects/components/budget-bar.tsx +61 -0
- package/templates/projects/components/command-palette.tsx +162 -0
- package/templates/projects/components/data-table.tsx +90 -0
- package/templates/projects/components/empty-state.tsx +43 -0
- package/templates/projects/components/kbd.tsx +23 -0
- package/templates/projects/components/page-header.tsx +36 -0
- package/templates/projects/components/record-dialog.tsx +126 -0
- package/templates/projects/components/sidebar.tsx +111 -0
- package/templates/projects/components/task-board.tsx +109 -0
- package/templates/projects/components/task-card.tsx +77 -0
- package/templates/projects/components/time-dialog.tsx +114 -0
- package/templates/projects/components/ui/badge.tsx +37 -0
- package/templates/projects/components/ui/button.tsx +56 -0
- package/templates/projects/components/ui/card.tsx +90 -0
- package/templates/projects/components/ui/dialog.tsx +110 -0
- package/templates/projects/components/ui/input.tsx +30 -0
- package/templates/projects/components/ui/label.tsx +21 -0
- package/templates/projects/components/ui/select.tsx +39 -0
- package/templates/projects/components/ui/table.tsx +124 -0
- package/templates/projects/components/ui/textarea.tsx +24 -0
- package/templates/projects/components.json +20 -0
- package/templates/projects/functions/logTime.ts +49 -0
- package/templates/projects/functions/moveTask.ts +40 -0
- package/templates/projects/functions/seedWorkspace.ts +59 -0
- package/templates/projects/gitignore +10 -0
- package/templates/projects/lib/format.ts +53 -0
- package/templates/projects/lib/search.ts +78 -0
- package/templates/projects/lib/seed.ts +140 -0
- package/templates/projects/lib/utils.ts +10 -0
- package/templates/projects/lib/work.ts +233 -0
- package/templates/projects/package.json +39 -0
- package/templates/projects/tests/components.test.tsx +200 -0
- package/templates/projects/tests/search.test.ts +83 -0
- package/templates/projects/tests/setup.ts +5 -0
- package/templates/projects/tests/work.test.ts +238 -0
- package/templates/projects/tsconfig.json +33 -0
- package/templates/restaurant/components/ui/badge.tsx +37 -0
- package/templates/restaurant/components/ui/dialog.tsx +110 -0
- package/templates/restaurant/components/ui/input.tsx +30 -0
- package/templates/restaurant/components/ui/label.tsx +21 -0
- package/templates/restaurant/components/ui/select.tsx +39 -0
- package/templates/restaurant/components/ui/table.tsx +124 -0
- package/templates/restaurant/components/ui/textarea.tsx +24 -0
- package/templates/restaurant/package.json +1 -0
- package/templates/shop/components/ui/badge.tsx +37 -0
- package/templates/shop/components/ui/dialog.tsx +110 -0
- package/templates/shop/components/ui/input.tsx +30 -0
- package/templates/shop/components/ui/label.tsx +21 -0
- package/templates/shop/components/ui/select.tsx +39 -0
- package/templates/shop/components/ui/table.tsx +124 -0
- package/templates/shop/components/ui/textarea.tsx +24 -0
- package/templates/shop/package.json +1 -0
- package/templates/todo/components/ui/badge.tsx +37 -0
- package/templates/todo/components/ui/dialog.tsx +110 -0
- package/templates/todo/components/ui/input.tsx +30 -0
- package/templates/todo/components/ui/label.tsx +21 -0
- package/templates/todo/components/ui/select.tsx +39 -0
- package/templates/todo/components/ui/table.tsx +124 -0
- package/templates/todo/components/ui/textarea.tsx +24 -0
- package/templates/todo/package.json +1 -0
- package/templates/waitlist/components/ui/badge.tsx +37 -0
- package/templates/waitlist/components/ui/dialog.tsx +110 -0
- package/templates/waitlist/components/ui/input.tsx +30 -0
- package/templates/waitlist/components/ui/label.tsx +21 -0
- package/templates/waitlist/components/ui/select.tsx +39 -0
- package/templates/waitlist/components/ui/table.tsx +124 -0
- package/templates/waitlist/components/ui/textarea.tsx +24 -0
- package/templates/waitlist/package.json +1 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="card"
|
|
9
|
+
className={cn(
|
|
10
|
+
"rounded-xl border bg-card text-card-foreground shadow-sm",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function CardHeader({
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
data-slot="card-header"
|
|
25
|
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function CardTitle({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
35
|
+
return (
|
|
36
|
+
<div
|
|
37
|
+
data-slot="card-title"
|
|
38
|
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function CardDescription({
|
|
45
|
+
className,
|
|
46
|
+
...props
|
|
47
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
data-slot="card-description"
|
|
51
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function CardContent({
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
61
|
+
return (
|
|
62
|
+
<div
|
|
63
|
+
data-slot="card-content"
|
|
64
|
+
className={cn("p-6 pt-0", className)}
|
|
65
|
+
{...props}
|
|
66
|
+
/>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function CardFooter({
|
|
71
|
+
className,
|
|
72
|
+
...props
|
|
73
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="card-footer"
|
|
77
|
+
className={cn("flex items-center p-6 pt-0", className)}
|
|
78
|
+
{...props}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export {
|
|
84
|
+
Card,
|
|
85
|
+
CardHeader,
|
|
86
|
+
CardFooter,
|
|
87
|
+
CardTitle,
|
|
88
|
+
CardDescription,
|
|
89
|
+
CardContent,
|
|
90
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
const Dialog = DialogPrimitive.Root;
|
|
10
|
+
const DialogTrigger = DialogPrimitive.Trigger;
|
|
11
|
+
const DialogPortal = DialogPrimitive.Portal;
|
|
12
|
+
const DialogClose = DialogPrimitive.Close;
|
|
13
|
+
|
|
14
|
+
const DialogOverlay = React.forwardRef<
|
|
15
|
+
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
|
16
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
|
17
|
+
>(({ className, ...props }, ref) => (
|
|
18
|
+
<DialogPrimitive.Overlay
|
|
19
|
+
ref={ref}
|
|
20
|
+
className={cn(
|
|
21
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
|
22
|
+
className,
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
/>
|
|
26
|
+
));
|
|
27
|
+
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
|
|
28
|
+
|
|
29
|
+
const DialogContent = React.forwardRef<
|
|
30
|
+
React.ElementRef<typeof DialogPrimitive.Content>,
|
|
31
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
|
32
|
+
>(({ className, children, ...props }, ref) => (
|
|
33
|
+
<DialogPortal>
|
|
34
|
+
<DialogOverlay />
|
|
35
|
+
<DialogPrimitive.Content
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn(
|
|
38
|
+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-xl border bg-background p-6 shadow-lg duration-200",
|
|
39
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
{children}
|
|
45
|
+
<DialogPrimitive.Close className="absolute right-3 top-3 rounded-md p-1 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring">
|
|
46
|
+
<X className="size-4" />
|
|
47
|
+
<span className="sr-only">Close</span>
|
|
48
|
+
</DialogPrimitive.Close>
|
|
49
|
+
</DialogPrimitive.Content>
|
|
50
|
+
</DialogPortal>
|
|
51
|
+
));
|
|
52
|
+
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
|
53
|
+
|
|
54
|
+
function DialogHeader({
|
|
55
|
+
className,
|
|
56
|
+
...props
|
|
57
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
58
|
+
return (
|
|
59
|
+
<div className={cn("flex flex-col gap-1.5 text-left", className)} {...props} />
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function DialogFooter({
|
|
64
|
+
className,
|
|
65
|
+
...props
|
|
66
|
+
}: React.HTMLAttributes<HTMLDivElement>) {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={cn("flex flex-col-reverse gap-2 sm:flex-row sm:justify-end", className)}
|
|
70
|
+
{...props}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const DialogTitle = React.forwardRef<
|
|
76
|
+
React.ElementRef<typeof DialogPrimitive.Title>,
|
|
77
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
78
|
+
>(({ className, ...props }, ref) => (
|
|
79
|
+
<DialogPrimitive.Title
|
|
80
|
+
ref={ref}
|
|
81
|
+
className={cn("text-base font-semibold leading-none tracking-tight", className)}
|
|
82
|
+
{...props}
|
|
83
|
+
/>
|
|
84
|
+
));
|
|
85
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
86
|
+
|
|
87
|
+
const DialogDescription = React.forwardRef<
|
|
88
|
+
React.ElementRef<typeof DialogPrimitive.Description>,
|
|
89
|
+
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
90
|
+
>(({ className, ...props }, ref) => (
|
|
91
|
+
<DialogPrimitive.Description
|
|
92
|
+
ref={ref}
|
|
93
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
94
|
+
{...props}
|
|
95
|
+
/>
|
|
96
|
+
));
|
|
97
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
Dialog,
|
|
101
|
+
DialogClose,
|
|
102
|
+
DialogContent,
|
|
103
|
+
DialogDescription,
|
|
104
|
+
DialogFooter,
|
|
105
|
+
DialogHeader,
|
|
106
|
+
DialogOverlay,
|
|
107
|
+
DialogPortal,
|
|
108
|
+
DialogTitle,
|
|
109
|
+
DialogTrigger,
|
|
110
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Input({
|
|
6
|
+
className,
|
|
7
|
+
type,
|
|
8
|
+
...props
|
|
9
|
+
}: React.InputHTMLAttributes<HTMLInputElement>) {
|
|
10
|
+
return (
|
|
11
|
+
<input
|
|
12
|
+
type={type}
|
|
13
|
+
data-slot="input"
|
|
14
|
+
className={cn(
|
|
15
|
+
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors",
|
|
16
|
+
"placeholder:text-muted-foreground",
|
|
17
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
18
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
19
|
+
// Date/time inputs render a native picker indicator that ignores the
|
|
20
|
+
// text color; nudge it so it stays visible in dark mode.
|
|
21
|
+
"[&::-webkit-calendar-picker-indicator]:opacity-60 dark:[&::-webkit-calendar-picker-indicator]:invert",
|
|
22
|
+
"file:border-0 file:bg-transparent file:text-sm file:font-medium",
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...props}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { Input };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Label({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: React.LabelHTMLAttributes<HTMLLabelElement>) {
|
|
9
|
+
return (
|
|
10
|
+
<label
|
|
11
|
+
data-slot="label"
|
|
12
|
+
className={cn(
|
|
13
|
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
|
14
|
+
className,
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Label };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A styled native `<select>`.
|
|
8
|
+
*
|
|
9
|
+
* shadcn's Select is a Radix listbox; this deliberately isn't. A native select
|
|
10
|
+
* needs no extra dependency, is keyboard- and screen-reader-correct for free,
|
|
11
|
+
* and opens as the platform picker on mobile — which is what you want for the
|
|
12
|
+
* plain "pick one value" case that scaffolded forms are full of. Reach for
|
|
13
|
+
* `npx shadcn add select` when you need custom option rendering or search.
|
|
14
|
+
*/
|
|
15
|
+
function Select({
|
|
16
|
+
className,
|
|
17
|
+
children,
|
|
18
|
+
...props
|
|
19
|
+
}: React.SelectHTMLAttributes<HTMLSelectElement>) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="relative">
|
|
22
|
+
<select
|
|
23
|
+
data-slot="select"
|
|
24
|
+
className={cn(
|
|
25
|
+
"flex h-9 w-full appearance-none rounded-md border border-input bg-transparent py-1 pl-3 pr-8 text-sm shadow-sm transition-colors",
|
|
26
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
27
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</select>
|
|
34
|
+
<ChevronDown className="pointer-events-none absolute right-2.5 top-1/2 size-4 -translate-y-1/2 opacity-50" />
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export { Select };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
// The wrapper owns the horizontal scroll so a wide table never widens the page.
|
|
6
|
+
function Table({ className, ...props }: React.HTMLAttributes<HTMLTableElement>) {
|
|
7
|
+
return (
|
|
8
|
+
<div className="relative w-full overflow-x-auto">
|
|
9
|
+
<table
|
|
10
|
+
data-slot="table"
|
|
11
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
12
|
+
{...props}
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function TableHeader({
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}: React.HTMLAttributes<HTMLTableSectionElement>) {
|
|
22
|
+
return (
|
|
23
|
+
<thead
|
|
24
|
+
data-slot="table-header"
|
|
25
|
+
className={cn("[&_tr]:border-b", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TableBody({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.HTMLAttributes<HTMLTableSectionElement>) {
|
|
35
|
+
return (
|
|
36
|
+
<tbody
|
|
37
|
+
data-slot="table-body"
|
|
38
|
+
className={cn("[&_tr:last-child]:border-0", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function TableFooter({
|
|
45
|
+
className,
|
|
46
|
+
...props
|
|
47
|
+
}: React.HTMLAttributes<HTMLTableSectionElement>) {
|
|
48
|
+
return (
|
|
49
|
+
<tfoot
|
|
50
|
+
data-slot="table-footer"
|
|
51
|
+
className={cn("border-t bg-muted/50 font-medium", className)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function TableRow({
|
|
58
|
+
className,
|
|
59
|
+
...props
|
|
60
|
+
}: React.HTMLAttributes<HTMLTableRowElement>) {
|
|
61
|
+
return (
|
|
62
|
+
<tr
|
|
63
|
+
data-slot="table-row"
|
|
64
|
+
className={cn(
|
|
65
|
+
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
|
66
|
+
className,
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function TableHead({
|
|
74
|
+
className,
|
|
75
|
+
...props
|
|
76
|
+
}: React.ThHTMLAttributes<HTMLTableCellElement>) {
|
|
77
|
+
return (
|
|
78
|
+
<th
|
|
79
|
+
data-slot="table-head"
|
|
80
|
+
className={cn(
|
|
81
|
+
"h-10 px-3 text-left align-middle text-xs font-medium text-muted-foreground",
|
|
82
|
+
className,
|
|
83
|
+
)}
|
|
84
|
+
{...props}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function TableCell({
|
|
90
|
+
className,
|
|
91
|
+
...props
|
|
92
|
+
}: React.TdHTMLAttributes<HTMLTableCellElement>) {
|
|
93
|
+
return (
|
|
94
|
+
<td
|
|
95
|
+
data-slot="table-cell"
|
|
96
|
+
className={cn("p-3 align-middle", className)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function TableCaption({
|
|
103
|
+
className,
|
|
104
|
+
...props
|
|
105
|
+
}: React.HTMLAttributes<HTMLTableCaptionElement>) {
|
|
106
|
+
return (
|
|
107
|
+
<caption
|
|
108
|
+
data-slot="table-caption"
|
|
109
|
+
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
|
110
|
+
{...props}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
Table,
|
|
117
|
+
TableHeader,
|
|
118
|
+
TableBody,
|
|
119
|
+
TableFooter,
|
|
120
|
+
TableHead,
|
|
121
|
+
TableRow,
|
|
122
|
+
TableCell,
|
|
123
|
+
TableCaption,
|
|
124
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
function Textarea({
|
|
6
|
+
className,
|
|
7
|
+
...props
|
|
8
|
+
}: React.TextareaHTMLAttributes<HTMLTextAreaElement>) {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
data-slot="textarea"
|
|
12
|
+
className={cn(
|
|
13
|
+
"flex min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors",
|
|
14
|
+
"placeholder:text-muted-foreground",
|
|
15
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
16
|
+
"disabled:cursor-not-allowed disabled:opacity-50",
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Textarea };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "zinc",
|
|
10
|
+
"cssVariables": true
|
|
11
|
+
},
|
|
12
|
+
"aliases": {
|
|
13
|
+
"components": "@/components",
|
|
14
|
+
"utils": "@/lib/utils",
|
|
15
|
+
"ui": "@/components/ui",
|
|
16
|
+
"lib": "@/lib",
|
|
17
|
+
"hooks": "@/hooks"
|
|
18
|
+
},
|
|
19
|
+
"iconLibrary": "lucide"
|
|
20
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { mutation, v } from "@pylonsync/functions";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Log time against a task.
|
|
5
|
+
*
|
|
6
|
+
* A mutation rather than a bare insert so `projectId` is taken from the TASK
|
|
7
|
+
* rather than trusted from the client — a mislabelled entry would land on
|
|
8
|
+
* another client\'s invoice, which is the worst failure this app has.
|
|
9
|
+
*
|
|
10
|
+
* Entries are append-only (see the policy). A correction is a NEGATIVE entry,
|
|
11
|
+
* which stays visible in the history rather than quietly changing what someone
|
|
12
|
+
* was billed.
|
|
13
|
+
*/
|
|
14
|
+
export default mutation<
|
|
15
|
+
{ taskId: string; minutes: number; note?: string },
|
|
16
|
+
{ ok: true; id: string }
|
|
17
|
+
>({
|
|
18
|
+
auth: "user",
|
|
19
|
+
args: {
|
|
20
|
+
taskId: v.id("Task"),
|
|
21
|
+
minutes: v.number(),
|
|
22
|
+
note: v.optional(v.string()),
|
|
23
|
+
},
|
|
24
|
+
async handler(ctx, args) {
|
|
25
|
+
const minutes = Math.trunc(Number(args.minutes) || 0);
|
|
26
|
+
if (minutes === 0) {
|
|
27
|
+
throw ctx.error("INVALID_ARGS", "Log a non-zero number of minutes.");
|
|
28
|
+
}
|
|
29
|
+
// A day has 1440 minutes; anything past that is a typo, and a typo on a
|
|
30
|
+
// timesheet becomes a typo on an invoice.
|
|
31
|
+
if (Math.abs(minutes) > 1440) {
|
|
32
|
+
throw ctx.error("INVALID_ARGS", "That's more than a day — check the value.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const task = await ctx.db.get("Task", args.taskId);
|
|
36
|
+
if (!task) throw ctx.error("NOT_FOUND", "Task not found.");
|
|
37
|
+
|
|
38
|
+
const id = await ctx.db.insert("TimeEntry", {
|
|
39
|
+
taskId: args.taskId,
|
|
40
|
+
projectId: task.projectId,
|
|
41
|
+
minutes,
|
|
42
|
+
note: args.note?.trim() || null,
|
|
43
|
+
userId: ctx.auth.userId,
|
|
44
|
+
spentOn: new Date().toISOString(),
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return { ok: true, id: id as string } as const;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { mutation, v } from "@pylonsync/functions";
|
|
2
|
+
import { isValidTaskStatus } from "../lib/work";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Move a task to another column.
|
|
6
|
+
*
|
|
7
|
+
* Validated server-side: the board only sends known statuses, but the endpoint
|
|
8
|
+
* is reachable directly and an unknown status would strand the task in a column
|
|
9
|
+
* that never renders.
|
|
10
|
+
*
|
|
11
|
+
* The new position is the end of the target column, computed here rather than
|
|
12
|
+
* sent by the client — two people dragging into the same column simultaneously
|
|
13
|
+
* would otherwise both claim the same index.
|
|
14
|
+
*/
|
|
15
|
+
export default mutation<{ taskId: string; status: string }, { ok: true }>({
|
|
16
|
+
auth: "user",
|
|
17
|
+
args: { taskId: v.id("Task"), status: v.string() },
|
|
18
|
+
async handler(ctx, args) {
|
|
19
|
+
if (!isValidTaskStatus(args.status)) {
|
|
20
|
+
throw ctx.error("INVALID_ARGS", `Unknown status "${args.status}".`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const task = await ctx.db.get("Task", args.taskId);
|
|
24
|
+
if (!task) throw ctx.error("NOT_FOUND", "Task not found.");
|
|
25
|
+
if (task.status === args.status) return { ok: true } as const;
|
|
26
|
+
|
|
27
|
+
const siblings = (await ctx.db.query("Task", {
|
|
28
|
+
projectId: task.projectId,
|
|
29
|
+
})) as Array<{ status?: string; position?: number }>;
|
|
30
|
+
const end = siblings.filter((row) => row.status === args.status).length;
|
|
31
|
+
|
|
32
|
+
await ctx.db.update("Task", args.taskId, {
|
|
33
|
+
status: args.status,
|
|
34
|
+
position: end,
|
|
35
|
+
updatedAt: new Date().toISOString(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return { ok: true } as const;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { mutation } from "@pylonsync/functions";
|
|
2
|
+
import { shapeSeed } from "../lib/seed";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fill a brand-new delivery workspace once.
|
|
6
|
+
*
|
|
7
|
+
* Seeds time as ENTRIES rather than totals, because logged time here is the sum
|
|
8
|
+
* of the ledger — a seed that wrote totals would contradict the app\'s own model.
|
|
9
|
+
* One project lands over its budget so that state is visible on first load.
|
|
10
|
+
*
|
|
11
|
+
* Returns immediately if any project exists, so it is safe on every load.
|
|
12
|
+
*/
|
|
13
|
+
export default mutation<Record<string, never>, { seeded: boolean }>({
|
|
14
|
+
auth: "user",
|
|
15
|
+
args: {},
|
|
16
|
+
async handler(ctx) {
|
|
17
|
+
const existing = await ctx.db.query("Project", { $limit: 1 });
|
|
18
|
+
if (existing.length > 0) return { seeded: false };
|
|
19
|
+
|
|
20
|
+
const seed = shapeSeed();
|
|
21
|
+
const me = ctx.auth.userId;
|
|
22
|
+
|
|
23
|
+
const clientIds = new Map<string, string>();
|
|
24
|
+
for (const client of seed.clients) {
|
|
25
|
+
const id = await ctx.db.insert("Client", client.row);
|
|
26
|
+
clientIds.set(client.key, id as string);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const projectIds = new Map<string, string>();
|
|
30
|
+
for (const project of seed.projects) {
|
|
31
|
+
const id = await ctx.db.insert("Project", {
|
|
32
|
+
...project.row,
|
|
33
|
+
clientId: clientIds.get(project.client) ?? null,
|
|
34
|
+
});
|
|
35
|
+
projectIds.set(project.key, id as string);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const taskIds = new Map<string, string>();
|
|
39
|
+
for (const task of seed.tasks) {
|
|
40
|
+
const id = await ctx.db.insert("Task", {
|
|
41
|
+
...task.row,
|
|
42
|
+
projectId: projectIds.get(task.project) ?? null,
|
|
43
|
+
assigneeId: me,
|
|
44
|
+
});
|
|
45
|
+
taskIds.set(task.key, id as string);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const entry of seed.entries) {
|
|
49
|
+
await ctx.db.insert("TimeEntry", {
|
|
50
|
+
...entry.row,
|
|
51
|
+
taskId: taskIds.get(entry.task) ?? null,
|
|
52
|
+
projectId: projectIds.get(entry.project) ?? null,
|
|
53
|
+
userId: me,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { seeded: true };
|
|
58
|
+
},
|
|
59
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Small display helpers shared across views. Pure, so the timeline and the
|
|
2
|
+
// queue can't drift on how they render the same instant.
|
|
3
|
+
|
|
4
|
+
/** Initials for an avatar: "Dana Whitfield" → "DW", "jordan@x.com" → "JO". */
|
|
5
|
+
export function initials(nameOrEmail: string | null | undefined): string {
|
|
6
|
+
const raw = (nameOrEmail ?? "").trim();
|
|
7
|
+
if (!raw) return "?";
|
|
8
|
+
const name = raw.includes("@") ? raw.split("@")[0] : raw;
|
|
9
|
+
const parts = name.split(/[\s._-]+/).filter(Boolean);
|
|
10
|
+
if (parts.length === 0) return "?";
|
|
11
|
+
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
12
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A stable accent per record, so the same person keeps their colour. */
|
|
16
|
+
export function accentIndex(seed: string, buckets = 6): number {
|
|
17
|
+
let hash = 0;
|
|
18
|
+
for (let i = 0; i < seed.length; i += 1) {
|
|
19
|
+
hash = (hash * 31 + seed.charCodeAt(i)) | 0;
|
|
20
|
+
}
|
|
21
|
+
return Math.abs(hash) % buckets;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** "just now" / "4h ago" / "Mar 3". */
|
|
25
|
+
export function relativeTime(
|
|
26
|
+
iso: string | null | undefined,
|
|
27
|
+
now: number = Date.now(),
|
|
28
|
+
): string {
|
|
29
|
+
if (!iso) return "";
|
|
30
|
+
const then = Date.parse(iso);
|
|
31
|
+
if (!Number.isFinite(then)) return "";
|
|
32
|
+
const seconds = Math.round((now - then) / 1000);
|
|
33
|
+
if (seconds < 45) return "just now";
|
|
34
|
+
const minutes = Math.round(seconds / 60);
|
|
35
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
36
|
+
const hours = Math.round(minutes / 60);
|
|
37
|
+
if (hours < 24) return `${hours}h ago`;
|
|
38
|
+
const days = Math.round(hours / 24);
|
|
39
|
+
if (days < 7) return `${days}d ago`;
|
|
40
|
+
return new Date(then).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** "3h 20m" / "45m" / "2d" — a duration an agent reads at a glance. */
|
|
44
|
+
export function duration(minutes: number): string {
|
|
45
|
+
const abs = Math.abs(Math.round(minutes));
|
|
46
|
+
if (abs < 60) return `${abs}m`;
|
|
47
|
+
if (abs < 1440) {
|
|
48
|
+
const hours = Math.floor(abs / 60);
|
|
49
|
+
const rest = abs % 60;
|
|
50
|
+
return rest === 0 ? `${hours}h` : `${hours}h ${rest}m`;
|
|
51
|
+
}
|
|
52
|
+
return `${Math.floor(abs / 1440)}d`;
|
|
53
|
+
}
|