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,840 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
4
|
+
import { loadSession, type StoredSession } from '@/lib/storage';
|
|
5
|
+
import {
|
|
6
|
+
listAgents,
|
|
7
|
+
checkAvailability,
|
|
8
|
+
createAvailabilityWindow,
|
|
9
|
+
updateAvailabilityWindow,
|
|
10
|
+
deleteAvailabilityWindow,
|
|
11
|
+
getAvailabilityWindows,
|
|
12
|
+
getPreferences,
|
|
13
|
+
updatePreferences,
|
|
14
|
+
createHold,
|
|
15
|
+
getActivityLog,
|
|
16
|
+
} from '@/lib/api';
|
|
17
|
+
import { Badge } from '@/components/ui/Badge';
|
|
18
|
+
import { useToast } from '@/components/ui/Toast';
|
|
19
|
+
import { SkeletonRow } from '@/components/ui/Skeleton';
|
|
20
|
+
import { EmptyState } from '@/components/ui/EmptyState';
|
|
21
|
+
import { CalendarClock, Check, Pencil, Plus, Trash2, X } from 'lucide-react';
|
|
22
|
+
import type { Agent } from '@/lib/schemas';
|
|
23
|
+
import { toLocalInputValue } from '@/lib/format';
|
|
24
|
+
|
|
25
|
+
type FocusBlock = { weekday: number; start_time: string; end_time: string };
|
|
26
|
+
|
|
27
|
+
const weekdayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
28
|
+
|
|
29
|
+
function toIsoLocalMinute(value: string) {
|
|
30
|
+
const date = new Date(value);
|
|
31
|
+
const offset = date.getTimezoneOffset();
|
|
32
|
+
return new Date(date.getTime() - offset * 60_000).toISOString().slice(0, 16).replace('T', ' ');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function localFieldValue(date: Date) {
|
|
36
|
+
return toLocalInputValue(date).replace('T', ' ');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function fieldValueToDate(value: string) {
|
|
40
|
+
return new Date(value.trim().replace(' ', 'T'));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function formatSlotRange(start: string, end: string) {
|
|
44
|
+
return `${toIsoLocalMinute(start).slice(11)} - ${toIsoLocalMinute(end).slice(11)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function activitySummary(event: { activity_type: string; metadata: Record<string, unknown> | null; status: string; status_code: number | null; error_code: string | null }) {
|
|
48
|
+
const metadata = event.metadata ?? {};
|
|
49
|
+
if (event.activity_type === 'availability_check') {
|
|
50
|
+
return `checked ${metadata.duration_minutes ?? ''}min slots`.replace(' ', ' ');
|
|
51
|
+
}
|
|
52
|
+
if (event.activity_type === 'hold_create') {
|
|
53
|
+
return event.status === 'success' ? 'claimed slot' : 'claim failed';
|
|
54
|
+
}
|
|
55
|
+
if (event.activity_type === 'conflict_preview') {
|
|
56
|
+
return 'previewed conflict';
|
|
57
|
+
}
|
|
58
|
+
if (event.activity_type.startsWith('work_') || event.activity_type.startsWith('automation_')) {
|
|
59
|
+
return event.activity_type.replaceAll('_', ' ');
|
|
60
|
+
}
|
|
61
|
+
return event.activity_type.replaceAll('_', ' ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default function AvailabilityPage() {
|
|
65
|
+
const { toast } = useToast();
|
|
66
|
+
const [session, setSession] = useState<StoredSession | null>(null);
|
|
67
|
+
const [agents, setAgents] = useState<Agent[]>([]);
|
|
68
|
+
const [selectedAgent, setSelectedAgent] = useState<string>('');
|
|
69
|
+
const [loading, setLoading] = useState(true);
|
|
70
|
+
|
|
71
|
+
// Availability windows
|
|
72
|
+
const [windows, setWindows] = useState<Array<{
|
|
73
|
+
id: string;
|
|
74
|
+
start_at: string;
|
|
75
|
+
end_at: string;
|
|
76
|
+
window_type: string;
|
|
77
|
+
}>>([]);
|
|
78
|
+
const [editingWindowId, setEditingWindowId] = useState<string | null>(null);
|
|
79
|
+
const [editWindowForm, setEditWindowForm] = useState({
|
|
80
|
+
start_at: '',
|
|
81
|
+
end_at: '',
|
|
82
|
+
window_type: 'available' as 'available' | 'focus' | 'blocked',
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Slot checker
|
|
86
|
+
const [from, setFrom] = useState(localFieldValue(new Date(Date.now() + 86400000)));
|
|
87
|
+
const [to, setTo] = useState(localFieldValue(new Date(Date.now() + 86400000 * 2)));
|
|
88
|
+
const [duration, setDuration] = useState(30);
|
|
89
|
+
const [slots, setSlots] = useState<Array<{
|
|
90
|
+
start: string;
|
|
91
|
+
end: string;
|
|
92
|
+
confidence: number;
|
|
93
|
+
competing_holds_count: number;
|
|
94
|
+
}>>([]);
|
|
95
|
+
const [checking, setChecking] = useState(false);
|
|
96
|
+
const [recentActivity, setRecentActivity] = useState<Array<{
|
|
97
|
+
id: string;
|
|
98
|
+
activity_type: string;
|
|
99
|
+
status: string;
|
|
100
|
+
status_code: number | null;
|
|
101
|
+
created_at: string;
|
|
102
|
+
metadata: Record<string, unknown> | null;
|
|
103
|
+
error_code: string | null;
|
|
104
|
+
}>>([]);
|
|
105
|
+
|
|
106
|
+
// New window form
|
|
107
|
+
const [showWindowForm, setShowWindowForm] = useState(false);
|
|
108
|
+
const [windowForm, setWindowForm] = useState({
|
|
109
|
+
start_at: localFieldValue(new Date(Date.now() + 86400000)),
|
|
110
|
+
end_at: localFieldValue(new Date(Date.now() + 86400000 + 28800000)),
|
|
111
|
+
window_type: 'available' as 'available' | 'focus' | 'blocked',
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Preferences
|
|
115
|
+
const [prefs, setPrefs] = useState<{
|
|
116
|
+
buffer_minutes_after: number;
|
|
117
|
+
buffer_minutes_before: number;
|
|
118
|
+
allow_back_to_back: boolean;
|
|
119
|
+
booking_window_days: number;
|
|
120
|
+
focus_blocks: FocusBlock[];
|
|
121
|
+
priority_over_agents: string[];
|
|
122
|
+
} | null>(null);
|
|
123
|
+
const [bufferDraft, setBufferDraft] = useState({
|
|
124
|
+
buffer_minutes_after: 0,
|
|
125
|
+
buffer_minutes_before: 0,
|
|
126
|
+
});
|
|
127
|
+
const preferenceSaveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
128
|
+
const selectedAgentName = agents.find((agent) => agent.id === selectedAgent)?.name || 'agent';
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const s = loadSession();
|
|
132
|
+
setSession(s);
|
|
133
|
+
if (s) {
|
|
134
|
+
listAgents(s.orgId)
|
|
135
|
+
.then((data) => {
|
|
136
|
+
setAgents(data);
|
|
137
|
+
if (data.length > 0) setSelectedAgent(data[0].id);
|
|
138
|
+
setLoading(false);
|
|
139
|
+
})
|
|
140
|
+
.catch(() => setLoading(false));
|
|
141
|
+
} else {
|
|
142
|
+
setLoading(false);
|
|
143
|
+
}
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
// Load windows and prefs when agent changes
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
if (!session || !selectedAgent) return;
|
|
149
|
+
getAvailabilityWindows({ agent_id: selectedAgent })
|
|
150
|
+
.then((w) => setWindows(w.map((win: any) => ({
|
|
151
|
+
id: win.id,
|
|
152
|
+
start_at: win.start_at,
|
|
153
|
+
end_at: win.end_at,
|
|
154
|
+
window_type: win.window_type,
|
|
155
|
+
}))))
|
|
156
|
+
.catch(() => {});
|
|
157
|
+
getPreferences(selectedAgent)
|
|
158
|
+
.then((r) => setPrefs(r.preferences))
|
|
159
|
+
.catch(() => {});
|
|
160
|
+
}, [session, selectedAgent]);
|
|
161
|
+
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!prefs) return;
|
|
164
|
+
setBufferDraft({
|
|
165
|
+
buffer_minutes_after: prefs.buffer_minutes_after,
|
|
166
|
+
buffer_minutes_before: prefs.buffer_minutes_before,
|
|
167
|
+
});
|
|
168
|
+
}, [prefs?.buffer_minutes_after, prefs?.buffer_minutes_before]);
|
|
169
|
+
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
return () => {
|
|
172
|
+
if (preferenceSaveTimer.current) {
|
|
173
|
+
clearTimeout(preferenceSaveTimer.current);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
const refreshWindows = async () => {
|
|
179
|
+
if (!selectedAgent) return;
|
|
180
|
+
const w = await getAvailabilityWindows({ agent_id: selectedAgent });
|
|
181
|
+
setWindows(w.map((win: any) => ({
|
|
182
|
+
id: win.id,
|
|
183
|
+
start_at: win.start_at,
|
|
184
|
+
end_at: win.end_at,
|
|
185
|
+
window_type: win.window_type,
|
|
186
|
+
})));
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
const refreshRecentActivity = useCallback(async () => {
|
|
190
|
+
if (!selectedAgent) return;
|
|
191
|
+
const activity = await getActivityLog({
|
|
192
|
+
agent_id: selectedAgent,
|
|
193
|
+
limit: 8,
|
|
194
|
+
}).catch(() => ({ events: [] }));
|
|
195
|
+
setRecentActivity(activity.events.map((event: any) => ({
|
|
196
|
+
id: event.id,
|
|
197
|
+
activity_type: event.activity_type,
|
|
198
|
+
status: event.status,
|
|
199
|
+
status_code: event.status_code,
|
|
200
|
+
created_at: event.created_at,
|
|
201
|
+
metadata: event.metadata,
|
|
202
|
+
error_code: event.error_code,
|
|
203
|
+
})));
|
|
204
|
+
}, [selectedAgent]);
|
|
205
|
+
|
|
206
|
+
useEffect(() => {
|
|
207
|
+
refreshRecentActivity();
|
|
208
|
+
}, [refreshRecentActivity]);
|
|
209
|
+
|
|
210
|
+
const handleCheckSlots = async () => {
|
|
211
|
+
if (!session || !selectedAgent) return;
|
|
212
|
+
setChecking(true);
|
|
213
|
+
try {
|
|
214
|
+
const result = await checkAvailability({
|
|
215
|
+
agent_id: selectedAgent,
|
|
216
|
+
from: fieldValueToDate(from).toISOString(),
|
|
217
|
+
to: fieldValueToDate(to).toISOString(),
|
|
218
|
+
duration_minutes: duration,
|
|
219
|
+
});
|
|
220
|
+
setSlots(result.slots.map((s) => ({
|
|
221
|
+
start: s.start,
|
|
222
|
+
end: s.end,
|
|
223
|
+
confidence: s.confidence,
|
|
224
|
+
competing_holds_count: s.competing_holds_count,
|
|
225
|
+
})));
|
|
226
|
+
await refreshRecentActivity();
|
|
227
|
+
} catch (err) {
|
|
228
|
+
toast((err as Error).message, 'error');
|
|
229
|
+
} finally {
|
|
230
|
+
setChecking(false);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const handleCreateWindow = async () => {
|
|
235
|
+
if (!session || !selectedAgent) return;
|
|
236
|
+
try {
|
|
237
|
+
await createAvailabilityWindow({
|
|
238
|
+
agent_id: selectedAgent,
|
|
239
|
+
start_at: fieldValueToDate(windowForm.start_at).toISOString(),
|
|
240
|
+
end_at: fieldValueToDate(windowForm.end_at).toISOString(),
|
|
241
|
+
window_type: windowForm.window_type,
|
|
242
|
+
});
|
|
243
|
+
toast('Window created', 'success');
|
|
244
|
+
setShowWindowForm(false);
|
|
245
|
+
await refreshWindows();
|
|
246
|
+
} catch (err) {
|
|
247
|
+
toast((err as Error).message, 'error');
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const startEditWindow = (window: { id: string; start_at: string; end_at: string; window_type: string }) => {
|
|
252
|
+
setEditingWindowId(window.id);
|
|
253
|
+
setEditWindowForm({
|
|
254
|
+
start_at: localFieldValue(new Date(window.start_at)),
|
|
255
|
+
end_at: localFieldValue(new Date(window.end_at)),
|
|
256
|
+
window_type: window.window_type as 'available' | 'focus' | 'blocked',
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const handleUpdateWindow = async () => {
|
|
261
|
+
if (!editingWindowId) return;
|
|
262
|
+
try {
|
|
263
|
+
await updateAvailabilityWindow({
|
|
264
|
+
window_id: editingWindowId,
|
|
265
|
+
start_at: fieldValueToDate(editWindowForm.start_at).toISOString(),
|
|
266
|
+
end_at: fieldValueToDate(editWindowForm.end_at).toISOString(),
|
|
267
|
+
window_type: editWindowForm.window_type,
|
|
268
|
+
});
|
|
269
|
+
toast('Window updated', 'success');
|
|
270
|
+
setEditingWindowId(null);
|
|
271
|
+
await refreshWindows();
|
|
272
|
+
} catch (err) {
|
|
273
|
+
toast((err as Error).message, 'error');
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const handleDeleteWindow = async (windowId: string) => {
|
|
278
|
+
if (!window.confirm('Delete this availability window?')) return;
|
|
279
|
+
try {
|
|
280
|
+
await deleteAvailabilityWindow(windowId);
|
|
281
|
+
toast('Window deleted', 'success');
|
|
282
|
+
if (editingWindowId === windowId) setEditingWindowId(null);
|
|
283
|
+
await refreshWindows();
|
|
284
|
+
} catch (err) {
|
|
285
|
+
toast((err as Error).message, 'error');
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const handleUpdatePrefs = async (data: Record<string, unknown>) => {
|
|
290
|
+
if (!session || !selectedAgent) return;
|
|
291
|
+
try {
|
|
292
|
+
await updatePreferences(selectedAgent, data);
|
|
293
|
+
const r = await getPreferences(selectedAgent);
|
|
294
|
+
setPrefs(r.preferences);
|
|
295
|
+
toast('Preferences updated', 'success');
|
|
296
|
+
} catch (err) {
|
|
297
|
+
toast((err as Error).message, 'error');
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const schedulePreferenceUpdate = (data: Record<string, unknown>) => {
|
|
302
|
+
if (preferenceSaveTimer.current) {
|
|
303
|
+
clearTimeout(preferenceSaveTimer.current);
|
|
304
|
+
}
|
|
305
|
+
preferenceSaveTimer.current = setTimeout(() => {
|
|
306
|
+
preferenceSaveTimer.current = null;
|
|
307
|
+
handleUpdatePrefs(data);
|
|
308
|
+
}, 500);
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const addFocusBlock = () => {
|
|
312
|
+
if (!prefs) return;
|
|
313
|
+
handleUpdatePrefs({
|
|
314
|
+
focus_blocks: [
|
|
315
|
+
...prefs.focus_blocks,
|
|
316
|
+
{ weekday: 1, start_time: '09:00', end_time: '10:00' },
|
|
317
|
+
],
|
|
318
|
+
});
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const updateFocusBlock = (index: number, block: FocusBlock) => {
|
|
322
|
+
if (!prefs) return;
|
|
323
|
+
handleUpdatePrefs({
|
|
324
|
+
focus_blocks: prefs.focus_blocks.map((item, itemIndex) => (itemIndex === index ? block : item)),
|
|
325
|
+
});
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const removeFocusBlock = (index: number) => {
|
|
329
|
+
if (!prefs) return;
|
|
330
|
+
handleUpdatePrefs({
|
|
331
|
+
focus_blocks: prefs.focus_blocks.filter((_, itemIndex) => itemIndex !== index),
|
|
332
|
+
});
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const togglePriorityOverAgent = (agentId: string) => {
|
|
336
|
+
if (!prefs) return;
|
|
337
|
+
const current = new Set(prefs.priority_over_agents);
|
|
338
|
+
if (current.has(agentId)) {
|
|
339
|
+
current.delete(agentId);
|
|
340
|
+
} else {
|
|
341
|
+
current.add(agentId);
|
|
342
|
+
}
|
|
343
|
+
handleUpdatePrefs({ priority_over_agents: Array.from(current) });
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const handleBookSlot = async (start: string, end: string) => {
|
|
347
|
+
if (!session || !selectedAgent) return;
|
|
348
|
+
try {
|
|
349
|
+
await createHold({
|
|
350
|
+
agent_id: selectedAgent,
|
|
351
|
+
start_at: start,
|
|
352
|
+
end_at: end,
|
|
353
|
+
reason: 'Booked from dashboard',
|
|
354
|
+
});
|
|
355
|
+
toast('Hold booked', 'success');
|
|
356
|
+
await handleCheckSlots();
|
|
357
|
+
} catch (err) {
|
|
358
|
+
toast((err as Error).message, 'error');
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
if (loading) {
|
|
363
|
+
return (
|
|
364
|
+
<div className="w-full max-w-none p-4 sm:p-6">
|
|
365
|
+
<h1 className="text-title font-semibold text-text-primary mb-6">Availability</h1>
|
|
366
|
+
<div className="space-y-2">{Array.from({ length: 3 }).map((_, i) => <SkeletonRow key={i} />)}</div>
|
|
367
|
+
</div>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<div className="w-full max-w-none p-4 sm:p-6">
|
|
373
|
+
<h1 className="text-title font-semibold text-text-primary mb-6">Availability</h1>
|
|
374
|
+
|
|
375
|
+
<div className="grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_360px]">
|
|
376
|
+
{/* Left: Windows + Slot checker */}
|
|
377
|
+
<div className="lg:col-span-2 space-y-6">
|
|
378
|
+
{/* Agent selector */}
|
|
379
|
+
<div className="flex items-center gap-3">
|
|
380
|
+
<label className="text-label uppercase text-text-tertiary">Agent</label>
|
|
381
|
+
<select
|
|
382
|
+
value={selectedAgent}
|
|
383
|
+
onChange={(e) => setSelectedAgent(e.target.value)}
|
|
384
|
+
className="rounded bg-surface border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
|
|
385
|
+
>
|
|
386
|
+
{agents.map((a) => (
|
|
387
|
+
<option key={a.id} value={a.id}>{a.name}</option>
|
|
388
|
+
))}
|
|
389
|
+
</select>
|
|
390
|
+
</div>
|
|
391
|
+
|
|
392
|
+
{/* Availability windows */}
|
|
393
|
+
<div>
|
|
394
|
+
<div className="flex items-center justify-between mb-3">
|
|
395
|
+
<h2 className="text-heading font-medium text-text-primary">Windows</h2>
|
|
396
|
+
<button
|
|
397
|
+
onClick={() => setShowWindowForm(!showWindowForm)}
|
|
398
|
+
className="flex items-center gap-1 text-body text-accent hover:text-accent-hover"
|
|
399
|
+
>
|
|
400
|
+
<Plus className="h-3.5 w-3.5" />
|
|
401
|
+
Add window
|
|
402
|
+
</button>
|
|
403
|
+
</div>
|
|
404
|
+
|
|
405
|
+
{showWindowForm && (
|
|
406
|
+
<div className="rounded border border-border bg-surface p-4 mb-3 space-y-3">
|
|
407
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
|
408
|
+
<div>
|
|
409
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">Start</label>
|
|
410
|
+
<input
|
|
411
|
+
type="text"
|
|
412
|
+
value={windowForm.start_at}
|
|
413
|
+
onChange={(e) => setWindowForm({ ...windowForm, start_at: e.target.value })}
|
|
414
|
+
placeholder="2026-05-13 09:00"
|
|
415
|
+
className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
418
|
+
<div>
|
|
419
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">End</label>
|
|
420
|
+
<input
|
|
421
|
+
type="text"
|
|
422
|
+
value={windowForm.end_at}
|
|
423
|
+
onChange={(e) => setWindowForm({ ...windowForm, end_at: e.target.value })}
|
|
424
|
+
placeholder="2026-05-13 17:00"
|
|
425
|
+
className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
|
|
426
|
+
/>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
<div>
|
|
430
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">Type</label>
|
|
431
|
+
<select
|
|
432
|
+
value={windowForm.window_type}
|
|
433
|
+
onChange={(e) => setWindowForm({ ...windowForm, window_type: e.target.value as any })}
|
|
434
|
+
className="rounded bg-bg border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
|
|
435
|
+
>
|
|
436
|
+
<option value="available">Available</option>
|
|
437
|
+
<option value="focus">Focus</option>
|
|
438
|
+
<option value="blocked">Blocked</option>
|
|
439
|
+
</select>
|
|
440
|
+
</div>
|
|
441
|
+
<button
|
|
442
|
+
onClick={handleCreateWindow}
|
|
443
|
+
className="rounded bg-accent text-white px-3 py-1.5 text-body font-medium hover:bg-accent-hover transition-colors"
|
|
444
|
+
>
|
|
445
|
+
Create window
|
|
446
|
+
</button>
|
|
447
|
+
</div>
|
|
448
|
+
)}
|
|
449
|
+
|
|
450
|
+
{windows.length === 0 ? (
|
|
451
|
+
<EmptyState
|
|
452
|
+
icon={CalendarClock}
|
|
453
|
+
title="No windows"
|
|
454
|
+
description="Create availability windows so agents know when they can reserve time."
|
|
455
|
+
/>
|
|
456
|
+
) : (
|
|
457
|
+
<div className="overflow-x-auto rounded border border-border dark-scroll">
|
|
458
|
+
<table className="w-full min-w-[720px]">
|
|
459
|
+
<thead>
|
|
460
|
+
<tr className="border-b border-border bg-surface text-label uppercase text-text-tertiary">
|
|
461
|
+
<th className="text-left px-4 py-2 font-medium">Start</th>
|
|
462
|
+
<th className="text-left px-4 py-2 font-medium">End</th>
|
|
463
|
+
<th className="text-left px-4 py-2 font-medium">Type</th>
|
|
464
|
+
<th className="text-right px-4 py-2 font-medium">Actions</th>
|
|
465
|
+
</tr>
|
|
466
|
+
</thead>
|
|
467
|
+
<tbody>
|
|
468
|
+
{windows.map((w) => (
|
|
469
|
+
<tr key={w.id} className="border-b border-border h-9 hover:bg-surface-raised transition-colors duration-150">
|
|
470
|
+
{editingWindowId === w.id ? (
|
|
471
|
+
<>
|
|
472
|
+
<td className="px-4 py-2">
|
|
473
|
+
<input
|
|
474
|
+
type="text"
|
|
475
|
+
value={editWindowForm.start_at}
|
|
476
|
+
onChange={(e) => setEditWindowForm({ ...editWindowForm, start_at: e.target.value })}
|
|
477
|
+
className="w-full rounded bg-bg border border-border px-2 py-1 font-mono text-body text-text-primary outline-none focus:border-border-focus"
|
|
478
|
+
/>
|
|
479
|
+
</td>
|
|
480
|
+
<td className="px-4 py-2">
|
|
481
|
+
<input
|
|
482
|
+
type="text"
|
|
483
|
+
value={editWindowForm.end_at}
|
|
484
|
+
onChange={(e) => setEditWindowForm({ ...editWindowForm, end_at: e.target.value })}
|
|
485
|
+
className="w-full rounded bg-bg border border-border px-2 py-1 font-mono text-body text-text-primary outline-none focus:border-border-focus"
|
|
486
|
+
/>
|
|
487
|
+
</td>
|
|
488
|
+
<td className="px-4 py-2">
|
|
489
|
+
<select
|
|
490
|
+
value={editWindowForm.window_type}
|
|
491
|
+
onChange={(e) => setEditWindowForm({ ...editWindowForm, window_type: e.target.value as 'available' | 'focus' | 'blocked' })}
|
|
492
|
+
className="rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus"
|
|
493
|
+
>
|
|
494
|
+
<option value="available">Available</option>
|
|
495
|
+
<option value="focus">Focus</option>
|
|
496
|
+
<option value="blocked">Blocked</option>
|
|
497
|
+
</select>
|
|
498
|
+
</td>
|
|
499
|
+
<td className="px-4 py-2">
|
|
500
|
+
<div className="flex items-center justify-end gap-2">
|
|
501
|
+
<button
|
|
502
|
+
onClick={handleUpdateWindow}
|
|
503
|
+
className="text-success hover:text-success/80"
|
|
504
|
+
title="Save changes"
|
|
505
|
+
>
|
|
506
|
+
<Check className="h-3.5 w-3.5" />
|
|
507
|
+
</button>
|
|
508
|
+
<button
|
|
509
|
+
onClick={() => setEditingWindowId(null)}
|
|
510
|
+
className="text-text-tertiary hover:text-text-secondary"
|
|
511
|
+
title="Cancel"
|
|
512
|
+
>
|
|
513
|
+
<X className="h-3.5 w-3.5" />
|
|
514
|
+
</button>
|
|
515
|
+
</div>
|
|
516
|
+
</td>
|
|
517
|
+
</>
|
|
518
|
+
) : (
|
|
519
|
+
<>
|
|
520
|
+
<td className="px-4 font-mono text-body text-text-secondary">
|
|
521
|
+
{toIsoLocalMinute(w.start_at)}
|
|
522
|
+
</td>
|
|
523
|
+
<td className="px-4 font-mono text-body text-text-secondary">
|
|
524
|
+
{toIsoLocalMinute(w.end_at)}
|
|
525
|
+
</td>
|
|
526
|
+
<td className="px-4">
|
|
527
|
+
<Badge variant={w.window_type === 'available' ? 'success' : w.window_type === 'focus' ? 'accent' : 'error'}>
|
|
528
|
+
{w.window_type}
|
|
529
|
+
</Badge>
|
|
530
|
+
</td>
|
|
531
|
+
<td className="px-4">
|
|
532
|
+
<div className="flex items-center justify-end gap-2">
|
|
533
|
+
<button
|
|
534
|
+
onClick={() => startEditWindow(w)}
|
|
535
|
+
className="text-text-tertiary hover:text-text-secondary"
|
|
536
|
+
title="Edit window"
|
|
537
|
+
>
|
|
538
|
+
<Pencil className="h-3.5 w-3.5" />
|
|
539
|
+
</button>
|
|
540
|
+
<button
|
|
541
|
+
onClick={() => handleDeleteWindow(w.id)}
|
|
542
|
+
className="text-error hover:text-error/80"
|
|
543
|
+
title="Delete window"
|
|
544
|
+
>
|
|
545
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
546
|
+
</button>
|
|
547
|
+
</div>
|
|
548
|
+
</td>
|
|
549
|
+
</>
|
|
550
|
+
)}
|
|
551
|
+
</tr>
|
|
552
|
+
))}
|
|
553
|
+
</tbody>
|
|
554
|
+
</table>
|
|
555
|
+
</div>
|
|
556
|
+
)}
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
{/* Slot checker */}
|
|
560
|
+
<div>
|
|
561
|
+
<h2 className="text-heading font-medium text-text-primary mb-3">Check slots</h2>
|
|
562
|
+
<div className="rounded border border-border bg-surface p-4 space-y-3">
|
|
563
|
+
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
|
|
564
|
+
<div>
|
|
565
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">From</label>
|
|
566
|
+
<input
|
|
567
|
+
type="text"
|
|
568
|
+
value={from}
|
|
569
|
+
onChange={(e) => setFrom(e.target.value)}
|
|
570
|
+
placeholder="2026-05-13 09:00"
|
|
571
|
+
className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
|
|
572
|
+
/>
|
|
573
|
+
</div>
|
|
574
|
+
<div>
|
|
575
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">To</label>
|
|
576
|
+
<input
|
|
577
|
+
type="text"
|
|
578
|
+
value={to}
|
|
579
|
+
onChange={(e) => setTo(e.target.value)}
|
|
580
|
+
placeholder="2026-05-13 17:00"
|
|
581
|
+
className="w-full rounded bg-bg border border-border px-3 py-1.5 font-mono text-body text-text-primary outline-none focus:border-border-focus"
|
|
582
|
+
/>
|
|
583
|
+
</div>
|
|
584
|
+
<div>
|
|
585
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">Duration (min)</label>
|
|
586
|
+
<input
|
|
587
|
+
type="number"
|
|
588
|
+
value={duration}
|
|
589
|
+
onChange={(e) => setDuration(Number(e.target.value))}
|
|
590
|
+
min={1}
|
|
591
|
+
max={480}
|
|
592
|
+
className="w-full rounded bg-bg border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
|
|
593
|
+
/>
|
|
594
|
+
</div>
|
|
595
|
+
</div>
|
|
596
|
+
<button
|
|
597
|
+
onClick={handleCheckSlots}
|
|
598
|
+
disabled={checking}
|
|
599
|
+
className="rounded bg-accent text-white px-3 py-1.5 text-body font-medium hover:bg-accent-hover transition-colors disabled:opacity-40"
|
|
600
|
+
>
|
|
601
|
+
{checking ? 'Checking...' : 'Check availability'}
|
|
602
|
+
</button>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
{slots.length > 0 && (
|
|
606
|
+
<div className="mt-3 overflow-x-auto rounded border border-border bg-surface dark-scroll">
|
|
607
|
+
<div className="border-b border-border px-4 py-3">
|
|
608
|
+
<h3 className="text-body font-medium text-text-primary">Available slots</h3>
|
|
609
|
+
</div>
|
|
610
|
+
<table className="w-full min-w-[700px]">
|
|
611
|
+
<thead>
|
|
612
|
+
<tr className="border-b border-border bg-surface text-label uppercase text-text-tertiary">
|
|
613
|
+
<th className="text-left px-4 py-2 font-medium">Slot</th>
|
|
614
|
+
<th className="text-left px-4 py-2 font-medium">Confidence</th>
|
|
615
|
+
<th className="text-left px-4 py-2 font-medium">Competition</th>
|
|
616
|
+
<th className="text-left px-4 py-2 font-medium"></th>
|
|
617
|
+
</tr>
|
|
618
|
+
</thead>
|
|
619
|
+
<tbody>
|
|
620
|
+
{slots.map((slot, i) => (
|
|
621
|
+
<tr key={i} className="border-b border-border h-9 hover:bg-surface-raised transition-colors duration-150">
|
|
622
|
+
<td className="px-4 font-mono text-body text-text-secondary">
|
|
623
|
+
{formatSlotRange(slot.start, slot.end)}
|
|
624
|
+
</td>
|
|
625
|
+
<td className="px-4">
|
|
626
|
+
<Badge variant={slot.confidence > 0.7 ? 'success' : slot.confidence > 0.4 ? 'warning' : 'error'}>
|
|
627
|
+
{Math.round(slot.confidence * 100)}%
|
|
628
|
+
</Badge>
|
|
629
|
+
</td>
|
|
630
|
+
<td className="px-4 font-mono text-body text-text-tertiary">
|
|
631
|
+
{slot.competing_holds_count} competing holds
|
|
632
|
+
</td>
|
|
633
|
+
<td className="px-4">
|
|
634
|
+
<button
|
|
635
|
+
onClick={() => handleBookSlot(slot.start, slot.end)}
|
|
636
|
+
className="text-body text-accent hover:text-accent-hover"
|
|
637
|
+
>
|
|
638
|
+
Book
|
|
639
|
+
</button>
|
|
640
|
+
</td>
|
|
641
|
+
</tr>
|
|
642
|
+
))}
|
|
643
|
+
</tbody>
|
|
644
|
+
</table>
|
|
645
|
+
</div>
|
|
646
|
+
)}
|
|
647
|
+
</div>
|
|
648
|
+
</div>
|
|
649
|
+
|
|
650
|
+
{/* Right: Preferences panel */}
|
|
651
|
+
<div className="space-y-6">
|
|
652
|
+
<div>
|
|
653
|
+
<h2 className="text-heading font-medium text-text-primary mb-3">Preferences</h2>
|
|
654
|
+
{prefs ? (
|
|
655
|
+
<div className="rounded border border-border bg-surface p-4 space-y-4">
|
|
656
|
+
<div>
|
|
657
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">
|
|
658
|
+
Buffer after (min)
|
|
659
|
+
</label>
|
|
660
|
+
<input
|
|
661
|
+
type="range"
|
|
662
|
+
min={0}
|
|
663
|
+
max={60}
|
|
664
|
+
step={5}
|
|
665
|
+
value={bufferDraft.buffer_minutes_after}
|
|
666
|
+
onChange={(e) => {
|
|
667
|
+
const value = Number(e.target.value);
|
|
668
|
+
setBufferDraft((current) => ({ ...current, buffer_minutes_after: value }));
|
|
669
|
+
schedulePreferenceUpdate({ buffer_minutes_after: value });
|
|
670
|
+
}}
|
|
671
|
+
className="w-full accent-accent"
|
|
672
|
+
/>
|
|
673
|
+
<span className="font-mono text-body text-text-secondary">{bufferDraft.buffer_minutes_after} min</span>
|
|
674
|
+
</div>
|
|
675
|
+
<div>
|
|
676
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">
|
|
677
|
+
Buffer before (min)
|
|
678
|
+
</label>
|
|
679
|
+
<input
|
|
680
|
+
type="range"
|
|
681
|
+
min={0}
|
|
682
|
+
max={60}
|
|
683
|
+
step={5}
|
|
684
|
+
value={bufferDraft.buffer_minutes_before}
|
|
685
|
+
onChange={(e) => {
|
|
686
|
+
const value = Number(e.target.value);
|
|
687
|
+
setBufferDraft((current) => ({ ...current, buffer_minutes_before: value }));
|
|
688
|
+
schedulePreferenceUpdate({ buffer_minutes_before: value });
|
|
689
|
+
}}
|
|
690
|
+
className="w-full accent-accent"
|
|
691
|
+
/>
|
|
692
|
+
<span className="font-mono text-body text-text-secondary">{bufferDraft.buffer_minutes_before} min</span>
|
|
693
|
+
</div>
|
|
694
|
+
<div className="flex items-center justify-between">
|
|
695
|
+
<label className="text-body text-text-secondary">Back-to-back</label>
|
|
696
|
+
<button
|
|
697
|
+
onClick={() => handleUpdatePrefs({ allow_back_to_back: !prefs.allow_back_to_back })}
|
|
698
|
+
className={`w-10 h-5 rounded-full transition-colors ${
|
|
699
|
+
prefs.allow_back_to_back ? 'bg-accent' : 'bg-border'
|
|
700
|
+
}`}
|
|
701
|
+
>
|
|
702
|
+
<div
|
|
703
|
+
className={`h-4 w-4 rounded-full bg-white transition-transform ${
|
|
704
|
+
prefs.allow_back_to_back ? 'translate-x-5' : 'translate-x-0.5'
|
|
705
|
+
}`}
|
|
706
|
+
/>
|
|
707
|
+
</button>
|
|
708
|
+
</div>
|
|
709
|
+
<div>
|
|
710
|
+
<label className="block text-label uppercase text-text-tertiary mb-1">
|
|
711
|
+
Booking window
|
|
712
|
+
</label>
|
|
713
|
+
<input
|
|
714
|
+
type="number"
|
|
715
|
+
min={1}
|
|
716
|
+
max={365}
|
|
717
|
+
value={prefs.booking_window_days}
|
|
718
|
+
onChange={(e) => handleUpdatePrefs({ booking_window_days: Number(e.target.value) })}
|
|
719
|
+
className="w-full rounded bg-bg border border-border px-3 py-1.5 text-body text-text-primary outline-none focus:border-border-focus"
|
|
720
|
+
/>
|
|
721
|
+
<span className="mt-1 block font-mono text-body text-text-secondary">
|
|
722
|
+
{prefs.booking_window_days} days
|
|
723
|
+
</span>
|
|
724
|
+
</div>
|
|
725
|
+
<div className="border-t border-border pt-4">
|
|
726
|
+
<div className="mb-2 flex items-center justify-between">
|
|
727
|
+
<label className="text-label uppercase text-text-tertiary">Focus blocks</label>
|
|
728
|
+
<button onClick={addFocusBlock} className="text-body text-accent hover:text-accent-hover">
|
|
729
|
+
Add
|
|
730
|
+
</button>
|
|
731
|
+
</div>
|
|
732
|
+
<div className="space-y-2">
|
|
733
|
+
{prefs.focus_blocks.length === 0 ? (
|
|
734
|
+
<p className="text-body text-text-tertiary">No focus blocks.</p>
|
|
735
|
+
) : (
|
|
736
|
+
prefs.focus_blocks.map((block, index) => (
|
|
737
|
+
<div className="grid grid-cols-[1fr_1fr_auto] gap-2 sm:grid-cols-[72px_1fr_1fr_auto]" key={`${block.weekday}-${block.start_time}-${index}`}>
|
|
738
|
+
<select
|
|
739
|
+
value={block.weekday}
|
|
740
|
+
onChange={(e) => updateFocusBlock(index, { ...block, weekday: Number(e.target.value) })}
|
|
741
|
+
className="col-span-3 rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus sm:col-span-1"
|
|
742
|
+
>
|
|
743
|
+
{weekdayLabels.map((label, value) => (
|
|
744
|
+
<option key={label} value={value}>{label}</option>
|
|
745
|
+
))}
|
|
746
|
+
</select>
|
|
747
|
+
<input
|
|
748
|
+
type="time"
|
|
749
|
+
value={block.start_time}
|
|
750
|
+
onChange={(e) => updateFocusBlock(index, { ...block, start_time: e.target.value })}
|
|
751
|
+
className="rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus"
|
|
752
|
+
/>
|
|
753
|
+
<input
|
|
754
|
+
type="time"
|
|
755
|
+
value={block.end_time}
|
|
756
|
+
onChange={(e) => updateFocusBlock(index, { ...block, end_time: e.target.value })}
|
|
757
|
+
className="rounded bg-bg border border-border px-2 py-1 text-body text-text-primary outline-none focus:border-border-focus"
|
|
758
|
+
/>
|
|
759
|
+
<button
|
|
760
|
+
onClick={() => removeFocusBlock(index)}
|
|
761
|
+
className="text-error hover:text-error/80"
|
|
762
|
+
title="Remove focus block"
|
|
763
|
+
>
|
|
764
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
765
|
+
</button>
|
|
766
|
+
</div>
|
|
767
|
+
))
|
|
768
|
+
)}
|
|
769
|
+
</div>
|
|
770
|
+
</div>
|
|
771
|
+
<div className="border-t border-border pt-4">
|
|
772
|
+
<label className="block text-label uppercase text-text-tertiary mb-2">
|
|
773
|
+
Priority over agents
|
|
774
|
+
</label>
|
|
775
|
+
<div className="space-y-2">
|
|
776
|
+
{agents.filter((agent) => agent.id !== selectedAgent).length === 0 ? (
|
|
777
|
+
<p className="text-body text-text-tertiary">Create another agent to set overrides.</p>
|
|
778
|
+
) : (
|
|
779
|
+
agents.filter((agent) => agent.id !== selectedAgent).map((agent) => (
|
|
780
|
+
<label key={agent.id} className="flex items-center justify-between rounded border border-border bg-bg px-3 py-2">
|
|
781
|
+
<span className="text-body text-text-secondary">{agent.name}</span>
|
|
782
|
+
<input
|
|
783
|
+
type="checkbox"
|
|
784
|
+
checked={prefs.priority_over_agents.includes(agent.id)}
|
|
785
|
+
onChange={() => togglePriorityOverAgent(agent.id)}
|
|
786
|
+
className="h-4 w-4 accent-accent"
|
|
787
|
+
/>
|
|
788
|
+
</label>
|
|
789
|
+
))
|
|
790
|
+
)}
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
) : (
|
|
795
|
+
<p className="text-body text-text-tertiary">Select an agent to view preferences.</p>
|
|
796
|
+
)}
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
</div>
|
|
800
|
+
|
|
801
|
+
<section className="mt-6">
|
|
802
|
+
<h2 className="text-heading font-medium text-text-primary mb-3">Recent activity - {selectedAgentName}</h2>
|
|
803
|
+
{recentActivity.length === 0 ? (
|
|
804
|
+
<div className="rounded border border-border bg-surface p-4 text-body text-text-tertiary">
|
|
805
|
+
No slot checks or claims observed for this agent yet.
|
|
806
|
+
</div>
|
|
807
|
+
) : (
|
|
808
|
+
<div className="overflow-x-auto rounded border border-border dark-scroll">
|
|
809
|
+
<table className="w-full min-w-[760px]">
|
|
810
|
+
<thead>
|
|
811
|
+
<tr className="border-b border-border bg-surface text-label uppercase text-text-tertiary">
|
|
812
|
+
<th className="text-left px-4 py-2 font-medium">Time</th>
|
|
813
|
+
<th className="text-left px-4 py-2 font-medium">Action</th>
|
|
814
|
+
<th className="text-left px-4 py-2 font-medium">Outcome</th>
|
|
815
|
+
</tr>
|
|
816
|
+
</thead>
|
|
817
|
+
<tbody>
|
|
818
|
+
{recentActivity.map((event) => (
|
|
819
|
+
<tr key={event.id} className="border-b border-border h-9 hover:bg-surface-raised">
|
|
820
|
+
<td className="px-4 font-mono text-body text-text-tertiary">
|
|
821
|
+
{toIsoLocalMinute(event.created_at)}
|
|
822
|
+
</td>
|
|
823
|
+
<td className="px-4 text-body text-text-secondary">
|
|
824
|
+
{activitySummary(event)}
|
|
825
|
+
</td>
|
|
826
|
+
<td className="px-4">
|
|
827
|
+
<Badge variant={event.status === 'success' ? 'success' : 'error'}>
|
|
828
|
+
{event.error_code || event.status_code || event.status}
|
|
829
|
+
</Badge>
|
|
830
|
+
</td>
|
|
831
|
+
</tr>
|
|
832
|
+
))}
|
|
833
|
+
</tbody>
|
|
834
|
+
</table>
|
|
835
|
+
</div>
|
|
836
|
+
)}
|
|
837
|
+
</section>
|
|
838
|
+
</div>
|
|
839
|
+
);
|
|
840
|
+
}
|