availsync 0.1.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/.adal/skills/stripe-best-practices/SKILL.md +42 -0
- package/.adal/skills/stripe-best-practices/references/billing.md +36 -0
- package/.adal/skills/stripe-best-practices/references/connect.md +48 -0
- package/.adal/skills/stripe-best-practices/references/payments.md +79 -0
- package/.adal/skills/stripe-best-practices/references/security.md +109 -0
- package/.adal/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.adal/skills/stripe-projects/SKILL.md +139 -0
- package/.adal/skills/upgrade-stripe/SKILL.md +185 -0
- package/.agents/skills/stripe-best-practices/SKILL.md +42 -0
- package/.agents/skills/stripe-best-practices/references/billing.md +36 -0
- package/.agents/skills/stripe-best-practices/references/connect.md +48 -0
- package/.agents/skills/stripe-best-practices/references/payments.md +79 -0
- package/.agents/skills/stripe-best-practices/references/security.md +109 -0
- package/.agents/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.agents/skills/stripe-projects/SKILL.md +139 -0
- package/.agents/skills/upgrade-stripe/SKILL.md +185 -0
- package/.augment/skills/stripe-best-practices/SKILL.md +42 -0
- package/.augment/skills/stripe-best-practices/references/billing.md +36 -0
- package/.augment/skills/stripe-best-practices/references/connect.md +48 -0
- package/.augment/skills/stripe-best-practices/references/payments.md +79 -0
- package/.augment/skills/stripe-best-practices/references/security.md +109 -0
- package/.augment/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.augment/skills/stripe-projects/SKILL.md +139 -0
- package/.augment/skills/upgrade-stripe/SKILL.md +185 -0
- package/.bob/skills/stripe-best-practices/SKILL.md +42 -0
- package/.bob/skills/stripe-best-practices/references/billing.md +36 -0
- package/.bob/skills/stripe-best-practices/references/connect.md +48 -0
- package/.bob/skills/stripe-best-practices/references/payments.md +79 -0
- package/.bob/skills/stripe-best-practices/references/security.md +109 -0
- package/.bob/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.bob/skills/stripe-projects/SKILL.md +139 -0
- package/.bob/skills/upgrade-stripe/SKILL.md +185 -0
- package/.claude/settings.local.json +7 -0
- package/.claude/skills/stripe-best-practices/SKILL.md +42 -0
- package/.claude/skills/stripe-best-practices/references/billing.md +36 -0
- package/.claude/skills/stripe-best-practices/references/connect.md +48 -0
- package/.claude/skills/stripe-best-practices/references/payments.md +79 -0
- package/.claude/skills/stripe-best-practices/references/security.md +109 -0
- package/.claude/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.claude/skills/stripe-projects/SKILL.md +139 -0
- package/.claude/skills/upgrade-stripe/SKILL.md +185 -0
- package/.codebuddy/skills/stripe-best-practices/SKILL.md +42 -0
- package/.codebuddy/skills/stripe-best-practices/references/billing.md +36 -0
- package/.codebuddy/skills/stripe-best-practices/references/connect.md +48 -0
- package/.codebuddy/skills/stripe-best-practices/references/payments.md +79 -0
- package/.codebuddy/skills/stripe-best-practices/references/security.md +109 -0
- package/.codebuddy/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.codebuddy/skills/stripe-projects/SKILL.md +139 -0
- package/.codebuddy/skills/upgrade-stripe/SKILL.md +185 -0
- package/.commandcode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.commandcode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.commandcode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.commandcode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.commandcode/skills/stripe-best-practices/references/security.md +109 -0
- package/.commandcode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.commandcode/skills/stripe-projects/SKILL.md +139 -0
- package/.commandcode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.continue/skills/stripe-best-practices/SKILL.md +42 -0
- package/.continue/skills/stripe-best-practices/references/billing.md +36 -0
- package/.continue/skills/stripe-best-practices/references/connect.md +48 -0
- package/.continue/skills/stripe-best-practices/references/payments.md +79 -0
- package/.continue/skills/stripe-best-practices/references/security.md +109 -0
- package/.continue/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.continue/skills/stripe-projects/SKILL.md +139 -0
- package/.continue/skills/upgrade-stripe/SKILL.md +185 -0
- package/.cortex/skills/stripe-best-practices/SKILL.md +42 -0
- package/.cortex/skills/stripe-best-practices/references/billing.md +36 -0
- package/.cortex/skills/stripe-best-practices/references/connect.md +48 -0
- package/.cortex/skills/stripe-best-practices/references/payments.md +79 -0
- package/.cortex/skills/stripe-best-practices/references/security.md +109 -0
- package/.cortex/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.cortex/skills/stripe-projects/SKILL.md +139 -0
- package/.cortex/skills/upgrade-stripe/SKILL.md +185 -0
- package/.crush/skills/stripe-best-practices/SKILL.md +42 -0
- package/.crush/skills/stripe-best-practices/references/billing.md +36 -0
- package/.crush/skills/stripe-best-practices/references/connect.md +48 -0
- package/.crush/skills/stripe-best-practices/references/payments.md +79 -0
- package/.crush/skills/stripe-best-practices/references/security.md +109 -0
- package/.crush/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.crush/skills/stripe-projects/SKILL.md +139 -0
- package/.crush/skills/upgrade-stripe/SKILL.md +185 -0
- package/.env.example +20 -0
- package/.factory/skills/stripe-best-practices/SKILL.md +42 -0
- package/.factory/skills/stripe-best-practices/references/billing.md +36 -0
- package/.factory/skills/stripe-best-practices/references/connect.md +48 -0
- package/.factory/skills/stripe-best-practices/references/payments.md +79 -0
- package/.factory/skills/stripe-best-practices/references/security.md +109 -0
- package/.factory/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.factory/skills/stripe-projects/SKILL.md +139 -0
- package/.factory/skills/upgrade-stripe/SKILL.md +185 -0
- package/.goose/skills/stripe-best-practices/SKILL.md +42 -0
- package/.goose/skills/stripe-best-practices/references/billing.md +36 -0
- package/.goose/skills/stripe-best-practices/references/connect.md +48 -0
- package/.goose/skills/stripe-best-practices/references/payments.md +79 -0
- package/.goose/skills/stripe-best-practices/references/security.md +109 -0
- package/.goose/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.goose/skills/stripe-projects/SKILL.md +139 -0
- package/.goose/skills/upgrade-stripe/SKILL.md +185 -0
- package/.iflow/skills/stripe-best-practices/SKILL.md +42 -0
- package/.iflow/skills/stripe-best-practices/references/billing.md +36 -0
- package/.iflow/skills/stripe-best-practices/references/connect.md +48 -0
- package/.iflow/skills/stripe-best-practices/references/payments.md +79 -0
- package/.iflow/skills/stripe-best-practices/references/security.md +109 -0
- package/.iflow/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.iflow/skills/stripe-projects/SKILL.md +139 -0
- package/.iflow/skills/upgrade-stripe/SKILL.md +185 -0
- package/.junie/skills/stripe-best-practices/SKILL.md +42 -0
- package/.junie/skills/stripe-best-practices/references/billing.md +36 -0
- package/.junie/skills/stripe-best-practices/references/connect.md +48 -0
- package/.junie/skills/stripe-best-practices/references/payments.md +79 -0
- package/.junie/skills/stripe-best-practices/references/security.md +109 -0
- package/.junie/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.junie/skills/stripe-projects/SKILL.md +139 -0
- package/.junie/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kilocode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kilocode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kilocode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kilocode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kilocode/skills/stripe-best-practices/references/security.md +109 -0
- package/.kilocode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kilocode/skills/stripe-projects/SKILL.md +139 -0
- package/.kilocode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kiro/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kiro/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kiro/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kiro/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kiro/skills/stripe-best-practices/references/security.md +109 -0
- package/.kiro/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kiro/skills/stripe-projects/SKILL.md +139 -0
- package/.kiro/skills/upgrade-stripe/SKILL.md +185 -0
- package/.kode/skills/stripe-best-practices/SKILL.md +42 -0
- package/.kode/skills/stripe-best-practices/references/billing.md +36 -0
- package/.kode/skills/stripe-best-practices/references/connect.md +48 -0
- package/.kode/skills/stripe-best-practices/references/payments.md +79 -0
- package/.kode/skills/stripe-best-practices/references/security.md +109 -0
- package/.kode/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.kode/skills/stripe-projects/SKILL.md +139 -0
- package/.kode/skills/upgrade-stripe/SKILL.md +185 -0
- package/.mcpjam/skills/stripe-best-practices/SKILL.md +42 -0
- package/.mcpjam/skills/stripe-best-practices/references/billing.md +36 -0
- package/.mcpjam/skills/stripe-best-practices/references/connect.md +48 -0
- package/.mcpjam/skills/stripe-best-practices/references/payments.md +79 -0
- package/.mcpjam/skills/stripe-best-practices/references/security.md +109 -0
- package/.mcpjam/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.mcpjam/skills/stripe-projects/SKILL.md +139 -0
- package/.mcpjam/skills/upgrade-stripe/SKILL.md +185 -0
- package/.mux/skills/stripe-best-practices/SKILL.md +42 -0
- package/.mux/skills/stripe-best-practices/references/billing.md +36 -0
- package/.mux/skills/stripe-best-practices/references/connect.md +48 -0
- package/.mux/skills/stripe-best-practices/references/payments.md +79 -0
- package/.mux/skills/stripe-best-practices/references/security.md +109 -0
- package/.mux/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.mux/skills/stripe-projects/SKILL.md +139 -0
- package/.mux/skills/upgrade-stripe/SKILL.md +185 -0
- package/.neovate/skills/stripe-best-practices/SKILL.md +42 -0
- package/.neovate/skills/stripe-best-practices/references/billing.md +36 -0
- package/.neovate/skills/stripe-best-practices/references/connect.md +48 -0
- package/.neovate/skills/stripe-best-practices/references/payments.md +79 -0
- package/.neovate/skills/stripe-best-practices/references/security.md +109 -0
- package/.neovate/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.neovate/skills/stripe-projects/SKILL.md +139 -0
- package/.neovate/skills/upgrade-stripe/SKILL.md +185 -0
- package/.nixpacksignore +14 -0
- package/.openhands/skills/stripe-best-practices/SKILL.md +42 -0
- package/.openhands/skills/stripe-best-practices/references/billing.md +36 -0
- package/.openhands/skills/stripe-best-practices/references/connect.md +48 -0
- package/.openhands/skills/stripe-best-practices/references/payments.md +79 -0
- package/.openhands/skills/stripe-best-practices/references/security.md +109 -0
- package/.openhands/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.openhands/skills/stripe-projects/SKILL.md +139 -0
- package/.openhands/skills/upgrade-stripe/SKILL.md +185 -0
- package/.pi/skills/stripe-best-practices/SKILL.md +42 -0
- package/.pi/skills/stripe-best-practices/references/billing.md +36 -0
- package/.pi/skills/stripe-best-practices/references/connect.md +48 -0
- package/.pi/skills/stripe-best-practices/references/payments.md +79 -0
- package/.pi/skills/stripe-best-practices/references/security.md +109 -0
- package/.pi/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.pi/skills/stripe-projects/SKILL.md +139 -0
- package/.pi/skills/upgrade-stripe/SKILL.md +185 -0
- package/.pochi/skills/stripe-best-practices/SKILL.md +42 -0
- package/.pochi/skills/stripe-best-practices/references/billing.md +36 -0
- package/.pochi/skills/stripe-best-practices/references/connect.md +48 -0
- package/.pochi/skills/stripe-best-practices/references/payments.md +79 -0
- package/.pochi/skills/stripe-best-practices/references/security.md +109 -0
- package/.pochi/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.pochi/skills/stripe-projects/SKILL.md +139 -0
- package/.pochi/skills/upgrade-stripe/SKILL.md +185 -0
- package/.qoder/skills/stripe-best-practices/SKILL.md +42 -0
- package/.qoder/skills/stripe-best-practices/references/billing.md +36 -0
- package/.qoder/skills/stripe-best-practices/references/connect.md +48 -0
- package/.qoder/skills/stripe-best-practices/references/payments.md +79 -0
- package/.qoder/skills/stripe-best-practices/references/security.md +109 -0
- package/.qoder/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.qoder/skills/stripe-projects/SKILL.md +139 -0
- package/.qoder/skills/upgrade-stripe/SKILL.md +185 -0
- package/.qwen/skills/stripe-best-practices/SKILL.md +42 -0
- package/.qwen/skills/stripe-best-practices/references/billing.md +36 -0
- package/.qwen/skills/stripe-best-practices/references/connect.md +48 -0
- package/.qwen/skills/stripe-best-practices/references/payments.md +79 -0
- package/.qwen/skills/stripe-best-practices/references/security.md +109 -0
- package/.qwen/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.qwen/skills/stripe-projects/SKILL.md +139 -0
- package/.qwen/skills/upgrade-stripe/SKILL.md +185 -0
- package/.roo/skills/stripe-best-practices/SKILL.md +42 -0
- package/.roo/skills/stripe-best-practices/references/billing.md +36 -0
- package/.roo/skills/stripe-best-practices/references/connect.md +48 -0
- package/.roo/skills/stripe-best-practices/references/payments.md +79 -0
- package/.roo/skills/stripe-best-practices/references/security.md +109 -0
- package/.roo/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.roo/skills/stripe-projects/SKILL.md +139 -0
- package/.roo/skills/upgrade-stripe/SKILL.md +185 -0
- package/.trae/skills/stripe-best-practices/SKILL.md +42 -0
- package/.trae/skills/stripe-best-practices/references/billing.md +36 -0
- package/.trae/skills/stripe-best-practices/references/connect.md +48 -0
- package/.trae/skills/stripe-best-practices/references/payments.md +79 -0
- package/.trae/skills/stripe-best-practices/references/security.md +109 -0
- package/.trae/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.trae/skills/stripe-projects/SKILL.md +139 -0
- package/.trae/skills/upgrade-stripe/SKILL.md +185 -0
- package/.vibe/skills/stripe-best-practices/SKILL.md +42 -0
- package/.vibe/skills/stripe-best-practices/references/billing.md +36 -0
- package/.vibe/skills/stripe-best-practices/references/connect.md +48 -0
- package/.vibe/skills/stripe-best-practices/references/payments.md +79 -0
- package/.vibe/skills/stripe-best-practices/references/security.md +109 -0
- package/.vibe/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.vibe/skills/stripe-projects/SKILL.md +139 -0
- package/.vibe/skills/upgrade-stripe/SKILL.md +185 -0
- package/.windsurf/skills/stripe-best-practices/SKILL.md +42 -0
- package/.windsurf/skills/stripe-best-practices/references/billing.md +36 -0
- package/.windsurf/skills/stripe-best-practices/references/connect.md +48 -0
- package/.windsurf/skills/stripe-best-practices/references/payments.md +79 -0
- package/.windsurf/skills/stripe-best-practices/references/security.md +109 -0
- package/.windsurf/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.windsurf/skills/stripe-projects/SKILL.md +139 -0
- package/.windsurf/skills/upgrade-stripe/SKILL.md +185 -0
- package/.zencoder/skills/stripe-best-practices/SKILL.md +42 -0
- package/.zencoder/skills/stripe-best-practices/references/billing.md +36 -0
- package/.zencoder/skills/stripe-best-practices/references/connect.md +48 -0
- package/.zencoder/skills/stripe-best-practices/references/payments.md +79 -0
- package/.zencoder/skills/stripe-best-practices/references/security.md +109 -0
- package/.zencoder/skills/stripe-best-practices/references/treasury.md +16 -0
- package/.zencoder/skills/stripe-projects/SKILL.md +139 -0
- package/.zencoder/skills/upgrade-stripe/SKILL.md +185 -0
- package/AUDIT.md +95 -0
- package/BLOCKERS.md +0 -0
- package/COOLIFY.md +51 -0
- package/MCP_SETUP.md +23 -0
- package/PRODUCTION_CHECKLIST.md +246 -0
- package/README.md +47 -0
- package/ROADMAP.md +91 -0
- package/docs/superpowers/plans/2026-05-11-availsync-frontend-sales-flow.md +2445 -0
- package/frontend/.env.example +2 -0
- package/frontend/app/admin/layout.tsx +13 -0
- package/frontend/app/admin/page.tsx +747 -0
- package/frontend/app/app/activity/page.tsx +257 -0
- package/frontend/app/app/agents/[agentId]/page.tsx +21 -0
- package/frontend/app/app/agents/page.tsx +1155 -0
- package/frontend/app/app/audit/page.tsx +225 -0
- package/frontend/app/app/availability/page.tsx +840 -0
- package/frontend/app/app/holds/page.tsx +262 -0
- package/frontend/app/app/layout.tsx +19 -0
- package/frontend/app/app/onboarding/page.tsx +10 -0
- package/frontend/app/app/onboarding/verify/page.tsx +309 -0
- package/frontend/app/app/page.tsx +508 -0
- package/frontend/app/app/settings/page.tsx +399 -0
- package/frontend/app/app/work/page.tsx +426 -0
- package/frontend/app/changelog/page.tsx +93 -0
- package/frontend/app/checkout/page.tsx +25 -0
- package/frontend/app/docs/api/page.tsx +157 -0
- package/frontend/app/docs/page.tsx +296 -0
- package/frontend/app/docs/pilot/page.tsx +127 -0
- package/frontend/app/docs/quickstart/page.tsx +318 -0
- package/frontend/app/docs/reliability/page.tsx +78 -0
- package/frontend/app/docs/sdk/node/page.tsx +166 -0
- package/frontend/app/globals.css +57 -0
- package/frontend/app/icon.png +0 -0
- package/frontend/app/layout.tsx +87 -0
- package/frontend/app/login/page.tsx +14 -0
- package/frontend/app/page.tsx +47 -0
- package/frontend/app/pricing/page.tsx +66 -0
- package/frontend/app/privacy/page.tsx +52 -0
- package/frontend/app/robots.ts +26 -0
- package/frontend/app/security/page.tsx +74 -0
- package/frontend/app/signup/page.tsx +14 -0
- package/frontend/app/sitemap.ts +14 -0
- package/frontend/app/terms/page.tsx +51 -0
- package/frontend/components/brand/AvailsyncLogo.tsx +56 -0
- package/frontend/components/checkout/CheckoutClient.tsx +100 -0
- package/frontend/components/dashboard/AgentForm.tsx +59 -0
- package/frontend/components/dashboard/AppShell.tsx +291 -0
- package/frontend/components/dashboard/AvailabilityChecker.tsx +117 -0
- package/frontend/components/dashboard/AvailabilityWindowForm.tsx +40 -0
- package/frontend/components/dashboard/HoldForm.tsx +133 -0
- package/frontend/components/dashboard/MetricCard.tsx +10 -0
- package/frontend/components/login/LoginForm.tsx +95 -0
- package/frontend/components/marketing/AgentCoordinationStory.tsx +1530 -0
- package/frontend/components/marketing/Faq.tsx +41 -0
- package/frontend/components/marketing/Hero.tsx +73 -0
- package/frontend/components/marketing/HowItWorks.tsx +28 -0
- package/frontend/components/marketing/ObserveModeTeaser.tsx +41 -0
- package/frontend/components/marketing/PricingTeaser.tsx +23 -0
- package/frontend/components/marketing/ProblemSolution.tsx +36 -0
- package/frontend/components/marketing/SiteFooter.tsx +59 -0
- package/frontend/components/marketing/SiteHeader.tsx +45 -0
- package/frontend/components/marketing/UseCases.tsx +27 -0
- package/frontend/components/onboarding/OnboardingClient.tsx +278 -0
- package/frontend/components/pricing/PricingCards.tsx +65 -0
- package/frontend/components/privacy/CookieConsent.tsx +230 -0
- package/frontend/components/privacy/CookieSettingsButton.tsx +15 -0
- package/frontend/components/seo/JsonLd.tsx +10 -0
- package/frontend/components/signup/SignupForm.tsx +55 -0
- package/frontend/components/ui/Badge.tsx +23 -0
- package/frontend/components/ui/Button.tsx +37 -0
- package/frontend/components/ui/Card.tsx +11 -0
- package/frontend/components/ui/ConfirmDialog.tsx +77 -0
- package/frontend/components/ui/EmptyState.tsx +24 -0
- package/frontend/components/ui/Input.tsx +14 -0
- package/frontend/components/ui/KeyDisplay.tsx +49 -0
- package/frontend/components/ui/Select.tsx +14 -0
- package/frontend/components/ui/Skeleton.tsx +24 -0
- package/frontend/components/ui/Tabs.tsx +19 -0
- package/frontend/components/ui/Textarea.tsx +14 -0
- package/frontend/components/ui/Toast.tsx +78 -0
- package/frontend/components/waitlist/WaitlistDialog.tsx +128 -0
- package/frontend/lib/api.ts +1282 -0
- package/frontend/lib/billing.ts +6 -0
- package/frontend/lib/cookieConsent.ts +113 -0
- package/frontend/lib/format.ts +16 -0
- package/frontend/lib/plans.ts +62 -0
- package/frontend/lib/schemas.ts +108 -0
- package/frontend/lib/seo.ts +376 -0
- package/frontend/lib/setupGuides.ts +630 -0
- package/frontend/lib/storage.ts +30 -0
- package/frontend/next-env.d.ts +6 -0
- package/frontend/next.config.mjs +13 -0
- package/frontend/package-lock.json +14409 -0
- package/frontend/package.json +41 -0
- package/frontend/playwright.config.ts +20 -0
- package/frontend/postcss.config.mjs +8 -0
- package/frontend/public/.gitkeep +0 -0
- package/frontend/public/brand/availsync-logo-board.png +0 -0
- package/frontend/public/brand/availsync-logo-dark.png +0 -0
- package/frontend/public/brand/availsync-mark-dark.png +0 -0
- package/frontend/public/brand/availsync-wordmark-dark.png +0 -0
- package/frontend/public/marketing/hero-agent-coordination.png +0 -0
- package/frontend/tailwind.config.ts +53 -0
- package/frontend/tests/smoke.spec.ts +89 -0
- package/frontend/tsconfig.json +23 -0
- package/jest.config.js +7 -0
- package/nixpacks.toml +11 -0
- package/package.json +53 -0
- package/packages/mcp/LICENSE +21 -0
- package/packages/mcp/README.md +60 -0
- package/packages/mcp/jest.config.cjs +8 -0
- package/packages/mcp/package.json +54 -0
- package/packages/mcp/src/helpers.ts +38 -0
- package/packages/mcp/src/index.test.ts +60 -0
- package/packages/mcp/src/index.ts +387 -0
- package/packages/mcp/tsconfig.json +20 -0
- package/packages/mcp/tsconfig.test.json +12 -0
- package/packages/node/LICENSE +21 -0
- package/packages/node/README.md +120 -0
- package/packages/node/jest.config.cjs +8 -0
- package/packages/node/package.json +46 -0
- package/packages/node/src/index.test.ts +360 -0
- package/packages/node/src/index.ts +402 -0
- package/packages/node/tsconfig.json +20 -0
- package/packages/node/tsconfig.test.json +12 -0
- package/plan.md +923 -0
- package/skills/stripe-best-practices/SKILL.md +42 -0
- package/skills/stripe-best-practices/references/billing.md +36 -0
- package/skills/stripe-best-practices/references/connect.md +48 -0
- package/skills/stripe-best-practices/references/payments.md +79 -0
- package/skills/stripe-best-practices/references/security.md +109 -0
- package/skills/stripe-best-practices/references/treasury.md +16 -0
- package/skills/stripe-projects/SKILL.md +139 -0
- package/skills/upgrade-stripe/SKILL.md +185 -0
- package/skills-lock.json +20 -0
- package/src/core/availability.ts +178 -0
- package/src/core/conflict.ts +209 -0
- package/src/core/work.ts +490 -0
- package/src/db/client.ts +17 -0
- package/src/db/migrations/001_init.sql +88 -0
- package/src/db/migrations/002_stripe.sql +2 -0
- package/src/db/migrations/003_workspace_auth.sql +19 -0
- package/src/db/migrations/004_agent_mcp_status.sql +2 -0
- package/src/db/migrations/005_hold_event_actor.sql +4 -0
- package/src/db/migrations/006_agent_activity.sql +35 -0
- package/src/db/migrations/007_work_coordination.sql +60 -0
- package/src/db/migrations/008_work_claim_leases.sql +20 -0
- package/src/db/migrations/009_billing_subscription_state.sql +23 -0
- package/src/db/migrations/010_agent_api_key_prefix.sql +10 -0
- package/src/db/migrations/011_org_verified_and_work_event_retention.sql +11 -0
- package/src/db/migrations/012_agent_enforcement_mode.sql +12 -0
- package/src/db/migrations/013_support_tickets.sql +21 -0
- package/src/db/migrations/014_paid_plan_waitlist.sql +23 -0
- package/src/db/migrations/015_agent_last_seen.sql +2 -0
- package/src/db/migrations.ts +164 -0
- package/src/db/run-migrations.ts +13 -0
- package/src/index.ts +183 -0
- package/src/lib/activity.ts +137 -0
- package/src/lib/apiKeys.ts +32 -0
- package/src/lib/appInfo.ts +26 -0
- package/src/lib/billingConfig.ts +3 -0
- package/src/lib/env.ts +75 -0
- package/src/lib/logger.ts +8 -0
- package/src/lib/plans.ts +204 -0
- package/src/mcp/server.js +5 -0
- package/src/mcp/server.ts +350 -0
- package/src/middleware/auth.ts +342 -0
- package/src/middleware/requestId.ts +16 -0
- package/src/routes/account.ts +168 -0
- package/src/routes/activity.ts +126 -0
- package/src/routes/admin.ts +514 -0
- package/src/routes/audit.ts +68 -0
- package/src/routes/auth.ts +203 -0
- package/src/routes/availability.ts +325 -0
- package/src/routes/billing.ts +406 -0
- package/src/routes/conflicts.ts +131 -0
- package/src/routes/holds.ts +437 -0
- package/src/routes/mcp.ts +57 -0
- package/src/routes/metrics.ts +39 -0
- package/src/routes/onboarding.ts +273 -0
- package/src/routes/orgs.ts +981 -0
- package/src/routes/preferences.ts +132 -0
- package/src/routes/session.ts +16 -0
- package/src/routes/support.ts +77 -0
- package/src/routes/value.ts +186 -0
- package/src/routes/waitlist.ts +63 -0
- package/src/routes/work.ts +1578 -0
- package/src/server.ts +36 -0
- package/src/types/index.ts +109 -0
- package/tests/integration/activity.route.test.ts +103 -0
- package/tests/integration/admin.route.test.ts +143 -0
- package/tests/integration/agent-keys.route.test.ts +237 -0
- package/tests/integration/availability.route.test.ts +125 -0
- package/tests/integration/billing.route.test.ts +393 -0
- package/tests/integration/conflicts.route.test.ts +131 -0
- package/tests/integration/flows.test.ts +154 -0
- package/tests/integration/helpers.ts +134 -0
- package/tests/integration/holds.route.test.ts +185 -0
- package/tests/integration/metrics.route.test.ts +100 -0
- package/tests/integration/onboarding.verify.route.test.ts +163 -0
- package/tests/integration/preferences.route.test.ts +53 -0
- package/tests/integration/session.route.test.ts +97 -0
- package/tests/integration/system.route.test.ts +92 -0
- package/tests/integration/value.route.test.ts +235 -0
- package/tests/integration/work.route.test.ts +745 -0
- package/tests/setup.ts +4 -0
- package/tests/smoke.sh +62 -0
- package/tests/unit/auth.test.ts +114 -0
- package/tests/unit/availability.test.ts +149 -0
- package/tests/unit/conflict.test.ts +118 -0
- package/tests/unit/env.test.ts +69 -0
- package/tests/unit/migrations.test.ts +135 -0
- package/tests/unit/request-id.test.ts +37 -0
- package/tmp-mobile-agents.png +0 -0
- package/tmp-next-mobile.err.log +10 -0
- package/tmp-next-mobile.log +5 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { usePathname } from 'next/navigation';
|
|
4
|
+
import { Activity, CalendarClock, FileText, GitBranch, Home, Settings, Users } from 'lucide-react';
|
|
5
|
+
import { useEffect, useState } from 'react';
|
|
6
|
+
import { clearSession } from '@/lib/storage';
|
|
7
|
+
import { createSupportTicket, getBillingStatus, logoutWorkspace, restoreSession } from '@/lib/api';
|
|
8
|
+
import { billingUpgradesEnabled } from '@/lib/billing';
|
|
9
|
+
import { useToast } from '@/components/ui/Toast';
|
|
10
|
+
import { AvailsyncLogo } from '@/components/brand/AvailsyncLogo';
|
|
11
|
+
|
|
12
|
+
const links = [
|
|
13
|
+
{ href: '/app', label: 'Overview', icon: Home },
|
|
14
|
+
{ href: '/app/agents', label: 'Agents', icon: Users },
|
|
15
|
+
{ href: '/app/availability', label: 'Availability', icon: CalendarClock },
|
|
16
|
+
{ href: '/app/work', label: 'Work Claims', icon: GitBranch },
|
|
17
|
+
{ href: '/app/activity', label: 'API Activity', icon: Activity },
|
|
18
|
+
{ href: '/app/audit', label: 'Audit', icon: FileText },
|
|
19
|
+
{ href: '/app/settings', label: 'Settings', icon: Settings },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export function AppShell({ children }: { children: React.ReactNode }) {
|
|
23
|
+
const pathname = usePathname();
|
|
24
|
+
const { toast } = useToast();
|
|
25
|
+
const isOnboarding = pathname.startsWith('/app/onboarding');
|
|
26
|
+
const [planInfo, setPlanInfo] = useState<{
|
|
27
|
+
plan: string;
|
|
28
|
+
agents_used: number;
|
|
29
|
+
agent_limit: number;
|
|
30
|
+
billing_email: string | null;
|
|
31
|
+
billing_upgrades_enabled: boolean;
|
|
32
|
+
} | null>(null);
|
|
33
|
+
const [userEmail, setUserEmail] = useState('');
|
|
34
|
+
const [checkingSession, setCheckingSession] = useState(!isOnboarding);
|
|
35
|
+
const [supportOpen, setSupportOpen] = useState(false);
|
|
36
|
+
const [supportSending, setSupportSending] = useState(false);
|
|
37
|
+
const [supportForm, setSupportForm] = useState({
|
|
38
|
+
email: '',
|
|
39
|
+
subject: '',
|
|
40
|
+
category: 'setup' as 'billing' | 'setup' | 'sdk' | 'agent_error' | 'bug' | 'other',
|
|
41
|
+
message: '',
|
|
42
|
+
});
|
|
43
|
+
const upgradesEnabled = planInfo?.billing_upgrades_enabled ?? billingUpgradesEnabled();
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (isOnboarding) return;
|
|
47
|
+
|
|
48
|
+
restoreSession()
|
|
49
|
+
.then((session) => {
|
|
50
|
+
setUserEmail(session.user.email);
|
|
51
|
+
setSupportForm((current) => ({ ...current, email: current.email || session.user.email }));
|
|
52
|
+
return getBillingStatus();
|
|
53
|
+
})
|
|
54
|
+
.then((billing) => {
|
|
55
|
+
setPlanInfo(billing);
|
|
56
|
+
setSupportForm((current) => ({ ...current, email: current.email || billing.billing_email || userEmail }));
|
|
57
|
+
})
|
|
58
|
+
.catch(() => {
|
|
59
|
+
clearSession();
|
|
60
|
+
window.location.href = '/login';
|
|
61
|
+
})
|
|
62
|
+
.finally(() => setCheckingSession(false));
|
|
63
|
+
}, [isOnboarding]);
|
|
64
|
+
|
|
65
|
+
async function signOut() {
|
|
66
|
+
await logoutWorkspace().catch(() => {});
|
|
67
|
+
clearSession();
|
|
68
|
+
window.location.href = '/login';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function submitSupportTicket() {
|
|
72
|
+
if (!supportForm.email || !supportForm.subject.trim() || !supportForm.message.trim()) {
|
|
73
|
+
toast('Fill email, subject, and message', 'error');
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setSupportSending(true);
|
|
78
|
+
try {
|
|
79
|
+
await createSupportTicket({
|
|
80
|
+
...supportForm,
|
|
81
|
+
subject: supportForm.subject.trim(),
|
|
82
|
+
message: supportForm.message.trim(),
|
|
83
|
+
context: {
|
|
84
|
+
path: pathname,
|
|
85
|
+
app_url: window.location.origin,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
toast('Support ticket received', 'success');
|
|
89
|
+
setSupportOpen(false);
|
|
90
|
+
setSupportForm({
|
|
91
|
+
email: supportForm.email,
|
|
92
|
+
subject: '',
|
|
93
|
+
category: 'setup',
|
|
94
|
+
message: '',
|
|
95
|
+
});
|
|
96
|
+
} catch (err) {
|
|
97
|
+
toast((err as Error).message, 'error');
|
|
98
|
+
} finally {
|
|
99
|
+
setSupportSending(false);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isOnboarding) {
|
|
104
|
+
return <main className="min-h-screen bg-bg text-text-primary">{children}</main>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (checkingSession) {
|
|
108
|
+
return <main className="min-h-screen bg-bg" />;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div className="flex min-h-dvh flex-col bg-bg text-text-primary lg:h-screen lg:flex-row">
|
|
113
|
+
<header className="sticky top-0 z-30 border-b border-border bg-surface lg:hidden">
|
|
114
|
+
<div className="flex items-center justify-between gap-3 px-4 py-3">
|
|
115
|
+
<a className="text-text-primary" href="/app">
|
|
116
|
+
<AvailsyncLogo markClassName="h-7 w-7" wordmarkClassName="h-[20px] sm:h-[22px]" />
|
|
117
|
+
</a>
|
|
118
|
+
<button
|
|
119
|
+
onClick={signOut}
|
|
120
|
+
className="min-h-10 rounded border border-border px-3 text-body text-text-secondary hover:text-text-primary"
|
|
121
|
+
>
|
|
122
|
+
Sign out
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
<nav className="dark-scroll flex gap-1 overflow-x-auto px-2 pb-2">
|
|
126
|
+
{links.map(({ href, label, icon: Icon }) => {
|
|
127
|
+
const isActive =
|
|
128
|
+
href === '/app' ? pathname === '/app' : pathname.startsWith(href);
|
|
129
|
+
return (
|
|
130
|
+
<a
|
|
131
|
+
className={`flex min-h-10 shrink-0 items-center gap-2 rounded px-3 text-body transition-colors duration-150 ${
|
|
132
|
+
isActive
|
|
133
|
+
? 'bg-surface-raised text-text-primary'
|
|
134
|
+
: 'text-text-secondary hover:bg-surface-raised hover:text-text-primary'
|
|
135
|
+
}`}
|
|
136
|
+
href={href}
|
|
137
|
+
key={href}
|
|
138
|
+
>
|
|
139
|
+
<Icon className="h-4 w-4 flex-shrink-0" />
|
|
140
|
+
{label}
|
|
141
|
+
</a>
|
|
142
|
+
);
|
|
143
|
+
})}
|
|
144
|
+
</nav>
|
|
145
|
+
</header>
|
|
146
|
+
|
|
147
|
+
<aside className="hidden w-60 flex-shrink-0 flex-col overflow-y-auto border-r border-border bg-surface dark-scroll lg:flex">
|
|
148
|
+
<div className="px-4 py-4">
|
|
149
|
+
<a className="text-text-primary" href="/app">
|
|
150
|
+
<AvailsyncLogo markClassName="h-7 w-7" wordmarkClassName="h-[20px]" />
|
|
151
|
+
</a>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<div className="h-px bg-border mx-3" />
|
|
155
|
+
|
|
156
|
+
<nav className="flex-1 px-2 py-2 space-y-0.5">
|
|
157
|
+
{links.map(({ href, label, icon: Icon }) => {
|
|
158
|
+
const isActive =
|
|
159
|
+
href === '/app' ? pathname === '/app' : pathname.startsWith(href);
|
|
160
|
+
return (
|
|
161
|
+
<a
|
|
162
|
+
className={`flex items-center gap-2.5 rounded px-3 h-7.5 text-body transition-colors duration-150 ${
|
|
163
|
+
isActive
|
|
164
|
+
? 'bg-surface-raised text-text-primary'
|
|
165
|
+
: 'text-text-secondary hover:bg-surface-raised hover:text-text-primary'
|
|
166
|
+
}`}
|
|
167
|
+
href={href}
|
|
168
|
+
key={href}
|
|
169
|
+
>
|
|
170
|
+
<Icon className="h-4 w-4 flex-shrink-0" />
|
|
171
|
+
{label}
|
|
172
|
+
</a>
|
|
173
|
+
);
|
|
174
|
+
})}
|
|
175
|
+
</nav>
|
|
176
|
+
|
|
177
|
+
<div className="h-px bg-border mx-3" />
|
|
178
|
+
|
|
179
|
+
<div className="px-4 py-3 text-label text-text-tertiary space-y-1">
|
|
180
|
+
<div>
|
|
181
|
+
Plan:{' '}
|
|
182
|
+
<span className="text-text-secondary capitalize">
|
|
183
|
+
{planInfo?.plan || 'Free'}
|
|
184
|
+
</span>
|
|
185
|
+
</div>
|
|
186
|
+
<div>
|
|
187
|
+
{planInfo ? `${planInfo.agents_used}/${planInfo.agent_limit}` : '0/3'} agents
|
|
188
|
+
</div>
|
|
189
|
+
{planInfo?.plan === 'free' && (
|
|
190
|
+
<a
|
|
191
|
+
href="/app/settings?tab=billing"
|
|
192
|
+
className="text-accent hover:text-accent-hover text-label inline-block mt-1"
|
|
193
|
+
>
|
|
194
|
+
{upgradesEnabled ? 'Upgrade' : 'Paid waitlist'} →
|
|
195
|
+
</a>
|
|
196
|
+
)}
|
|
197
|
+
<button
|
|
198
|
+
onClick={signOut}
|
|
199
|
+
className="block text-left text-text-tertiary hover:text-text-secondary text-label mt-2"
|
|
200
|
+
>
|
|
201
|
+
Sign out
|
|
202
|
+
</button>
|
|
203
|
+
</div>
|
|
204
|
+
</aside>
|
|
205
|
+
|
|
206
|
+
<main className="min-w-0 flex-1 overflow-y-auto bg-bg dark-scroll">
|
|
207
|
+
{children}
|
|
208
|
+
</main>
|
|
209
|
+
{planInfo && planInfo.plan !== 'free' && (
|
|
210
|
+
<>
|
|
211
|
+
<button
|
|
212
|
+
onClick={() => setSupportOpen(true)}
|
|
213
|
+
className="fixed bottom-4 right-4 z-40 rounded border border-border bg-accent px-4 py-2 text-body font-medium text-white shadow-lg hover:bg-accent-hover"
|
|
214
|
+
>
|
|
215
|
+
Support
|
|
216
|
+
</button>
|
|
217
|
+
{supportOpen && (
|
|
218
|
+
<div className="fixed inset-0 z-50 flex items-end justify-end bg-black/45 p-4 sm:items-center">
|
|
219
|
+
<div className="w-full max-w-md rounded border border-border bg-surface p-5 shadow-xl">
|
|
220
|
+
<div className="mb-4 flex items-start justify-between gap-4">
|
|
221
|
+
<div>
|
|
222
|
+
<h2 className="text-heading font-semibold text-text-primary">Contact support</h2>
|
|
223
|
+
<p className="mt-1 text-body text-text-secondary">
|
|
224
|
+
Send a ticket. We will reply directly by email.
|
|
225
|
+
</p>
|
|
226
|
+
</div>
|
|
227
|
+
<button
|
|
228
|
+
onClick={() => setSupportOpen(false)}
|
|
229
|
+
className="text-text-tertiary hover:text-text-primary"
|
|
230
|
+
>
|
|
231
|
+
Close
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
<div className="space-y-3">
|
|
235
|
+
<div>
|
|
236
|
+
<label className="mb-1 block text-label uppercase text-text-tertiary">Email</label>
|
|
237
|
+
<input
|
|
238
|
+
value={supportForm.email}
|
|
239
|
+
onChange={(event) => setSupportForm({ ...supportForm, email: event.target.value })}
|
|
240
|
+
className="w-full rounded border border-border bg-bg px-3 py-2 text-body text-text-primary outline-none focus:border-border-focus"
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
<div>
|
|
244
|
+
<label className="mb-1 block text-label uppercase text-text-tertiary">Category</label>
|
|
245
|
+
<select
|
|
246
|
+
value={supportForm.category}
|
|
247
|
+
onChange={(event) => setSupportForm({ ...supportForm, category: event.target.value as typeof supportForm.category })}
|
|
248
|
+
className="w-full rounded border border-border bg-bg px-3 py-2 text-body text-text-primary outline-none focus:border-border-focus"
|
|
249
|
+
>
|
|
250
|
+
<option value="setup">Setup</option>
|
|
251
|
+
<option value="sdk">SDK</option>
|
|
252
|
+
<option value="agent_error">Agent error</option>
|
|
253
|
+
<option value="bug">Bug</option>
|
|
254
|
+
<option value="billing">Billing</option>
|
|
255
|
+
<option value="other">Other</option>
|
|
256
|
+
</select>
|
|
257
|
+
</div>
|
|
258
|
+
<div>
|
|
259
|
+
<label className="mb-1 block text-label uppercase text-text-tertiary">Subject</label>
|
|
260
|
+
<input
|
|
261
|
+
value={supportForm.subject}
|
|
262
|
+
onChange={(event) => setSupportForm({ ...supportForm, subject: event.target.value })}
|
|
263
|
+
className="w-full rounded border border-border bg-bg px-3 py-2 text-body text-text-primary outline-none focus:border-border-focus"
|
|
264
|
+
placeholder="What do you need help with?"
|
|
265
|
+
/>
|
|
266
|
+
</div>
|
|
267
|
+
<div>
|
|
268
|
+
<label className="mb-1 block text-label uppercase text-text-tertiary">Message</label>
|
|
269
|
+
<textarea
|
|
270
|
+
value={supportForm.message}
|
|
271
|
+
onChange={(event) => setSupportForm({ ...supportForm, message: event.target.value })}
|
|
272
|
+
className="min-h-32 w-full rounded border border-border bg-bg px-3 py-2 text-body text-text-primary outline-none focus:border-border-focus"
|
|
273
|
+
placeholder="Describe the issue, expected behavior, and what you already tried."
|
|
274
|
+
/>
|
|
275
|
+
</div>
|
|
276
|
+
<button
|
|
277
|
+
onClick={submitSupportTicket}
|
|
278
|
+
disabled={supportSending}
|
|
279
|
+
className="w-full rounded bg-accent px-4 py-2 text-body font-medium text-white hover:bg-accent-hover disabled:opacity-50"
|
|
280
|
+
>
|
|
281
|
+
{supportSending ? 'Sending...' : 'Create ticket'}
|
|
282
|
+
</button>
|
|
283
|
+
</div>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
)}
|
|
287
|
+
</>
|
|
288
|
+
)}
|
|
289
|
+
</div>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { checkAvailability, listAgents } from '@/lib/api';
|
|
5
|
+
import { fromLocalInputValue, formatDateTime, toLocalInputValue } from '@/lib/format';
|
|
6
|
+
import { loadSession, type StoredSession } from '@/lib/storage';
|
|
7
|
+
import { Button } from '@/components/ui/Button';
|
|
8
|
+
import { Input } from '@/components/ui/Input';
|
|
9
|
+
import type { Agent, Slot } from '@/lib/schemas';
|
|
10
|
+
|
|
11
|
+
export function AvailabilityChecker() {
|
|
12
|
+
const [session, setSession] = useState<StoredSession | null>(null);
|
|
13
|
+
const [agents, setAgents] = useState<Agent[]>([]);
|
|
14
|
+
const [agentId, setAgentId] = useState('');
|
|
15
|
+
const [from, setFrom] = useState(toLocalInputValue(new Date(Date.now() + 24 * 60 * 60 * 1000)));
|
|
16
|
+
const [to, setTo] = useState(toLocalInputValue(new Date(Date.now() + 25 * 60 * 60 * 1000)));
|
|
17
|
+
const [duration, setDuration] = useState(30);
|
|
18
|
+
const [slots, setSlots] = useState<Slot[]>([]);
|
|
19
|
+
const [error, setError] = useState('');
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const loaded = loadSession();
|
|
23
|
+
setSession(loaded);
|
|
24
|
+
if (loaded) {
|
|
25
|
+
listAgents(loaded.orgId)
|
|
26
|
+
.then((rows) => {
|
|
27
|
+
setAgents(rows);
|
|
28
|
+
if (rows[0]) setAgentId(rows[0].id);
|
|
29
|
+
})
|
|
30
|
+
.catch(() => {});
|
|
31
|
+
}
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
async function onSubmit(event: React.FormEvent) {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
if (!session) return;
|
|
37
|
+
setError('');
|
|
38
|
+
try {
|
|
39
|
+
const result = await checkAvailability({
|
|
40
|
+
agent_id: agentId,
|
|
41
|
+
from: fromLocalInputValue(from),
|
|
42
|
+
to: fromLocalInputValue(to),
|
|
43
|
+
duration_minutes: duration,
|
|
44
|
+
});
|
|
45
|
+
setSlots(result.slots);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
setError(err instanceof Error ? err.message : 'Could not check availability');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!session) {
|
|
52
|
+
return <p className="text-body text-text-secondary">Create an agent in onboarding before checking availability.</p>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<div className="grid gap-6">
|
|
57
|
+
<form className="grid gap-4 rounded border border-border bg-surface p-5" onSubmit={onSubmit}>
|
|
58
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="availability-from">
|
|
59
|
+
From
|
|
60
|
+
</label>
|
|
61
|
+
<Input
|
|
62
|
+
id="availability-from"
|
|
63
|
+
type="datetime-local"
|
|
64
|
+
value={from}
|
|
65
|
+
onChange={(event) => setFrom(event.target.value)}
|
|
66
|
+
/>
|
|
67
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="availability-agent">
|
|
68
|
+
Agent
|
|
69
|
+
</label>
|
|
70
|
+
<select
|
|
71
|
+
id="availability-agent"
|
|
72
|
+
value={agentId}
|
|
73
|
+
onChange={(event) => setAgentId(event.target.value)}
|
|
74
|
+
className="rounded border border-border bg-surface px-3 py-2 text-body text-text-primary"
|
|
75
|
+
>
|
|
76
|
+
{agents.map((agent) => (
|
|
77
|
+
<option key={agent.id} value={agent.id}>{agent.name}</option>
|
|
78
|
+
))}
|
|
79
|
+
</select>
|
|
80
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="availability-to">
|
|
81
|
+
To
|
|
82
|
+
</label>
|
|
83
|
+
<Input
|
|
84
|
+
id="availability-to"
|
|
85
|
+
type="datetime-local"
|
|
86
|
+
value={to}
|
|
87
|
+
onChange={(event) => setTo(event.target.value)}
|
|
88
|
+
/>
|
|
89
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="availability-duration">
|
|
90
|
+
Duration minutes
|
|
91
|
+
</label>
|
|
92
|
+
<Input
|
|
93
|
+
id="availability-duration"
|
|
94
|
+
max={480}
|
|
95
|
+
min={1}
|
|
96
|
+
type="number"
|
|
97
|
+
value={duration}
|
|
98
|
+
onChange={(event) => setDuration(Number(event.target.value))}
|
|
99
|
+
/>
|
|
100
|
+
{error && <p className="text-body text-error">{error}</p>}
|
|
101
|
+
<Button type="submit">Check availability</Button>
|
|
102
|
+
</form>
|
|
103
|
+
<div className="grid gap-3">
|
|
104
|
+
{slots.map((slot) => (
|
|
105
|
+
<div className="rounded border border-border bg-surface p-4" key={slot.start}>
|
|
106
|
+
<p className="font-semibold text-text-primary">
|
|
107
|
+
{formatDateTime(slot.start)} - {formatDateTime(slot.end)}
|
|
108
|
+
</p>
|
|
109
|
+
<p className="mt-1 text-body text-text-secondary">
|
|
110
|
+
Confidence {slot.confidence} - Competing holds {slot.competing_holds_count}
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Input } from '@/components/ui/Input';
|
|
4
|
+
|
|
5
|
+
type AvailabilityWindowFormProps = {
|
|
6
|
+
startAt: string;
|
|
7
|
+
endAt: string;
|
|
8
|
+
onStartAtChange: (value: string) => void;
|
|
9
|
+
onEndAtChange: (value: string) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function AvailabilityWindowForm({
|
|
13
|
+
startAt,
|
|
14
|
+
endAt,
|
|
15
|
+
onStartAtChange,
|
|
16
|
+
onEndAtChange,
|
|
17
|
+
}: AvailabilityWindowFormProps) {
|
|
18
|
+
return (
|
|
19
|
+
<>
|
|
20
|
+
<label className="text-sm font-semibold" htmlFor="availability-starts">
|
|
21
|
+
Availability starts
|
|
22
|
+
</label>
|
|
23
|
+
<Input
|
|
24
|
+
id="availability-starts"
|
|
25
|
+
type="datetime-local"
|
|
26
|
+
value={startAt}
|
|
27
|
+
onChange={(event) => onStartAtChange(event.target.value)}
|
|
28
|
+
/>
|
|
29
|
+
<label className="text-sm font-semibold" htmlFor="availability-ends">
|
|
30
|
+
Availability ends
|
|
31
|
+
</label>
|
|
32
|
+
<Input
|
|
33
|
+
id="availability-ends"
|
|
34
|
+
type="datetime-local"
|
|
35
|
+
value={endAt}
|
|
36
|
+
onChange={(event) => onEndAtChange(event.target.value)}
|
|
37
|
+
/>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { createHold, listAgents, listHolds, releaseHold } from '@/lib/api';
|
|
5
|
+
import { fromLocalInputValue, formatDateTime, toLocalInputValue } from '@/lib/format';
|
|
6
|
+
import { loadSession, type StoredSession } from '@/lib/storage';
|
|
7
|
+
import { Button } from '@/components/ui/Button';
|
|
8
|
+
import { Input } from '@/components/ui/Input';
|
|
9
|
+
import type { Agent, Hold } from '@/lib/schemas';
|
|
10
|
+
|
|
11
|
+
export function HoldForm() {
|
|
12
|
+
const [session, setSession] = useState<StoredSession | null>(null);
|
|
13
|
+
const [agentId, setAgentId] = useState('');
|
|
14
|
+
const [agents, setAgents] = useState<Agent[]>([]);
|
|
15
|
+
const [holds, setHolds] = useState<Hold[]>([]);
|
|
16
|
+
const [startAt, setStartAt] = useState(
|
|
17
|
+
toLocalInputValue(new Date(Date.now() + 24 * 60 * 60 * 1000)),
|
|
18
|
+
);
|
|
19
|
+
const [endAt, setEndAt] = useState(
|
|
20
|
+
toLocalInputValue(new Date(Date.now() + 24.5 * 60 * 60 * 1000)),
|
|
21
|
+
);
|
|
22
|
+
const [reason, setReason] = useState('Manual dashboard reservation');
|
|
23
|
+
const [error, setError] = useState('');
|
|
24
|
+
|
|
25
|
+
async function refresh(current: StoredSession) {
|
|
26
|
+
const agentRows = await listAgents(current.orgId);
|
|
27
|
+
setAgents(agentRows);
|
|
28
|
+
const nextAgentId = agentId || agentRows[0]?.id || '';
|
|
29
|
+
if (nextAgentId && !agentId) setAgentId(nextAgentId);
|
|
30
|
+
setHolds(await listHolds({ agent_id: nextAgentId || undefined }));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
const loaded = loadSession();
|
|
35
|
+
setSession(loaded);
|
|
36
|
+
if (loaded) {
|
|
37
|
+
refresh(loaded).catch(() => setError('Could not load holds'));
|
|
38
|
+
}
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
async function onSubmit(event: React.FormEvent) {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
if (!session) return;
|
|
44
|
+
setError('');
|
|
45
|
+
try {
|
|
46
|
+
await createHold({
|
|
47
|
+
agent_id: agentId,
|
|
48
|
+
start_at: fromLocalInputValue(startAt),
|
|
49
|
+
end_at: fromLocalInputValue(endAt),
|
|
50
|
+
reason,
|
|
51
|
+
});
|
|
52
|
+
await refresh(session);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
setError(err instanceof Error ? err.message : 'Could not create hold');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function onRelease(holdId: string) {
|
|
59
|
+
if (!session) return;
|
|
60
|
+
await releaseHold(holdId);
|
|
61
|
+
await refresh(session);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!session) {
|
|
65
|
+
return <p className="text-body text-text-secondary">Create an agent before managing holds.</p>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div className="grid gap-6">
|
|
70
|
+
<form className="grid gap-4 rounded border border-border bg-surface p-5" onSubmit={onSubmit}>
|
|
71
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="hold-start">
|
|
72
|
+
Start
|
|
73
|
+
</label>
|
|
74
|
+
<Input
|
|
75
|
+
id="hold-start"
|
|
76
|
+
type="datetime-local"
|
|
77
|
+
value={startAt}
|
|
78
|
+
onChange={(event) => setStartAt(event.target.value)}
|
|
79
|
+
/>
|
|
80
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="hold-agent">
|
|
81
|
+
Agent
|
|
82
|
+
</label>
|
|
83
|
+
<select
|
|
84
|
+
id="hold-agent"
|
|
85
|
+
value={agentId}
|
|
86
|
+
onChange={(event) => setAgentId(event.target.value)}
|
|
87
|
+
className="rounded border border-border bg-surface px-3 py-2 text-body text-text-primary"
|
|
88
|
+
>
|
|
89
|
+
{agents.map((agent) => (
|
|
90
|
+
<option key={agent.id} value={agent.id}>{agent.name}</option>
|
|
91
|
+
))}
|
|
92
|
+
</select>
|
|
93
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="hold-end">
|
|
94
|
+
End
|
|
95
|
+
</label>
|
|
96
|
+
<Input
|
|
97
|
+
id="hold-end"
|
|
98
|
+
type="datetime-local"
|
|
99
|
+
value={endAt}
|
|
100
|
+
onChange={(event) => setEndAt(event.target.value)}
|
|
101
|
+
/>
|
|
102
|
+
<label className="text-label uppercase text-text-tertiary" htmlFor="hold-reason">
|
|
103
|
+
Reason
|
|
104
|
+
</label>
|
|
105
|
+
<Input id="hold-reason" value={reason} onChange={(event) => setReason(event.target.value)} />
|
|
106
|
+
{error && <p className="text-body text-error">{error}</p>}
|
|
107
|
+
<Button type="submit">Create hold</Button>
|
|
108
|
+
</form>
|
|
109
|
+
<div className="grid gap-3">
|
|
110
|
+
{holds.map((hold) => (
|
|
111
|
+
<div
|
|
112
|
+
className="flex flex-col justify-between gap-3 rounded border border-border bg-surface p-4 md:flex-row md:items-center"
|
|
113
|
+
key={hold.id}
|
|
114
|
+
>
|
|
115
|
+
<div>
|
|
116
|
+
<p className="font-semibold text-text-primary">
|
|
117
|
+
{formatDateTime(hold.start_at)} - {formatDateTime(hold.end_at)}
|
|
118
|
+
</p>
|
|
119
|
+
<p className="mt-1 text-body text-text-secondary">
|
|
120
|
+
{hold.status} - {hold.reason || 'No reason'}
|
|
121
|
+
</p>
|
|
122
|
+
</div>
|
|
123
|
+
{hold.status === 'confirmed' && (
|
|
124
|
+
<Button onClick={() => onRelease(hold.id)} variant="secondary">
|
|
125
|
+
Release
|
|
126
|
+
</Button>
|
|
127
|
+
)}
|
|
128
|
+
</div>
|
|
129
|
+
))}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Card } from '@/components/ui/Card';
|
|
2
|
+
|
|
3
|
+
export function MetricCard({ label, value }: { label: string; value: string }) {
|
|
4
|
+
return (
|
|
5
|
+
<Card>
|
|
6
|
+
<p className="text-label uppercase text-text-tertiary">{label}</p>
|
|
7
|
+
<p className="mt-2 text-3xl font-semibold text-text-primary">{value}</p>
|
|
8
|
+
</Card>
|
|
9
|
+
);
|
|
10
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { loginWorkspace, restoreSession } from '@/lib/api';
|
|
6
|
+
import { clearSession, saveSession } from '@/lib/storage';
|
|
7
|
+
|
|
8
|
+
export function LoginForm() {
|
|
9
|
+
const router = useRouter();
|
|
10
|
+
const [email, setEmail] = useState('');
|
|
11
|
+
const [password, setPassword] = useState('');
|
|
12
|
+
const [error, setError] = useState('');
|
|
13
|
+
const [loading, setLoading] = useState(false);
|
|
14
|
+
|
|
15
|
+
async function onSubmit(event: React.FormEvent) {
|
|
16
|
+
event.preventDefault();
|
|
17
|
+
setError('');
|
|
18
|
+
setLoading(true);
|
|
19
|
+
try {
|
|
20
|
+
clearSession();
|
|
21
|
+
await loginWorkspace(email, password);
|
|
22
|
+
const restored = await restoreSession();
|
|
23
|
+
saveSession({
|
|
24
|
+
orgId: restored.org.id,
|
|
25
|
+
userId: restored.user.id,
|
|
26
|
+
email: restored.user.email,
|
|
27
|
+
});
|
|
28
|
+
router.push('/app');
|
|
29
|
+
} catch (err) {
|
|
30
|
+
setError(err instanceof Error ? err.message : 'Could not sign in');
|
|
31
|
+
} finally {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="min-h-screen bg-bg flex items-center justify-center p-6">
|
|
38
|
+
<div className="w-full max-w-sm">
|
|
39
|
+
<a
|
|
40
|
+
href="/"
|
|
41
|
+
className="mb-6 inline-block text-body text-text-tertiary transition-colors hover:text-text-secondary"
|
|
42
|
+
>
|
|
43
|
+
Back to homepage
|
|
44
|
+
</a>
|
|
45
|
+
<h1 className="text-title font-semibold text-text-primary mb-2">Workspace login</h1>
|
|
46
|
+
<p className="text-body text-text-secondary mb-6">Sign in with your dashboard email and password.</p>
|
|
47
|
+
<form onSubmit={onSubmit} className="space-y-4">
|
|
48
|
+
<div>
|
|
49
|
+
<label className="block text-label uppercase text-text-tertiary mb-1" htmlFor="email">
|
|
50
|
+
Email
|
|
51
|
+
</label>
|
|
52
|
+
<input
|
|
53
|
+
id="email"
|
|
54
|
+
required
|
|
55
|
+
type="email"
|
|
56
|
+
value={email}
|
|
57
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
58
|
+
className="w-full rounded bg-surface border border-border px-3 py-2.5 text-body text-text-primary outline-none focus:border-border-focus"
|
|
59
|
+
placeholder="you@company.com"
|
|
60
|
+
autoFocus
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
<div>
|
|
64
|
+
<label className="block text-label uppercase text-text-tertiary mb-1" htmlFor="password">
|
|
65
|
+
Password
|
|
66
|
+
</label>
|
|
67
|
+
<input
|
|
68
|
+
id="password"
|
|
69
|
+
required
|
|
70
|
+
type="password"
|
|
71
|
+
value={password}
|
|
72
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
73
|
+
className="w-full rounded bg-surface border border-border px-3 py-2.5 text-body text-text-primary outline-none focus:border-border-focus"
|
|
74
|
+
placeholder="Password"
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
{error && <p className="text-body text-error">{error}</p>}
|
|
78
|
+
<button
|
|
79
|
+
disabled={loading}
|
|
80
|
+
type="submit"
|
|
81
|
+
className="w-full rounded bg-accent text-white py-2.5 text-body font-medium hover:bg-accent-hover transition-colors disabled:opacity-40 active:scale-[0.98]"
|
|
82
|
+
>
|
|
83
|
+
{loading ? 'Signing in...' : 'Sign in'}
|
|
84
|
+
</button>
|
|
85
|
+
<a
|
|
86
|
+
href="/signup"
|
|
87
|
+
className="block text-center text-body text-text-secondary hover:text-text-primary transition-colors"
|
|
88
|
+
>
|
|
89
|
+
Create workspace instead
|
|
90
|
+
</a>
|
|
91
|
+
</form>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
}
|