@wopr-network/platform-ui-core 1.0.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/.env.paperclip +18 -0
- package/.env.wopr +18 -0
- package/README.md +36 -0
- package/biome.json +52 -0
- package/next.config.ts +45 -0
- package/package.json +84 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/window.svg +1 -0
- package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +741 -0
- package/src/__tests__/account-page-redirect.test.tsx +73 -0
- package/src/__tests__/account-switcher.test.tsx +85 -0
- package/src/__tests__/activity-page.test.tsx +176 -0
- package/src/__tests__/add-payment-method-dialog.test.tsx +160 -0
- package/src/__tests__/admin-api.test.ts +244 -0
- package/src/__tests__/admin-gpu-api.test.ts +188 -0
- package/src/__tests__/admin-guard.test.tsx +79 -0
- package/src/__tests__/admin-marketplace-api.test.ts +179 -0
- package/src/__tests__/admin-middleware.test.ts +157 -0
- package/src/__tests__/admin-tenant-table.test.tsx +95 -0
- package/src/__tests__/affiliate-dashboard.test.tsx +178 -0
- package/src/__tests__/api-401-redirect.test.ts +78 -0
- package/src/__tests__/api-client.test.ts +316 -0
- package/src/__tests__/api-config.test.ts +89 -0
- package/src/__tests__/api-control-instance.test.ts +69 -0
- package/src/__tests__/api-fleet-resources.test.ts +52 -0
- package/src/__tests__/api-fleet-trpc.test.ts +252 -0
- package/src/__tests__/api-get-instance-config.test.ts +41 -0
- package/src/__tests__/api-null-guards.test.ts +244 -0
- package/src/__tests__/api-rename-instance.test.ts +60 -0
- package/src/__tests__/api-update-instance-config.test.ts +60 -0
- package/src/__tests__/audit-log-table-pagination.test.tsx +136 -0
- package/src/__tests__/auth-client.test.ts +87 -0
- package/src/__tests__/auth-password-reset.test.tsx +435 -0
- package/src/__tests__/auth-redirect.test.tsx +60 -0
- package/src/__tests__/auth.test.tsx +269 -0
- package/src/__tests__/auto-topup-card.test.tsx +257 -0
- package/src/__tests__/backups-tab.test.tsx +221 -0
- package/src/__tests__/billing-byok-callout.test.tsx +76 -0
- package/src/__tests__/billing-layout-nav-hidden.test.tsx +47 -0
- package/src/__tests__/billing-payment-org-invoices.test.tsx +123 -0
- package/src/__tests__/billing.test.tsx +509 -0
- package/src/__tests__/bot-settings/resources-tab.test.tsx +119 -0
- package/src/__tests__/bot-settings/storage-tab.test.tsx +80 -0
- package/src/__tests__/bot-settings/vps-info-panel.test.tsx +108 -0
- package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +52 -0
- package/src/__tests__/bot-settings-data-control.test.ts +49 -0
- package/src/__tests__/bot-settings-restart.test.tsx +149 -0
- package/src/__tests__/bot-settings.test.tsx +678 -0
- package/src/__tests__/brand.test.ts +335 -0
- package/src/__tests__/buy-credits-panel.test.tsx +249 -0
- package/src/__tests__/buy-crypto-credits-panel.test.tsx +178 -0
- package/src/__tests__/capability-conflicts.test.ts +88 -0
- package/src/__tests__/capability-resolver.test.tsx +173 -0
- package/src/__tests__/changeset-detail.test.tsx +156 -0
- package/src/__tests__/channel-setup-logger.test.ts +22 -0
- package/src/__tests__/channel-setup-toast.test.tsx +60 -0
- package/src/__tests__/channel-wizard.test.tsx +505 -0
- package/src/__tests__/chat/ambient-dot.test.tsx +35 -0
- package/src/__tests__/chat/chat-input.test.tsx +78 -0
- package/src/__tests__/chat/chat-message.test.tsx +45 -0
- package/src/__tests__/chat/chat-panel.test.tsx +111 -0
- package/src/__tests__/chat/chat-widget.test.tsx +82 -0
- package/src/__tests__/chat-store.test.ts +87 -0
- package/src/__tests__/command-center.test.tsx +246 -0
- package/src/__tests__/compliance-retention-edit.test.tsx +134 -0
- package/src/__tests__/coupon-input.test.tsx +119 -0
- package/src/__tests__/create-instance.test.tsx +96 -0
- package/src/__tests__/create-org-wizard.test.tsx +200 -0
- package/src/__tests__/credit-balance.test.tsx +103 -0
- package/src/__tests__/credits.test.tsx +376 -0
- package/src/__tests__/csrf-middleware.test.ts +198 -0
- package/src/__tests__/degraded-state-banner.test.tsx +130 -0
- package/src/__tests__/dividend-calculator.test.tsx +20 -0
- package/src/__tests__/dividend-stats.test.tsx +64 -0
- package/src/__tests__/dividend.test.tsx +169 -0
- package/src/__tests__/dockerfile.test.ts +110 -0
- package/src/__tests__/email-verification-banner.test.tsx +64 -0
- package/src/__tests__/env-example.test.ts +25 -0
- package/src/__tests__/error-boundaries.test.tsx +64 -0
- package/src/__tests__/fetch-pricing.test.ts +121 -0
- package/src/__tests__/field-oauth.test.tsx +302 -0
- package/src/__tests__/fixtures/mock-manifests-data.js +372 -0
- package/src/__tests__/fixtures/mock-manifests.ts +24 -0
- package/src/__tests__/fleet-health-timestamp.test.tsx +101 -0
- package/src/__tests__/fleet-health-update.test.tsx +83 -0
- package/src/__tests__/format-credit.test.ts +58 -0
- package/src/__tests__/gpu-dashboard.test.tsx +236 -0
- package/src/__tests__/hosted-usage-date-range.test.tsx +54 -0
- package/src/__tests__/instance-detail.test.tsx +571 -0
- package/src/__tests__/instance-list.test.tsx +230 -0
- package/src/__tests__/landing-hero.test.tsx +27 -0
- package/src/__tests__/landing-nav.test.tsx +24 -0
- package/src/__tests__/layout-snapshots.test.tsx +167 -0
- package/src/__tests__/logger.test.ts +54 -0
- package/src/__tests__/login-page-redirect.test.tsx +142 -0
- package/src/__tests__/manifest-validation.test.ts +126 -0
- package/src/__tests__/marketplace-admin.test.tsx +151 -0
- package/src/__tests__/marketplace.test.tsx +609 -0
- package/src/__tests__/merge-api-rates.test.ts +70 -0
- package/src/__tests__/middleware.test.ts +690 -0
- package/src/__tests__/network-page.test.tsx +100 -0
- package/src/__tests__/next-config-headers.test.ts +28 -0
- package/src/__tests__/not-found.test.tsx +26 -0
- package/src/__tests__/notifications.test.tsx +128 -0
- package/src/__tests__/oauth-buttons.test.tsx +101 -0
- package/src/__tests__/oauth-error-mapping.test.tsx +97 -0
- package/src/__tests__/observability.test.tsx +541 -0
- package/src/__tests__/onboarding-data.test.ts +363 -0
- package/src/__tests__/onboarding-page.test.tsx +113 -0
- package/src/__tests__/onboarding-store.test.ts +121 -0
- package/src/__tests__/org-billing-api.test.tsx +70 -0
- package/src/__tests__/org-billing-null-guards.test.ts +64 -0
- package/src/__tests__/org-billing-page.test.tsx +124 -0
- package/src/__tests__/plugin-definition.test.ts +43 -0
- package/src/__tests__/plugin-install-flow.test.tsx +535 -0
- package/src/__tests__/plugin-registry.test.tsx +475 -0
- package/src/__tests__/plugin-setup/setup-chat-panel.test.ts +142 -0
- package/src/__tests__/plugin-setup/use-plugin-setup-chat.test.ts +49 -0
- package/src/__tests__/plugin-tool-definitions.test.ts +51 -0
- package/src/__tests__/plugin-tool-sync.test.ts +59 -0
- package/src/__tests__/portfolio-chart.test.tsx +24 -0
- package/src/__tests__/pricing.test.tsx +107 -0
- package/src/__tests__/promotion-form.test.tsx +180 -0
- package/src/__tests__/promotions-list.test.tsx +194 -0
- package/src/__tests__/provider-key-api.test.ts +134 -0
- package/src/__tests__/resend-verification-button.test.tsx +104 -0
- package/src/__tests__/sanitize-redirect-url.test.ts +47 -0
- package/src/__tests__/secrets-audit-pagination.test.tsx +139 -0
- package/src/__tests__/settings.test.tsx +937 -0
- package/src/__tests__/setup-checklist.test.tsx +274 -0
- package/src/__tests__/setup.ts +82 -0
- package/src/__tests__/smoke.test.tsx +10 -0
- package/src/__tests__/snapshot-api.test.ts +104 -0
- package/src/__tests__/status-api.test.ts +46 -0
- package/src/__tests__/status-badge.test.tsx +33 -0
- package/src/__tests__/status-colors.test.ts +83 -0
- package/src/__tests__/status-page.test.tsx +86 -0
- package/src/__tests__/step-superpowers.test.tsx +218 -0
- package/src/__tests__/story-sections.test.tsx +24 -0
- package/src/__tests__/superpower-content-sanitize.test.tsx +87 -0
- package/src/__tests__/superpower-content.test.tsx +44 -0
- package/src/__tests__/suspension-banner.test.tsx +140 -0
- package/src/__tests__/tenant-context.test.tsx +146 -0
- package/src/__tests__/tenant-keys-api.test.ts +114 -0
- package/src/__tests__/tenant-table-pagination.test.tsx +124 -0
- package/src/__tests__/terminal-log-cleanup.test.tsx +51 -0
- package/src/__tests__/terminal-sequence.test.tsx +28 -0
- package/src/__tests__/transaction-history.test.tsx +325 -0
- package/src/__tests__/trpc-types.test.ts +102 -0
- package/src/__tests__/use-capability-meta.test.ts +161 -0
- package/src/__tests__/use-chat.test.ts +616 -0
- package/src/__tests__/use-has-org.test.ts +44 -0
- package/src/__tests__/use-image-status.test.ts +77 -0
- package/src/__tests__/use-pagination-params.test.ts +88 -0
- package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +53 -0
- package/src/__tests__/use-webmcp.test.ts +119 -0
- package/src/__tests__/validate-elevenlabs-key.test.ts +95 -0
- package/src/__tests__/validate-redirect-url.test.ts +61 -0
- package/src/__tests__/verify-page.test.tsx +140 -0
- package/src/__tests__/verify-redirect.test.tsx +41 -0
- package/src/__tests__/verify-result-banner.test.tsx +66 -0
- package/src/__tests__/webmcp-feature-detect.test.ts +54 -0
- package/src/__tests__/webmcp-hook.test.tsx +72 -0
- package/src/__tests__/webmcp-marketplace-onboarding-tools.test.ts +185 -0
- package/src/__tests__/webmcp-register.test.ts +103 -0
- package/src/__tests__/webmcp-set-provider.test.ts +47 -0
- package/src/__tests__/webmcp-tools.test.ts +348 -0
- package/src/app/(auth)/error.tsx +72 -0
- package/src/app/(auth)/forgot-password/page.tsx +137 -0
- package/src/app/(auth)/layout.tsx +14 -0
- package/src/app/(auth)/loading.tsx +26 -0
- package/src/app/(auth)/login/page.tsx +188 -0
- package/src/app/(auth)/reset-password/page.tsx +169 -0
- package/src/app/(auth)/signup/page.tsx +309 -0
- package/src/app/(dashboard)/billing/credits/page.tsx +209 -0
- package/src/app/(dashboard)/billing/error.tsx +72 -0
- package/src/app/(dashboard)/billing/layout.tsx +73 -0
- package/src/app/(dashboard)/billing/loading.tsx +41 -0
- package/src/app/(dashboard)/billing/payment/page.tsx +639 -0
- package/src/app/(dashboard)/billing/plans/page.tsx +58 -0
- package/src/app/(dashboard)/billing/referrals/page.tsx +7 -0
- package/src/app/(dashboard)/billing/usage/hosted/page.tsx +348 -0
- package/src/app/(dashboard)/billing/usage/page.tsx +663 -0
- package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +400 -0
- package/src/app/(dashboard)/changesets/[id]/error.tsx +57 -0
- package/src/app/(dashboard)/changesets/[id]/loading.tsx +23 -0
- package/src/app/(dashboard)/changesets/[id]/page.tsx +10 -0
- package/src/app/(dashboard)/changesets/error.tsx +72 -0
- package/src/app/(dashboard)/changesets/page.tsx +10 -0
- package/src/app/(dashboard)/chat/page.tsx +74 -0
- package/src/app/(dashboard)/dashboard/bots/[id]/settings/page.tsx +10 -0
- package/src/app/(dashboard)/dashboard/network/page.tsx +97 -0
- package/src/app/(dashboard)/dashboard/page.tsx +13 -0
- package/src/app/(dashboard)/error.tsx +72 -0
- package/src/app/(dashboard)/layout.tsx +113 -0
- package/src/app/(dashboard)/loading.tsx +27 -0
- package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +548 -0
- package/src/app/(dashboard)/marketplace/error.tsx +72 -0
- package/src/app/(dashboard)/marketplace/loading.tsx +27 -0
- package/src/app/(dashboard)/marketplace/page.tsx +268 -0
- package/src/app/(dashboard)/not-found.tsx +46 -0
- package/src/app/(dashboard)/onboarding/page.tsx +267 -0
- package/src/app/(dashboard)/settings/account/page.tsx +132 -0
- package/src/app/(dashboard)/settings/activity/page.tsx +280 -0
- package/src/app/(dashboard)/settings/api-keys/page.tsx +530 -0
- package/src/app/(dashboard)/settings/brain/page.tsx +412 -0
- package/src/app/(dashboard)/settings/error.tsx +72 -0
- package/src/app/(dashboard)/settings/layout.tsx +114 -0
- package/src/app/(dashboard)/settings/loading.tsx +31 -0
- package/src/app/(dashboard)/settings/notifications/page.tsx +216 -0
- package/src/app/(dashboard)/settings/org/page.tsx +617 -0
- package/src/app/(dashboard)/settings/profile/page.tsx +510 -0
- package/src/app/(dashboard)/settings/providers/page.tsx +842 -0
- package/src/app/(dashboard)/settings/secrets/page.tsx +658 -0
- package/src/app/(dashboard)/settings/security/page.tsx +1133 -0
- package/src/app/admin/accounting/loading.tsx +32 -0
- package/src/app/admin/accounting/page.tsx +5 -0
- package/src/app/admin/affiliates/loading.tsx +32 -0
- package/src/app/admin/affiliates/page.tsx +5 -0
- package/src/app/admin/audit/loading.tsx +32 -0
- package/src/app/admin/audit/page.tsx +5 -0
- package/src/app/admin/billing-health/loading.tsx +17 -0
- package/src/app/admin/billing-health/page.tsx +10 -0
- package/src/app/admin/compliance/page.tsx +5 -0
- package/src/app/admin/error.tsx +72 -0
- package/src/app/admin/gpu/loading.tsx +38 -0
- package/src/app/admin/gpu/page.tsx +5 -0
- package/src/app/admin/incidents/page.tsx +10 -0
- package/src/app/admin/inference/loading.tsx +32 -0
- package/src/app/admin/inference/page.tsx +5 -0
- package/src/app/admin/layout.tsx +44 -0
- package/src/app/admin/loading.tsx +32 -0
- package/src/app/admin/marketplace/loading.tsx +32 -0
- package/src/app/admin/marketplace/page.tsx +5 -0
- package/src/app/admin/migrations/loading.tsx +22 -0
- package/src/app/admin/migrations/page.tsx +5 -0
- package/src/app/admin/onboarding/loading.tsx +18 -0
- package/src/app/admin/onboarding/page.tsx +5 -0
- package/src/app/admin/promotions/[id]/edit/loading.tsx +16 -0
- package/src/app/admin/promotions/[id]/edit/page.tsx +56 -0
- package/src/app/admin/promotions/[id]/loading.tsx +15 -0
- package/src/app/admin/promotions/[id]/page.tsx +311 -0
- package/src/app/admin/promotions/loading.tsx +21 -0
- package/src/app/admin/promotions/new/loading.tsx +16 -0
- package/src/app/admin/promotions/new/page.tsx +12 -0
- package/src/app/admin/promotions/page.tsx +266 -0
- package/src/app/admin/rate-overrides/loading.tsx +17 -0
- package/src/app/admin/rate-overrides/page.tsx +290 -0
- package/src/app/admin/roles/loading.tsx +27 -0
- package/src/app/admin/roles/page.tsx +5 -0
- package/src/app/admin/tenants/loading.tsx +32 -0
- package/src/app/admin/tenants/page.tsx +5 -0
- package/src/app/apple-icon.tsx +32 -0
- package/src/app/auth/callback/[provider]/page.tsx +104 -0
- package/src/app/auth/verify/page.tsx +224 -0
- package/src/app/channels/error.tsx +72 -0
- package/src/app/channels/loading.tsx +29 -0
- package/src/app/channels/page.tsx +262 -0
- package/src/app/channels/setup/[plugin]/page.tsx +136 -0
- package/src/app/error.tsx +72 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/fleet/error.tsx +72 -0
- package/src/app/fleet/health/page.tsx +9 -0
- package/src/app/fleet/layout.tsx +14 -0
- package/src/app/fleet/loading.tsx +33 -0
- package/src/app/fleet/page.tsx +5 -0
- package/src/app/global-error.tsx +96 -0
- package/src/app/globals.css +251 -0
- package/src/app/icon.svg +4 -0
- package/src/app/instances/[id]/instance-detail-client.tsx +1298 -0
- package/src/app/instances/[id]/page.tsx +10 -0
- package/src/app/instances/error.tsx +72 -0
- package/src/app/instances/instance-list-client.tsx +540 -0
- package/src/app/instances/loading.tsx +33 -0
- package/src/app/instances/new/create-instance-client.tsx +377 -0
- package/src/app/instances/new/page.tsx +9 -0
- package/src/app/instances/page.tsx +9 -0
- package/src/app/layout.tsx +83 -0
- package/src/app/not-found.tsx +38 -0
- package/src/app/og/route.tsx +50 -0
- package/src/app/page.tsx +39 -0
- package/src/app/plugins/error.tsx +72 -0
- package/src/app/plugins/layout.tsx +14 -0
- package/src/app/plugins/loading.tsx +30 -0
- package/src/app/plugins/page.tsx +555 -0
- package/src/app/pricing/error.tsx +72 -0
- package/src/app/pricing/loading.tsx +25 -0
- package/src/app/pricing/page.tsx +20 -0
- package/src/app/privacy/page.tsx +406 -0
- package/src/app/robots.ts +9 -0
- package/src/app/sitemap.ts +11 -0
- package/src/app/status/error.tsx +72 -0
- package/src/app/status/loading.tsx +21 -0
- package/src/app/status/page.tsx +20 -0
- package/src/app/terms/page.tsx +414 -0
- package/src/components/account-switcher.tsx +82 -0
- package/src/components/admin/accounting-dashboard.tsx +190 -0
- package/src/components/admin/admin-guard.tsx +36 -0
- package/src/components/admin/admin-nav.tsx +71 -0
- package/src/components/admin/affiliate-dashboard.tsx +564 -0
- package/src/components/admin/audit-log-table.tsx +336 -0
- package/src/components/admin/billing-health-dashboard.test.tsx +40 -0
- package/src/components/admin/billing-health-dashboard.tsx +416 -0
- package/src/components/admin/bulk-actions-bar.test.tsx +92 -0
- package/src/components/admin/bulk-actions-bar.tsx +80 -0
- package/src/components/admin/bulk-export-dialog.test.tsx +75 -0
- package/src/components/admin/bulk-export-dialog.tsx +189 -0
- package/src/components/admin/bulk-grant-dialog.test.tsx +81 -0
- package/src/components/admin/bulk-grant-dialog.tsx +147 -0
- package/src/components/admin/bulk-preview-dialog.test.tsx +72 -0
- package/src/components/admin/bulk-preview-dialog.tsx +106 -0
- package/src/components/admin/bulk-reactivate-dialog.test.tsx +51 -0
- package/src/components/admin/bulk-reactivate-dialog.tsx +55 -0
- package/src/components/admin/bulk-select-all-banner.test.tsx +36 -0
- package/src/components/admin/bulk-select-all-banner.tsx +44 -0
- package/src/components/admin/bulk-suspend-dialog.test.tsx +77 -0
- package/src/components/admin/bulk-suspend-dialog.tsx +129 -0
- package/src/components/admin/bulk-undo-toast.test.tsx +66 -0
- package/src/components/admin/bulk-undo-toast.tsx +121 -0
- package/src/components/admin/compliance-dashboard.tsx +1341 -0
- package/src/components/admin/gpu-dashboard.tsx +552 -0
- package/src/components/admin/grant-credits-dialog.tsx +121 -0
- package/src/components/admin/incident-dashboard.test.tsx +44 -0
- package/src/components/admin/incident-dashboard.tsx +717 -0
- package/src/components/admin/inference-dashboard.tsx +415 -0
- package/src/components/admin/marketplace-admin.tsx +765 -0
- package/src/components/admin/migrations-dashboard.tsx +404 -0
- package/src/components/admin/onboarding-dashboard.tsx +422 -0
- package/src/components/admin/promotions/promotion-form.tsx +440 -0
- package/src/components/admin/roles-dashboard.tsx +278 -0
- package/src/components/admin/suspend-dialog.tsx +98 -0
- package/src/components/admin/tenant-notes-panel.tsx +134 -0
- package/src/components/admin/tenant-row-actions.tsx +78 -0
- package/src/components/admin/tenant-table.tsx +339 -0
- package/src/components/auth/auth-error.tsx +22 -0
- package/src/components/auth/auth-redirect.tsx +18 -0
- package/src/components/auth/auth-shell.tsx +25 -0
- package/src/components/auth/email-verification-banner.tsx +25 -0
- package/src/components/auth/email-verification-result-banner.tsx +70 -0
- package/src/components/auth/resend-verification-button.tsx +94 -0
- package/src/components/auth/wopr-wordmark.tsx +19 -0
- package/src/components/billing/add-payment-method-dialog.tsx +267 -0
- package/src/components/billing/affiliate-dashboard.tsx +300 -0
- package/src/components/billing/auto-topup-card.tsx +432 -0
- package/src/components/billing/buy-credits-panel.tsx +180 -0
- package/src/components/billing/buy-crypto-credits-panel.tsx +96 -0
- package/src/components/billing/byok-callout.tsx +87 -0
- package/src/components/billing/coupon-input.tsx +86 -0
- package/src/components/billing/credit-balance.tsx +79 -0
- package/src/components/billing/degraded-state-banner.tsx +95 -0
- package/src/components/billing/dividend-banner.tsx +97 -0
- package/src/components/billing/dividend-eligibility.tsx +86 -0
- package/src/components/billing/dividend-pool-stats.tsx +86 -0
- package/src/components/billing/first-dividend-dialog.tsx +109 -0
- package/src/components/billing/low-balance-banner.tsx +50 -0
- package/src/components/billing/org-billing-page.tsx +360 -0
- package/src/components/billing/suspension-banner.tsx +53 -0
- package/src/components/billing/transaction-history.tsx +239 -0
- package/src/components/bot-settings/__tests__/bot-settings-client.test.tsx +205 -0
- package/src/components/bot-settings/backups-tab.tsx +377 -0
- package/src/components/bot-settings/bot-settings-client.tsx +1712 -0
- package/src/components/bot-settings/resources-tab.tsx +203 -0
- package/src/components/bot-settings/storage-tab.tsx +248 -0
- package/src/components/bot-settings/vps-info-panel.tsx +132 -0
- package/src/components/bot-settings/vps-upgrade-card.tsx +110 -0
- package/src/components/capability/CapabilityResolver.tsx +113 -0
- package/src/components/channel-wizard/field-interactive.tsx +48 -0
- package/src/components/channel-wizard/field-oauth.tsx +181 -0
- package/src/components/channel-wizard/field-paste.tsx +47 -0
- package/src/components/channel-wizard/field-qr.tsx +302 -0
- package/src/components/channel-wizard/index.ts +6 -0
- package/src/components/channel-wizard/step-renderer.tsx +103 -0
- package/src/components/channel-wizard/wizard.tsx +200 -0
- package/src/components/chat/ambient-dot.tsx +32 -0
- package/src/components/chat/chat-input.tsx +56 -0
- package/src/components/chat/chat-message.tsx +36 -0
- package/src/components/chat/chat-panel.tsx +138 -0
- package/src/components/chat/chat-widget.tsx +41 -0
- package/src/components/chat/index.ts +5 -0
- package/src/components/dashboard/command-center.tsx +614 -0
- package/src/components/instances/friends-tab.test.tsx +265 -0
- package/src/components/instances/friends-tab.tsx +721 -0
- package/src/components/landing/hero.tsx +53 -0
- package/src/components/landing/landing-nav.tsx +21 -0
- package/src/components/landing/landing-page.tsx +71 -0
- package/src/components/landing/portfolio-chart.tsx +349 -0
- package/src/components/landing/story-sections.tsx +50 -0
- package/src/components/landing/terminal-lines.ts +99 -0
- package/src/components/landing/terminal-sequence.tsx +453 -0
- package/src/components/landing/typing-effect.tsx +43 -0
- package/src/components/marketplace/category-filter.tsx +61 -0
- package/src/components/marketplace/empty-state.tsx +61 -0
- package/src/components/marketplace/featured-heroes.tsx +84 -0
- package/src/components/marketplace/first-visit-hero.tsx +110 -0
- package/src/components/marketplace/index.ts +9 -0
- package/src/components/marketplace/install-wizard.tsx +782 -0
- package/src/components/marketplace/marketplace-tabs.tsx +54 -0
- package/src/components/marketplace/plugin-card.tsx +129 -0
- package/src/components/marketplace/superpower-card.tsx +104 -0
- package/src/components/marketplace/superpower-content.tsx +117 -0
- package/src/components/marketplace/terminal-search.tsx +67 -0
- package/src/components/oauth-buttons.tsx +75 -0
- package/src/components/observability/fleet-health.tsx +370 -0
- package/src/components/observability/health-overview.tsx +246 -0
- package/src/components/observability/logs-viewer.tsx +215 -0
- package/src/components/observability/metrics-dashboard.tsx +288 -0
- package/src/components/onboarding/fallback-setup.tsx +137 -0
- package/src/components/onboarding/index.ts +3 -0
- package/src/components/onboarding/setup-checklist.tsx +333 -0
- package/src/components/onboarding/step-superpowers.tsx +122 -0
- package/src/components/plugin-setup/index.ts +1 -0
- package/src/components/plugin-setup/setup-chat-panel.tsx +188 -0
- package/src/components/pricing/dividend-calculator.tsx +47 -0
- package/src/components/pricing/dividend-stats.tsx +117 -0
- package/src/components/pricing/pricing-page.tsx +229 -0
- package/src/components/settings/create-org-wizard.tsx +225 -0
- package/src/components/sidebar.tsx +202 -0
- package/src/components/status/status-page.tsx +209 -0
- package/src/components/status-badge.tsx +28 -0
- package/src/components/theme-provider.tsx +8 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/badge.tsx +47 -0
- package/src/components/ui/banner.tsx +36 -0
- package/src/components/ui/button.tsx +64 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/checkbox.tsx +52 -0
- package/src/components/ui/collapsible.tsx +31 -0
- package/src/components/ui/credit-detailed.tsx +33 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +228 -0
- package/src/components/ui/form.tsx +151 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +21 -0
- package/src/components/ui/popover.tsx +74 -0
- package/src/components/ui/progress.tsx +28 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/select.tsx +175 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +125 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/switch.tsx +35 -0
- package/src/components/ui/table.tsx +92 -0
- package/src/components/ui/tabs.tsx +81 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +44 -0
- package/src/config/provider-docs.ts +17 -0
- package/src/hooks/__tests__/use-async.test.ts +127 -0
- package/src/hooks/__tests__/use-count-up.test.ts +129 -0
- package/src/hooks/__tests__/use-debounce.test.ts +105 -0
- package/src/hooks/__tests__/use-fleet-sse.test.ts +216 -0
- package/src/hooks/__tests__/use-local-storage.test.ts +74 -0
- package/src/hooks/__tests__/use-mobile.test.ts +86 -0
- package/src/hooks/__tests__/use-save-queue.test.ts +159 -0
- package/src/hooks/use-async.ts +54 -0
- package/src/hooks/use-capability-meta.ts +99 -0
- package/src/hooks/use-count-up.ts +23 -0
- package/src/hooks/use-debounce.ts +12 -0
- package/src/hooks/use-fleet-sse.ts +47 -0
- package/src/hooks/use-has-org.ts +18 -0
- package/src/hooks/use-image-status.ts +36 -0
- package/src/hooks/use-local-storage.ts +36 -0
- package/src/hooks/use-mobile.ts +17 -0
- package/src/hooks/use-page-context.ts +24 -0
- package/src/hooks/use-pagination-params.ts +30 -0
- package/src/hooks/use-plugin-registry.ts +247 -0
- package/src/hooks/use-plugin-setup-chat.ts +211 -0
- package/src/hooks/use-save-queue.ts +54 -0
- package/src/hooks/use-webmcp.ts +40 -0
- package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +112 -0
- package/src/lib/__tests__/admin-api.test.ts +487 -0
- package/src/lib/__tests__/api-bot-crud.test.ts +391 -0
- package/src/lib/__tests__/api-fetch.test.ts +196 -0
- package/src/lib/__tests__/bot-settings-data.test.ts +352 -0
- package/src/lib/__tests__/org-api.test.ts +281 -0
- package/src/lib/__tests__/org-billing-api.test.ts +242 -0
- package/src/lib/__tests__/pricing-data.test.ts +32 -0
- package/src/lib/__tests__/settings-api.test.ts +272 -0
- package/src/lib/admin-affiliate-api.ts +51 -0
- package/src/lib/admin-api.ts +325 -0
- package/src/lib/admin-compliance-api.ts +127 -0
- package/src/lib/admin-gpu-api.ts +82 -0
- package/src/lib/admin-incident-api.ts +121 -0
- package/src/lib/admin-inference-api.ts +47 -0
- package/src/lib/admin-marketplace-api.ts +97 -0
- package/src/lib/api-config.test.ts +111 -0
- package/src/lib/api-config.ts +65 -0
- package/src/lib/api-errors.test.ts +43 -0
- package/src/lib/api.ts +2011 -0
- package/src/lib/auth-client.ts +11 -0
- package/src/lib/bot-settings-data.ts +342 -0
- package/src/lib/brand-config.ts +145 -0
- package/src/lib/brand.ts +669 -0
- package/src/lib/changeset-api.ts +29 -0
- package/src/lib/changeset-types.ts +56 -0
- package/src/lib/channel-manifests.ts +50 -0
- package/src/lib/chat/chat-context.tsx +70 -0
- package/src/lib/chat/chat-store.ts +62 -0
- package/src/lib/chat/types.ts +35 -0
- package/src/lib/chat/use-chat.ts +255 -0
- package/src/lib/cost-comparison-data.test.ts +95 -0
- package/src/lib/cost-comparison-data.ts +54 -0
- package/src/lib/errors.test.ts +64 -0
- package/src/lib/errors.ts +52 -0
- package/src/lib/fetch-utils.test.ts +57 -0
- package/src/lib/fetch-utils.ts +25 -0
- package/src/lib/format-credit.test.ts +66 -0
- package/src/lib/format-credit.ts +24 -0
- package/src/lib/format.test.ts +62 -0
- package/src/lib/format.ts +17 -0
- package/src/lib/logger.ts +28 -0
- package/src/lib/marketplace-data.ts +346 -0
- package/src/lib/oauth-errors.ts +19 -0
- package/src/lib/onboarding-data.ts +1265 -0
- package/src/lib/onboarding-store.ts +233 -0
- package/src/lib/org-api.ts +74 -0
- package/src/lib/org-billing-api.ts +81 -0
- package/src/lib/page-prompts.test.ts +32 -0
- package/src/lib/page-prompts.ts +23 -0
- package/src/lib/plugin/index.ts +32 -0
- package/src/lib/plugin/tool-definitions.ts +306 -0
- package/src/lib/pricing-data.ts +115 -0
- package/src/lib/promotions-types.ts +58 -0
- package/src/lib/settings-api.ts +63 -0
- package/src/lib/status-colors.ts +38 -0
- package/src/lib/tenant-context.tsx +134 -0
- package/src/lib/trpc-types.ts +173 -0
- package/src/lib/trpc.tsx +86 -0
- package/src/lib/utils.test.ts +55 -0
- package/src/lib/utils.ts +18 -0
- package/src/lib/validate-redirect-url.ts +39 -0
- package/src/lib/webmcp/feature-detect.ts +13 -0
- package/src/lib/webmcp/marketplace-onboarding-tools.ts +202 -0
- package/src/lib/webmcp/register.ts +44 -0
- package/src/lib/webmcp/tools.ts +422 -0
- package/src/proxy.ts +258 -0
- package/src/types/missing-deps.d.ts +160 -0
- package/src/types/motion-dom.d.ts +162 -0
- package/src/types/vitest-matchers.d.ts +40 -0
- package/src/types/web-mcp.d.ts +22 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +26 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import userEvent from "@testing-library/user-event";
|
|
3
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import type { CreditHistoryResponse } from "@/lib/api";
|
|
5
|
+
|
|
6
|
+
const mockGetCreditHistory = vi.fn<(cursor?: string) => Promise<CreditHistoryResponse>>();
|
|
7
|
+
|
|
8
|
+
vi.mock("@/lib/api", () => ({
|
|
9
|
+
getCreditHistory: (...args: unknown[]) => mockGetCreditHistory(args[0] as string | undefined),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
// Must import AFTER vi.mock
|
|
13
|
+
const { TransactionHistory } = await import("@/components/billing/transaction-history");
|
|
14
|
+
|
|
15
|
+
describe("TransactionHistory", () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
mockGetCreditHistory.mockReset();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("shows loading skeletons initially", () => {
|
|
21
|
+
// Never resolve — keeps component in loading state
|
|
22
|
+
// biome-ignore lint/suspicious/noEmptyBlockStatements: intentionally never-resolving promise for loading state test
|
|
23
|
+
mockGetCreditHistory.mockReturnValue(new Promise(() => {}));
|
|
24
|
+
render(<TransactionHistory />);
|
|
25
|
+
expect(screen.getByText("Transaction History")).toBeInTheDocument();
|
|
26
|
+
// 4 skeleton rows are rendered (no transaction text visible)
|
|
27
|
+
expect(screen.queryByText("No transactions yet.")).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("renders empty state when no transactions", async () => {
|
|
31
|
+
mockGetCreditHistory.mockResolvedValue({ transactions: [], nextCursor: null });
|
|
32
|
+
render(<TransactionHistory />);
|
|
33
|
+
await waitFor(() => {
|
|
34
|
+
expect(screen.getByText("No transactions yet.")).toBeInTheDocument();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("renders transaction descriptions", async () => {
|
|
39
|
+
mockGetCreditHistory.mockResolvedValue({
|
|
40
|
+
transactions: [
|
|
41
|
+
{
|
|
42
|
+
id: "tx-1",
|
|
43
|
+
type: "purchase",
|
|
44
|
+
description: "Credit top-up",
|
|
45
|
+
amount: 25.0,
|
|
46
|
+
createdAt: "2025-06-15T10:00:00Z",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
nextCursor: null,
|
|
50
|
+
});
|
|
51
|
+
render(<TransactionHistory />);
|
|
52
|
+
await waitFor(() => {
|
|
53
|
+
expect(screen.getByText("Credit top-up")).toBeInTheDocument();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("renders positive amounts with + prefix and emerald color", async () => {
|
|
58
|
+
mockGetCreditHistory.mockResolvedValue({
|
|
59
|
+
transactions: [
|
|
60
|
+
{
|
|
61
|
+
id: "tx-1",
|
|
62
|
+
type: "purchase",
|
|
63
|
+
description: "Top-up",
|
|
64
|
+
amount: 50.0,
|
|
65
|
+
createdAt: "2025-06-15T10:00:00Z",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
nextCursor: null,
|
|
69
|
+
});
|
|
70
|
+
render(<TransactionHistory />);
|
|
71
|
+
await waitFor(() => {
|
|
72
|
+
const amountEl = screen.getByText("+$50.00");
|
|
73
|
+
expect(amountEl).toBeInTheDocument();
|
|
74
|
+
expect(amountEl.className).toContain("text-emerald-500");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("renders negative amounts with - prefix and red color", async () => {
|
|
79
|
+
mockGetCreditHistory.mockResolvedValue({
|
|
80
|
+
transactions: [
|
|
81
|
+
{
|
|
82
|
+
id: "tx-1",
|
|
83
|
+
type: "bot_runtime",
|
|
84
|
+
description: "Bot usage",
|
|
85
|
+
amount: -3.5,
|
|
86
|
+
createdAt: "2025-06-15T10:00:00Z",
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
nextCursor: null,
|
|
90
|
+
});
|
|
91
|
+
render(<TransactionHistory />);
|
|
92
|
+
await waitFor(() => {
|
|
93
|
+
const amountEl = screen.getByText("-$3.50");
|
|
94
|
+
expect(amountEl).toBeInTheDocument();
|
|
95
|
+
expect(amountEl.className).toContain("text-red-500");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("renders correct type badge labels", async () => {
|
|
100
|
+
mockGetCreditHistory.mockResolvedValue({
|
|
101
|
+
transactions: [
|
|
102
|
+
{
|
|
103
|
+
id: "tx-1",
|
|
104
|
+
type: "purchase",
|
|
105
|
+
description: "a",
|
|
106
|
+
amount: 10,
|
|
107
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "tx-2",
|
|
111
|
+
type: "signup_credit",
|
|
112
|
+
description: "b",
|
|
113
|
+
amount: 5,
|
|
114
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "tx-3",
|
|
118
|
+
type: "bot_runtime",
|
|
119
|
+
description: "c",
|
|
120
|
+
amount: -2,
|
|
121
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "tx-4",
|
|
125
|
+
type: "refund",
|
|
126
|
+
description: "d",
|
|
127
|
+
amount: 3,
|
|
128
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "tx-5",
|
|
132
|
+
type: "bonus",
|
|
133
|
+
description: "e",
|
|
134
|
+
amount: 1,
|
|
135
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: "tx-6",
|
|
139
|
+
type: "adjustment",
|
|
140
|
+
description: "f",
|
|
141
|
+
amount: -1,
|
|
142
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: "tx-7",
|
|
146
|
+
type: "community_dividend",
|
|
147
|
+
description: "g",
|
|
148
|
+
amount: 2,
|
|
149
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
nextCursor: null,
|
|
153
|
+
});
|
|
154
|
+
render(<TransactionHistory />);
|
|
155
|
+
await waitFor(() => {
|
|
156
|
+
expect(screen.getByText("Purchase")).toBeInTheDocument();
|
|
157
|
+
expect(screen.getByText("Signup credit")).toBeInTheDocument();
|
|
158
|
+
expect(screen.getByText("Bot runtime")).toBeInTheDocument();
|
|
159
|
+
expect(screen.getByText("Refund")).toBeInTheDocument();
|
|
160
|
+
expect(screen.getByText("Bonus")).toBeInTheDocument();
|
|
161
|
+
expect(screen.getByText("Adjustment")).toBeInTheDocument();
|
|
162
|
+
expect(screen.getByText("Dividend")).toBeInTheDocument();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("renders formatted dates", async () => {
|
|
167
|
+
mockGetCreditHistory.mockResolvedValue({
|
|
168
|
+
transactions: [
|
|
169
|
+
{
|
|
170
|
+
id: "tx-1",
|
|
171
|
+
type: "purchase",
|
|
172
|
+
description: "Top-up",
|
|
173
|
+
amount: 10,
|
|
174
|
+
createdAt: "2025-06-15T10:00:00Z",
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
nextCursor: null,
|
|
178
|
+
});
|
|
179
|
+
render(<TransactionHistory />);
|
|
180
|
+
await waitFor(() => {
|
|
181
|
+
// toLocaleDateString("en-US", { month: "short", day: "numeric" }) → "Jun 15"
|
|
182
|
+
expect(screen.getByText("Jun 15")).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("shows error state with retry button", async () => {
|
|
187
|
+
mockGetCreditHistory.mockRejectedValue(new Error("Network error"));
|
|
188
|
+
render(<TransactionHistory />);
|
|
189
|
+
await waitFor(() => {
|
|
190
|
+
expect(screen.getByText("Failed to load transactions.")).toBeInTheDocument();
|
|
191
|
+
expect(screen.getByText("Retry")).toBeInTheDocument();
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("retries loading on retry button click", async () => {
|
|
196
|
+
const user = userEvent.setup();
|
|
197
|
+
// First call fails, second succeeds
|
|
198
|
+
mockGetCreditHistory
|
|
199
|
+
.mockRejectedValueOnce(new Error("fail"))
|
|
200
|
+
.mockResolvedValueOnce({ transactions: [], nextCursor: null });
|
|
201
|
+
|
|
202
|
+
render(<TransactionHistory />);
|
|
203
|
+
await waitFor(() => {
|
|
204
|
+
expect(screen.getByText("Retry")).toBeInTheDocument();
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await user.click(screen.getByText("Retry"));
|
|
208
|
+
await waitFor(() => {
|
|
209
|
+
expect(screen.getByText("No transactions yet.")).toBeInTheDocument();
|
|
210
|
+
});
|
|
211
|
+
expect(mockGetCreditHistory).toHaveBeenCalledTimes(2);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("shows 'Load more' button when cursor exists", async () => {
|
|
215
|
+
mockGetCreditHistory.mockResolvedValue({
|
|
216
|
+
transactions: [
|
|
217
|
+
{
|
|
218
|
+
id: "tx-1",
|
|
219
|
+
type: "purchase",
|
|
220
|
+
description: "Top-up",
|
|
221
|
+
amount: 10,
|
|
222
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
nextCursor: "next-page-cursor",
|
|
226
|
+
});
|
|
227
|
+
render(<TransactionHistory />);
|
|
228
|
+
await waitFor(() => {
|
|
229
|
+
expect(screen.getByText("Load more")).toBeInTheDocument();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("does not show 'Load more' when cursor is null", async () => {
|
|
234
|
+
mockGetCreditHistory.mockResolvedValue({
|
|
235
|
+
transactions: [
|
|
236
|
+
{
|
|
237
|
+
id: "tx-1",
|
|
238
|
+
type: "purchase",
|
|
239
|
+
description: "Top-up",
|
|
240
|
+
amount: 10,
|
|
241
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
nextCursor: null,
|
|
245
|
+
});
|
|
246
|
+
render(<TransactionHistory />);
|
|
247
|
+
await waitFor(() => {
|
|
248
|
+
expect(screen.getByText("Top-up")).toBeInTheDocument();
|
|
249
|
+
});
|
|
250
|
+
expect(screen.queryByText("Load more")).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("loads more transactions on 'Load more' click", async () => {
|
|
254
|
+
const user = userEvent.setup();
|
|
255
|
+
mockGetCreditHistory
|
|
256
|
+
.mockResolvedValueOnce({
|
|
257
|
+
transactions: [
|
|
258
|
+
{
|
|
259
|
+
id: "tx-1",
|
|
260
|
+
type: "purchase",
|
|
261
|
+
description: "First batch",
|
|
262
|
+
amount: 10,
|
|
263
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
nextCursor: "cursor-2",
|
|
267
|
+
})
|
|
268
|
+
.mockResolvedValueOnce({
|
|
269
|
+
transactions: [
|
|
270
|
+
{
|
|
271
|
+
id: "tx-2",
|
|
272
|
+
type: "refund",
|
|
273
|
+
description: "Second batch",
|
|
274
|
+
amount: 5,
|
|
275
|
+
createdAt: "2025-01-02T00:00:00Z",
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
nextCursor: null,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
render(<TransactionHistory />);
|
|
282
|
+
await waitFor(() => {
|
|
283
|
+
expect(screen.getByText("First batch")).toBeInTheDocument();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await user.click(screen.getByText("Load more"));
|
|
287
|
+
await waitFor(() => {
|
|
288
|
+
expect(screen.getByText("Second batch")).toBeInTheDocument();
|
|
289
|
+
});
|
|
290
|
+
// Both batches visible
|
|
291
|
+
expect(screen.getByText("First batch")).toBeInTheDocument();
|
|
292
|
+
// Cursor was passed to second call
|
|
293
|
+
expect(mockGetCreditHistory).toHaveBeenCalledWith("cursor-2");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it("shows inline error when load-more fails but keeps existing transactions", async () => {
|
|
297
|
+
const user = userEvent.setup();
|
|
298
|
+
mockGetCreditHistory
|
|
299
|
+
.mockResolvedValueOnce({
|
|
300
|
+
transactions: [
|
|
301
|
+
{
|
|
302
|
+
id: "tx-1",
|
|
303
|
+
type: "purchase",
|
|
304
|
+
description: "Existing",
|
|
305
|
+
amount: 10,
|
|
306
|
+
createdAt: "2025-01-01T00:00:00Z",
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
nextCursor: "cursor-2",
|
|
310
|
+
})
|
|
311
|
+
.mockRejectedValueOnce(new Error("fail"));
|
|
312
|
+
|
|
313
|
+
render(<TransactionHistory />);
|
|
314
|
+
await waitFor(() => {
|
|
315
|
+
expect(screen.getByText("Existing")).toBeInTheDocument();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
await user.click(screen.getByText("Load more"));
|
|
319
|
+
await waitFor(() => {
|
|
320
|
+
expect(screen.getByText("Failed to load more transactions.")).toBeInTheDocument();
|
|
321
|
+
});
|
|
322
|
+
// Original transaction still visible
|
|
323
|
+
expect(screen.getByText("Existing")).toBeInTheDocument();
|
|
324
|
+
});
|
|
325
|
+
});
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { AppRouter } from "@/lib/trpc-types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compile-time assertion: causes a TypeScript error if T is not `true`.
|
|
6
|
+
* This makes type-level tests actually fail at type-check time (tsc --noEmit)
|
|
7
|
+
* when a namespace or procedure is removed from the AppRouter stub.
|
|
8
|
+
*/
|
|
9
|
+
type Assert<T extends true> = T;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Type-level tests for the AppRouter stub.
|
|
13
|
+
*
|
|
14
|
+
* These verify that the type contract declared in trpc-types.ts
|
|
15
|
+
* includes all router namespaces the UI depends on. If a namespace
|
|
16
|
+
* is removed from the stub, these tests fail at type-check time
|
|
17
|
+
* (tsc --noEmit / npm run check), catching the breakage before runtime.
|
|
18
|
+
*/
|
|
19
|
+
describe("trpc-types AppRouter contract", () => {
|
|
20
|
+
it("declares all expected router namespaces", () => {
|
|
21
|
+
type Router = AppRouter["_def"]["record"];
|
|
22
|
+
type _CheckPageContext = Assert<"pageContext" extends keyof Router ? true : false>;
|
|
23
|
+
type _CheckAdmin = Assert<"admin" extends keyof Router ? true : false>;
|
|
24
|
+
type _CheckBilling = Assert<"billing" extends keyof Router ? true : false>;
|
|
25
|
+
type _CheckFleet = Assert<"fleet" extends keyof Router ? true : false>;
|
|
26
|
+
type _CheckPromotions = Assert<"promotions" extends keyof Router ? true : false>;
|
|
27
|
+
type _CheckRateOverrides = Assert<"rateOverrides" extends keyof Router ? true : false>;
|
|
28
|
+
type _CheckSettings = Assert<"settings" extends keyof Router ? true : false>;
|
|
29
|
+
type _CheckCapabilities = Assert<"capabilities" extends keyof Router ? true : false>;
|
|
30
|
+
type _CheckAuthSocial = Assert<"authSocial" extends keyof Router ? true : false>;
|
|
31
|
+
// Runtime assertion is intentionally trivial — the real gate is tsc --noEmit
|
|
32
|
+
// which fails if any Assert<...> type above resolves to `false`.
|
|
33
|
+
expect(true).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("billing namespace has critical procedures", () => {
|
|
37
|
+
type Billing = AppRouter["_def"]["record"]["billing"];
|
|
38
|
+
type _CheckCurrentPlan = Assert<"currentPlan" extends keyof Billing ? true : false>;
|
|
39
|
+
type _CheckCreditsBalance = Assert<"creditsBalance" extends keyof Billing ? true : false>;
|
|
40
|
+
type _CheckCreditsCheckout = Assert<"creditsCheckout" extends keyof Billing ? true : false>;
|
|
41
|
+
type _CheckApplyCoupon = Assert<"applyCoupon" extends keyof Billing ? true : false>;
|
|
42
|
+
type _CheckPortalSession = Assert<"portalSession" extends keyof Billing ? true : false>;
|
|
43
|
+
type _CheckAutoTopupSettings = Assert<"autoTopupSettings" extends keyof Billing ? true : false>;
|
|
44
|
+
type _CheckAccountStatus = Assert<"accountStatus" extends keyof Billing ? true : false>;
|
|
45
|
+
// Runtime assertion is intentionally trivial — the real gate is tsc --noEmit
|
|
46
|
+
// which fails if any Assert<...> type above resolves to `false`.
|
|
47
|
+
expect(true).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("fleet namespace has instance management procedures", () => {
|
|
51
|
+
type Fleet = AppRouter["_def"]["record"]["fleet"];
|
|
52
|
+
type _CheckListInstances = Assert<"listInstances" extends keyof Fleet ? true : false>;
|
|
53
|
+
type _CheckGetInstance = Assert<"getInstance" extends keyof Fleet ? true : false>;
|
|
54
|
+
type _CheckCreateInstance = Assert<"createInstance" extends keyof Fleet ? true : false>;
|
|
55
|
+
type _CheckControlInstance = Assert<"controlInstance" extends keyof Fleet ? true : false>;
|
|
56
|
+
type _CheckGetInstanceHealth = Assert<"getInstanceHealth" extends keyof Fleet ? true : false>;
|
|
57
|
+
type _CheckGetInstanceLogs = Assert<"getInstanceLogs" extends keyof Fleet ? true : false>;
|
|
58
|
+
type _CheckGetInstanceMetrics = Assert<"getInstanceMetrics" extends keyof Fleet ? true : false>;
|
|
59
|
+
type _CheckListTemplates = Assert<"listTemplates" extends keyof Fleet ? true : false>;
|
|
60
|
+
// Runtime assertion is intentionally trivial — the real gate is tsc --noEmit
|
|
61
|
+
// which fails if any Assert<...> type above resolves to `false`.
|
|
62
|
+
expect(true).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("promotions namespace has CRUD and coupon procedures", () => {
|
|
66
|
+
type Promos = AppRouter["_def"]["record"]["promotions"];
|
|
67
|
+
type _CheckList = Assert<"list" extends keyof Promos ? true : false>;
|
|
68
|
+
type _CheckCreate = Assert<"create" extends keyof Promos ? true : false>;
|
|
69
|
+
type _CheckUpdate = Assert<"update" extends keyof Promos ? true : false>;
|
|
70
|
+
type _CheckActivate = Assert<"activate" extends keyof Promos ? true : false>;
|
|
71
|
+
type _CheckPause = Assert<"pause" extends keyof Promos ? true : false>;
|
|
72
|
+
type _CheckCancel = Assert<"cancel" extends keyof Promos ? true : false>;
|
|
73
|
+
type _CheckGenerateCouponBatch = Assert<
|
|
74
|
+
"generateCouponBatch" extends keyof Promos ? true : false
|
|
75
|
+
>;
|
|
76
|
+
// Runtime assertion is intentionally trivial — the real gate is tsc --noEmit
|
|
77
|
+
// which fails if any Assert<...> type above resolves to `false`.
|
|
78
|
+
expect(true).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("capabilities namespace has key and settings procedures", () => {
|
|
82
|
+
type Caps = AppRouter["_def"]["record"]["capabilities"];
|
|
83
|
+
type _CheckStoreKey = Assert<"storeKey" extends keyof Caps ? true : false>;
|
|
84
|
+
type _CheckTestKey = Assert<"testKey" extends keyof Caps ? true : false>;
|
|
85
|
+
type _CheckListCapabilitySettings = Assert<
|
|
86
|
+
"listCapabilitySettings" extends keyof Caps ? true : false
|
|
87
|
+
>;
|
|
88
|
+
type _CheckListCapabilityMeta = Assert<"listCapabilityMeta" extends keyof Caps ? true : false>;
|
|
89
|
+
// Runtime assertion is intentionally trivial — the real gate is tsc --noEmit
|
|
90
|
+
// which fails if any Assert<...> type above resolves to `false`.
|
|
91
|
+
expect(true).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("admin namespace has inference and billingHealth", () => {
|
|
95
|
+
type Admin = AppRouter["_def"]["record"]["admin"];
|
|
96
|
+
type _CheckInference = Assert<"inference" extends keyof Admin ? true : false>;
|
|
97
|
+
type _CheckBillingHealth = Assert<"billingHealth" extends keyof Admin ? true : false>;
|
|
98
|
+
// Runtime assertion is intentionally trivial — the real gate is tsc --noEmit
|
|
99
|
+
// which fails if any Assert<...> type above resolves to `false`.
|
|
100
|
+
expect(true).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
vi.mock("@/lib/api-config", () => ({
|
|
4
|
+
API_BASE_URL: "http://localhost:3001/api",
|
|
5
|
+
PLATFORM_BASE_URL: "http://localhost:3001",
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock("@/lib/fetch-utils", () => ({
|
|
9
|
+
handleUnauthorized: vi.fn(),
|
|
10
|
+
UnauthorizedError: class extends Error {
|
|
11
|
+
constructor(msg = "Session expired") {
|
|
12
|
+
super(msg);
|
|
13
|
+
this.name = "UnauthorizedError";
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const mockListCapabilityMeta = vi.fn();
|
|
19
|
+
|
|
20
|
+
vi.mock("@/lib/trpc", () => ({
|
|
21
|
+
trpcVanilla: {
|
|
22
|
+
capabilities: {
|
|
23
|
+
listCapabilityMeta: { query: (...args: unknown[]) => mockListCapabilityMeta(...args) },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import { renderHook, waitFor } from "@testing-library/react";
|
|
29
|
+
import { useCapabilityMeta } from "@/hooks/use-capability-meta";
|
|
30
|
+
import type { CapabilityMetaEntry } from "@/lib/api";
|
|
31
|
+
import { fetchCapabilityMeta } from "@/lib/settings-api";
|
|
32
|
+
|
|
33
|
+
describe("fetchCapabilityMeta", () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
mockListCapabilityMeta.mockReset();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("returns metadata from tRPC procedure", async () => {
|
|
39
|
+
const mockData: CapabilityMetaEntry[] = [
|
|
40
|
+
{
|
|
41
|
+
capability: "transcription",
|
|
42
|
+
label: "Transcription",
|
|
43
|
+
description: "Powered by Whisper.",
|
|
44
|
+
pricing: "$0.006/min",
|
|
45
|
+
hostedProvider: "Whisper",
|
|
46
|
+
icon: "mic",
|
|
47
|
+
sortOrder: 0,
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
mockListCapabilityMeta.mockResolvedValue(mockData);
|
|
51
|
+
const result = await fetchCapabilityMeta();
|
|
52
|
+
expect(result).toEqual(mockData);
|
|
53
|
+
expect(mockListCapabilityMeta).toHaveBeenCalledOnce();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("throws on tRPC error (caller handles fallback)", async () => {
|
|
57
|
+
mockListCapabilityMeta.mockRejectedValue(new Error("Network error"));
|
|
58
|
+
await expect(fetchCapabilityMeta()).rejects.toThrow("Network error");
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe("useCapabilityMeta hook", () => {
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
mockListCapabilityMeta.mockReset();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns metadata from API when available", async () => {
|
|
68
|
+
const mockData: CapabilityMetaEntry[] = [
|
|
69
|
+
{
|
|
70
|
+
capability: "transcription",
|
|
71
|
+
label: "Transcription",
|
|
72
|
+
description: "Powered by Whisper.",
|
|
73
|
+
pricing: "$0.006/min",
|
|
74
|
+
hostedProvider: "Whisper",
|
|
75
|
+
icon: "mic",
|
|
76
|
+
sortOrder: 0,
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
capability: "text-gen",
|
|
80
|
+
label: "Text Generation",
|
|
81
|
+
description: "200+ models.",
|
|
82
|
+
pricing: "$0.002/1K tokens",
|
|
83
|
+
hostedProvider: "OpenRouter",
|
|
84
|
+
icon: "bot",
|
|
85
|
+
sortOrder: 2,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
mockListCapabilityMeta.mockResolvedValue(mockData);
|
|
89
|
+
|
|
90
|
+
const { result } = renderHook(() => useCapabilityMeta());
|
|
91
|
+
|
|
92
|
+
expect(result.current.loading).toBe(true);
|
|
93
|
+
await waitFor(() => expect(result.current.loading).toBe(false));
|
|
94
|
+
expect(result.current.meta).toEqual(mockData);
|
|
95
|
+
expect(result.current.error).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("falls back to defaults when API fails", async () => {
|
|
99
|
+
mockListCapabilityMeta.mockRejectedValue(new Error("fail"));
|
|
100
|
+
|
|
101
|
+
const { result } = renderHook(() => useCapabilityMeta());
|
|
102
|
+
|
|
103
|
+
await waitFor(() => expect(result.current.loading).toBe(false));
|
|
104
|
+
expect(result.current.error).toBe(true);
|
|
105
|
+
expect(result.current.meta.length).toBeGreaterThan(0);
|
|
106
|
+
expect(result.current.meta[0].capability).toBe("transcription");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("returns meta sorted by sortOrder", async () => {
|
|
110
|
+
const unsorted: CapabilityMetaEntry[] = [
|
|
111
|
+
{
|
|
112
|
+
capability: "b",
|
|
113
|
+
label: "B",
|
|
114
|
+
description: "",
|
|
115
|
+
pricing: "",
|
|
116
|
+
hostedProvider: "",
|
|
117
|
+
icon: "bot",
|
|
118
|
+
sortOrder: 2,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
capability: "a",
|
|
122
|
+
label: "A",
|
|
123
|
+
description: "",
|
|
124
|
+
pricing: "",
|
|
125
|
+
hostedProvider: "",
|
|
126
|
+
icon: "bot",
|
|
127
|
+
sortOrder: 1,
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
mockListCapabilityMeta.mockResolvedValue(unsorted);
|
|
131
|
+
|
|
132
|
+
const { result } = renderHook(() => useCapabilityMeta());
|
|
133
|
+
await waitFor(() => expect(result.current.loading).toBe(false));
|
|
134
|
+
expect(result.current.meta[0].capability).toBe("a");
|
|
135
|
+
expect(result.current.meta[1].capability).toBe("b");
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("getMeta returns entry for known capability and fallback for unknown", async () => {
|
|
139
|
+
const data: CapabilityMetaEntry[] = [
|
|
140
|
+
{
|
|
141
|
+
capability: "transcription",
|
|
142
|
+
label: "Transcription",
|
|
143
|
+
description: "Desc",
|
|
144
|
+
pricing: "$1",
|
|
145
|
+
hostedProvider: "W",
|
|
146
|
+
icon: "mic",
|
|
147
|
+
sortOrder: 0,
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
mockListCapabilityMeta.mockResolvedValue(data);
|
|
151
|
+
|
|
152
|
+
const { result } = renderHook(() => useCapabilityMeta());
|
|
153
|
+
await waitFor(() => expect(result.current.loading).toBe(false));
|
|
154
|
+
|
|
155
|
+
expect(result.current.getMeta("transcription").label).toBe("Transcription");
|
|
156
|
+
|
|
157
|
+
const unknown = result.current.getMeta("brand-new-cap");
|
|
158
|
+
expect(unknown.label).toBe("Brand New Cap");
|
|
159
|
+
expect(unknown.icon).toBe("sparkles");
|
|
160
|
+
});
|
|
161
|
+
});
|