@wopr-network/platform-ui-core 1.27.8 → 1.27.9
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/next.config.ts +1 -2
- package/package.json +17 -17
- package/src/__tests__/account-switcher.test.tsx +21 -20
- package/src/__tests__/activity-page.test.tsx +2 -6
- package/src/__tests__/add-payment-method-dialog.test.tsx +9 -32
- package/src/__tests__/admin-api.test.ts +1 -6
- package/src/__tests__/admin-gpu-api.test.ts +1 -3
- package/src/__tests__/admin-marketplace-api.test.ts +1 -4
- package/src/__tests__/admin-middleware.test.ts +76 -83
- package/src/__tests__/affiliate-dashboard.test.tsx +3 -3
- package/src/__tests__/api-401-redirect.test.ts +46 -9
- package/src/__tests__/api-client.test.ts +3 -5
- package/src/__tests__/api-config.test.ts +22 -42
- package/src/__tests__/api-fleet-resources.test.ts +1 -2
- package/src/__tests__/api-fleet-trpc.test.ts +2 -8
- package/src/__tests__/api-null-guards.test.ts +3 -1
- package/src/__tests__/audit-log-table-pagination.test.tsx +2 -6
- package/src/__tests__/auth-password-reset.test.tsx +7 -21
- package/src/__tests__/auth-redirect.test.tsx +8 -2
- package/src/__tests__/auth.test.tsx +25 -23
- package/src/__tests__/auto-topup-card.test.tsx +4 -12
- package/src/__tests__/backups-tab.test.tsx +3 -4
- package/src/__tests__/billing-layout-nav-hidden.test.tsx +5 -37
- package/src/__tests__/billing-payment-org-invoices.test.tsx +2 -18
- package/src/__tests__/billing.test.tsx +8 -39
- package/src/__tests__/bot-settings/resources-tab.test.tsx +1 -3
- package/src/__tests__/bot-settings/storage-tab.test.tsx +1 -3
- package/src/__tests__/bot-settings/vps-upgrade-card.test.tsx +1 -3
- package/src/__tests__/bot-settings-restart.test.tsx +1 -3
- package/src/__tests__/bot-settings.test.tsx +2 -6
- package/src/__tests__/brand.test.ts +6 -26
- package/src/__tests__/buy-credits-panel.test.tsx +1 -3
- package/src/__tests__/buy-crypto-credits-panel.test.tsx +101 -119
- package/src/__tests__/capability-conflicts.test.ts +2 -8
- package/src/__tests__/capability-resolver.test.tsx +2 -12
- package/src/__tests__/channel-wizard.test.tsx +4 -17
- package/src/__tests__/chat/chat-panel.test.tsx +1 -4
- package/src/__tests__/chat-store.test.ts +5 -15
- package/src/__tests__/command-center.test.tsx +10 -12
- package/src/__tests__/compliance-retention-edit.test.tsx +3 -6
- package/src/__tests__/confirmation-tracker.test.tsx +3 -18
- package/src/__tests__/coupon-input.test.tsx +1 -3
- package/src/__tests__/create-instance.test.tsx +1 -3
- package/src/__tests__/credit-balance.test.tsx +4 -12
- package/src/__tests__/credits.test.tsx +32 -85
- package/src/__tests__/email-verification-banner.test.tsx +2 -6
- package/src/__tests__/error-boundaries.test.tsx +0 -1
- package/src/__tests__/fetch-pricing.test.ts +2 -1
- package/src/__tests__/field-oauth.test.tsx +2 -6
- package/src/__tests__/fixtures/mock-manifests-data.js +1 -3
- package/src/__tests__/fixtures/mock-manifests.ts +2 -4
- package/src/__tests__/fleet-health-timestamp.test.tsx +1 -8
- package/src/__tests__/fleet-health-update.test.tsx +1 -8
- package/src/__tests__/gpu-dashboard.test.tsx +2 -6
- package/src/__tests__/instance-detail.test.tsx +3 -9
- package/src/__tests__/instance-list.test.tsx +1 -5
- package/src/__tests__/layout-snapshots.test.tsx +64 -11
- package/src/__tests__/marketplace-admin.test.tsx +2 -6
- package/src/__tests__/marketplace.test.tsx +11 -35
- package/src/__tests__/merge-api-rates.test.ts +1 -6
- package/src/__tests__/middleware.test.ts +32 -219
- package/src/__tests__/next-config-headers.test.ts +1 -3
- package/src/__tests__/notifications.test.tsx +4 -11
- package/src/__tests__/oauth-buttons.test.tsx +36 -59
- package/src/__tests__/oauth-error-mapping.test.tsx +2 -6
- package/src/__tests__/observability.test.tsx +23 -36
- package/src/__tests__/onboarding-page.test.tsx +4 -6
- package/src/__tests__/org-billing-api.test.tsx +1 -6
- package/src/__tests__/plugin-install-flow.test.tsx +28 -58
- package/src/__tests__/plugin-registry.test.tsx +3 -11
- package/src/__tests__/plugin-tool-sync.test.ts +1 -3
- package/src/__tests__/plugins-catalog-error.test.tsx +2 -6
- package/src/__tests__/plugins-toggle-race.test.tsx +3 -5
- package/src/__tests__/portfolio-chart.test.tsx +2 -6
- package/src/__tests__/promotion-form.test.tsx +2 -6
- package/src/__tests__/promotions-list.test.tsx +1 -3
- package/src/__tests__/provider-key-api.test.ts +2 -1
- package/src/__tests__/resend-verification-button.test.tsx +8 -24
- package/src/__tests__/secrets-audit-pagination.test.tsx +1 -3
- package/src/__tests__/settings.test.tsx +11 -21
- package/src/__tests__/setup-checklist.test.tsx +3 -9
- package/src/__tests__/setup.ts +25 -6
- package/src/__tests__/snapshot-api.test.ts +2 -1
- package/src/__tests__/step-superpowers.test.tsx +1 -3
- package/src/__tests__/tenant-context.test.tsx +1 -6
- package/src/__tests__/tenant-keys-api.test.ts +3 -4
- package/src/__tests__/tenant-table-pagination.test.tsx +2 -6
- package/src/__tests__/terminal-log-cleanup.test.tsx +0 -1
- package/src/__tests__/transaction-history.test.tsx +190 -238
- package/src/__tests__/trpc-types.test.ts +2 -6
- package/src/__tests__/use-chat.test.ts +1 -3
- package/src/__tests__/use-plugin-setup-chat-stale-closure.test.ts +1 -4
- package/src/__tests__/use-sidecar-bridge.test.tsx +105 -0
- package/src/__tests__/use-webmcp.test.ts +1 -3
- package/src/__tests__/validate-elevenlabs-key.test.ts +2 -1
- package/src/__tests__/verify-page.test.tsx +4 -13
- package/src/__tests__/verify-redirect.test.tsx +2 -6
- package/src/app/(auth)/error.tsx +1 -7
- package/src/app/(auth)/forgot-password/page.tsx +4 -18
- package/src/app/(auth)/login/page.tsx +5 -22
- package/src/app/(auth)/reset-password/page.tsx +2 -12
- package/src/app/(auth)/signup/page.tsx +10 -44
- package/src/app/(auth)/verify/page.tsx +47 -0
- package/src/app/(dashboard)/billing/credits/page.tsx +14 -67
- package/src/app/(dashboard)/billing/error.tsx +2 -10
- package/src/app/(dashboard)/billing/layout.tsx +12 -62
- package/src/app/(dashboard)/billing/payment/page.tsx +17 -68
- package/src/app/(dashboard)/billing/plans/page.tsx +3 -9
- package/src/app/(dashboard)/billing/usage/hosted/page.tsx +8 -25
- package/src/app/(dashboard)/billing/usage/page.tsx +63 -103
- package/src/app/(dashboard)/changesets/[id]/changeset-detail-client.tsx +9 -27
- package/src/app/(dashboard)/changesets/[id]/error.tsx +2 -6
- package/src/app/(dashboard)/changesets/error.tsx +1 -7
- package/src/app/(dashboard)/chat/page.tsx +2 -6
- package/src/app/(dashboard)/dashboard/network/page.tsx +5 -19
- package/src/app/(dashboard)/error.tsx +1 -7
- package/src/app/(dashboard)/layout.tsx +15 -36
- package/src/app/(dashboard)/marketplace/[plugin]/page.tsx +14 -51
- package/src/app/(dashboard)/marketplace/error.tsx +1 -7
- package/src/app/(dashboard)/marketplace/page.tsx +6 -27
- package/src/app/(dashboard)/not-found.tsx +2 -5
- package/src/app/(dashboard)/onboarding/page.tsx +5 -22
- package/src/app/(dashboard)/settings/account/page.tsx +1 -6
- package/src/app/(dashboard)/settings/activity/page.tsx +8 -34
- package/src/app/(dashboard)/settings/api-keys/page.tsx +15 -60
- package/src/app/(dashboard)/settings/brain/page.tsx +9 -31
- package/src/app/(dashboard)/settings/error.tsx +2 -10
- package/src/app/(dashboard)/settings/notifications/page.tsx +2 -6
- package/src/app/(dashboard)/settings/org/page.tsx +13 -56
- package/src/app/(dashboard)/settings/page.tsx +1 -0
- package/src/app/(dashboard)/settings/profile/page.tsx +126 -73
- package/src/app/(dashboard)/settings/providers/page.tsx +21 -78
- package/src/app/(dashboard)/settings/secrets/page.tsx +13 -58
- package/src/app/(dashboard)/settings/security/page.tsx +31 -111
- package/src/app/admin/email-templates/email-templates-client.tsx +15 -58
- package/src/app/admin/error.tsx +1 -7
- package/src/app/admin/fleet-updates/error.tsx +1 -7
- package/src/app/admin/fleet-updates/fleet-updates-client.tsx +10 -50
- package/src/app/admin/layout.tsx +4 -0
- package/src/app/admin/payment-methods/page.tsx +9 -38
- package/src/app/admin/products/error.tsx +2 -7
- package/src/app/admin/products/page.tsx +1 -4
- package/src/app/admin/promotions/[id]/page.tsx +9 -38
- package/src/app/admin/promotions/page.tsx +9 -36
- package/src/app/admin/rate-overrides/page.tsx +9 -45
- package/src/app/auth/callback/[provider]/page.tsx +1 -8
- package/src/app/auth/verify/page.tsx +9 -36
- package/src/app/channels/error.tsx +2 -10
- package/src/app/channels/layout.tsx +9 -0
- package/src/app/channels/page.tsx +8 -20
- package/src/app/channels/setup/[plugin]/page.tsx +3 -5
- package/src/app/error.tsx +1 -7
- package/src/app/fleet/error.tsx +1 -7
- package/src/app/fleet/layout.tsx +5 -0
- package/src/app/fleet/settings/page.tsx +1 -3
- package/src/app/global-error.tsx +2 -10
- package/src/app/globals.css +1 -4
- package/src/app/instances/[id]/instance-detail-client.tsx +51 -125
- package/src/app/instances/error.tsx +2 -10
- package/src/app/instances/instance-list-client.tsx +20 -69
- package/src/app/instances/layout.tsx +9 -0
- package/src/app/instances/new/create-instance-client.tsx +10 -31
- package/src/app/layout.tsx +2 -10
- package/src/app/not-found.tsx +1 -3
- package/src/app/page.tsx +1 -2
- package/src/app/plugins/error.tsx +2 -10
- package/src/app/plugins/layout.tsx +5 -0
- package/src/app/plugins/page.tsx +16 -48
- package/src/app/pricing/error.tsx +1 -7
- package/src/app/privacy/page.tsx +93 -150
- package/src/app/status/error.tsx +1 -7
- package/src/app/terms/page.tsx +89 -144
- package/src/components/account-switcher.tsx +25 -52
- package/src/components/admin/accounting-dashboard.tsx +1 -3
- package/src/components/admin/admin-guard.tsx +1 -3
- package/src/components/admin/admin-nav.tsx +1 -3
- package/src/components/admin/affiliate-dashboard.tsx +25 -94
- package/src/components/admin/audit-log-table.tsx +13 -49
- package/src/components/admin/billing-health-dashboard.tsx +7 -25
- package/src/components/admin/bulk-actions-bar.test.tsx +1 -7
- package/src/components/admin/bulk-actions-bar.tsx +1 -3
- package/src/components/admin/bulk-export-dialog.test.tsx +1 -7
- package/src/components/admin/bulk-export-dialog.tsx +6 -32
- package/src/components/admin/bulk-grant-dialog.test.tsx +2 -6
- package/src/components/admin/bulk-grant-dialog.tsx +4 -15
- package/src/components/admin/bulk-preview-dialog.tsx +3 -12
- package/src/components/admin/bulk-reactivate-dialog.tsx +1 -7
- package/src/components/admin/bulk-select-all-banner.tsx +1 -6
- package/src/components/admin/bulk-suspend-dialog.tsx +5 -12
- package/src/components/admin/bulk-undo-toast.tsx +1 -2
- package/src/components/admin/compliance-dashboard.tsx +31 -101
- package/src/components/admin/gpu-dashboard.tsx +21 -70
- package/src/components/admin/grant-credits-dialog.tsx +4 -17
- package/src/components/admin/incident-dashboard.tsx +10 -25
- package/src/components/admin/inference-dashboard.tsx +14 -54
- package/src/components/admin/marketplace-admin.tsx +18 -60
- package/src/components/admin/migrations-dashboard.tsx +9 -42
- package/src/components/admin/onboarding-dashboard.tsx +14 -64
- package/src/components/admin/pool-config-dashboard.tsx +4 -10
- package/src/components/admin/products/fleet-form.tsx +2 -11
- package/src/components/admin/products/nav-editor.tsx +3 -10
- package/src/components/admin/promotions/promotion-form.tsx +9 -42
- package/src/components/admin/roles-dashboard.tsx +7 -34
- package/src/components/admin/suspend-dialog.tsx +4 -11
- package/src/components/admin/tenant-notes-panel.tsx +1 -3
- package/src/components/admin/tenant-row-actions.tsx +4 -20
- package/src/components/admin/tenant-table.tsx +12 -49
- package/src/components/auth/auth-redirect.tsx +11 -3
- package/src/components/auth/email-verification-result-banner.tsx +1 -3
- package/src/components/auth/resend-verification-button.tsx +2 -10
- package/src/components/auth/wopr-wordmark.tsx +1 -3
- package/src/components/billing/add-payment-method-dialog.tsx +1 -2
- package/src/components/billing/affiliate-dashboard.tsx +4 -16
- package/src/components/billing/amount-selector.tsx +1 -3
- package/src/components/billing/auto-topup-card.tsx +2 -11
- package/src/components/billing/buy-credits-panel.tsx +4 -14
- package/src/components/billing/byok-callout.tsx +6 -8
- package/src/components/billing/confirmation-tracker.tsx +4 -14
- package/src/components/billing/credit-balance-badge.tsx +22 -0
- package/src/components/billing/credit-balance.tsx +3 -9
- package/src/components/billing/crypto-checkout.tsx +5 -24
- package/src/components/billing/degraded-state-banner.tsx +1 -3
- package/src/components/billing/deposit-view.tsx +301 -41
- package/src/components/billing/dividend-banner.tsx +1 -3
- package/src/components/billing/dividend-eligibility.tsx +3 -12
- package/src/components/billing/dividend-pool-stats.tsx +6 -20
- package/src/components/billing/first-dividend-dialog.tsx +2 -2
- package/src/components/billing/org-billing-page.tsx +8 -31
- package/src/components/billing/payment-method-picker.tsx +2 -10
- package/src/components/billing/suspension-banner.tsx +2 -7
- package/src/components/billing/transaction-history.tsx +10 -58
- package/src/components/billing/unified-checkout.tsx +547 -0
- package/src/components/bot-settings/backups-tab.tsx +9 -33
- package/src/components/bot-settings/bot-settings-client.tsx +32 -134
- package/src/components/bot-settings/resources-tab.tsx +2 -9
- package/src/components/bot-settings/storage-tab.tsx +19 -48
- package/src/components/bot-settings/vps-info-panel.tsx +3 -11
- package/src/components/bot-settings/vps-upgrade-card.tsx +3 -4
- package/src/components/brand-hydrator.tsx +13 -0
- package/src/components/channel-wizard/field-interactive.tsx +1 -3
- package/src/components/channel-wizard/field-qr.tsx +10 -39
- package/src/components/channel-wizard/step-renderer.tsx +5 -28
- package/src/components/channel-wizard/wizard.tsx +6 -31
- package/src/components/chat/chat-message.tsx +1 -4
- package/src/components/chat/chat-panel.tsx +4 -18
- package/src/components/chat/chat-widget.tsx +3 -14
- package/src/components/dashboard/command-center.tsx +15 -61
- package/src/components/fleet/update-settings-card.tsx +7 -23
- package/src/components/instance-update-banner.tsx +130 -0
- package/src/components/instances/friends-tab.test.tsx +2 -9
- package/src/components/instances/friends-tab.tsx +18 -74
- package/src/components/instances/update-available-badge.tsx +2 -11
- package/src/components/landing/hero.tsx +3 -9
- package/src/components/landing/landing-page.tsx +1 -3
- package/src/components/landing/portfolio-chart.tsx +4 -9
- package/src/components/landing/story-sections.tsx +1 -3
- package/src/components/landing/terminal-sequence.tsx +4 -17
- package/src/components/marketplace/empty-state.tsx +2 -6
- package/src/components/marketplace/first-visit-hero.tsx +1 -3
- package/src/components/marketplace/install-wizard.tsx +20 -77
- package/src/components/marketplace/marketplace-tabs.tsx +1 -4
- package/src/components/marketplace/plugin-card.tsx +2 -9
- package/src/components/marketplace/superpower-content.tsx +1 -3
- package/src/components/marketplace/terminal-search.tsx +2 -8
- package/src/components/oauth-buttons.tsx +29 -14
- package/src/components/observability/fleet-health.tsx +5 -18
- package/src/components/observability/health-overview.tsx +7 -20
- package/src/components/observability/logs-viewer.tsx +8 -32
- package/src/components/observability/metrics-dashboard.tsx +2 -15
- package/src/components/onboarding/fallback-setup.tsx +6 -25
- package/src/components/onboarding/setup-checklist.tsx +18 -51
- package/src/components/onboarding/step-superpowers.tsx +1 -4
- package/src/components/plugin-setup/setup-chat-panel.tsx +6 -22
- package/src/components/pricing/dividend-calculator.tsx +6 -12
- package/src/components/pricing/dividend-stats.tsx +5 -17
- package/src/components/pricing/pricing-page.tsx +17 -36
- package/src/components/settings/create-org-wizard.tsx +2 -5
- package/src/components/sidebar.tsx +7 -42
- package/src/components/sidecar-frame.tsx +78 -0
- package/src/components/status/status-page.tsx +6 -28
- package/src/components/ui/alert-dialog.tsx +8 -25
- package/src/components/ui/badge.tsx +2 -8
- package/src/components/ui/banner.tsx +1 -6
- package/src/components/ui/card.tsx +5 -24
- package/src/components/ui/checkbox.tsx +1 -5
- package/src/components/ui/collapsible.tsx +3 -8
- package/src/components/ui/dialog.tsx +4 -10
- package/src/components/ui/dropdown-menu.tsx +9 -18
- package/src/components/ui/form.tsx +2 -16
- package/src/components/ui/popover.tsx +3 -23
- package/src/components/ui/progress.tsx +1 -5
- package/src/components/ui/radio-group.tsx +3 -15
- package/src/components/ui/select.tsx +4 -17
- package/src/components/ui/sheet.tsx +5 -19
- package/src/components/ui/skeleton.tsx +1 -7
- package/src/components/ui/table.tsx +5 -22
- package/src/components/ui/tabs.tsx +3 -13
- package/src/components/ui/tooltip.tsx +1 -1
- package/src/components/unified-sidebar.tsx +493 -0
- package/src/hooks/__tests__/use-fleet-sse.test.ts +1 -4
- package/src/hooks/__tests__/use-save-queue.test.ts +2 -8
- package/src/hooks/use-credit-balance.ts +27 -0
- package/src/hooks/use-my-org-role.ts +1 -3
- package/src/hooks/use-plugin-registry.ts +8 -14
- package/src/hooks/use-plugin-setup-chat.ts +2 -5
- package/src/hooks/use-sidecar-bridge.tsx +148 -0
- package/src/hooks/use-webmcp.ts +1 -4
- package/src/lib/__tests__/admin-api.test.ts +1 -3
- package/src/lib/__tests__/api-bot-crud.test.ts +8 -18
- package/src/lib/__tests__/api-fetch.test.ts +4 -16
- package/src/lib/__tests__/org-billing-api.test.ts +1 -3
- package/src/lib/__tests__/pricing-data.test.ts +0 -8
- package/src/lib/__tests__/settings-api.test.ts +1 -3
- package/src/lib/admin-affiliate-api.ts +2 -7
- package/src/lib/admin-api.ts +6 -26
- package/src/lib/admin-incident-api.ts +11 -19
- package/src/lib/admin-marketplace-api.ts +1 -5
- package/src/lib/api-config.test.ts +5 -50
- package/src/lib/api.ts +143 -122
- package/src/lib/auth-client.ts +1 -2
- package/src/lib/bot-settings-data.ts +11 -36
- package/src/lib/brand-config.ts +56 -115
- package/src/lib/brand.ts +2 -15
- package/src/lib/chat/use-chat.ts +2 -7
- package/src/lib/cost-comparison-data.test.ts +1 -3
- package/src/lib/cost-comparison-data.ts +1 -4
- package/src/lib/errors.ts +1 -4
- package/src/lib/fetch-utils.test.ts +26 -9
- package/src/lib/fetch-utils.ts +40 -11
- package/src/lib/logger.ts +2 -0
- package/src/lib/marketplace-data.ts +3 -11
- package/src/lib/oauth-errors.ts +2 -4
- package/src/lib/onboarding-data.ts +3 -11
- package/src/lib/org-api.ts +2 -10
- package/src/lib/org-billing-api.ts +5 -19
- package/src/lib/plugin/tool-definitions.ts +1 -2
- package/src/lib/require-auth.ts +57 -0
- package/src/lib/settings-api.ts +1 -4
- package/src/lib/sidecar-routes.ts +43 -0
- package/src/lib/trpc-server.ts +49 -0
- package/src/lib/trpc-types.ts +4 -6
- package/src/lib/trpc.tsx +12 -4
- package/src/lib/validate-redirect-url.ts +1 -4
- package/src/lib/webmcp/marketplace-onboarding-tools.ts +6 -16
- package/src/lib/webmcp/register.ts +1 -4
- package/src/lib/webmcp/tools.ts +2 -9
- package/src/proxy.ts +35 -212
- package/src/types/missing-deps.d.ts +2 -8
- package/tsconfig.json +1 -8
- package/biome.json +0 -52
- package/src/__tests__/__snapshots__/layout-snapshots.test.tsx.snap +0 -741
- package/src/__tests__/billing-byok-callout.test.tsx +0 -76
- package/src/lib/__tests__/__snapshots__/pricing-data.test.ts.snap +0 -112
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Bot,
|
|
5
|
+
ChevronRight,
|
|
6
|
+
DollarSign,
|
|
7
|
+
FolderKanban,
|
|
8
|
+
Inbox,
|
|
9
|
+
LayoutDashboard,
|
|
10
|
+
LogOutIcon,
|
|
11
|
+
Plus,
|
|
12
|
+
PlusCircle,
|
|
13
|
+
Repeat,
|
|
14
|
+
SettingsIcon,
|
|
15
|
+
Shield,
|
|
16
|
+
Target,
|
|
17
|
+
UserIcon,
|
|
18
|
+
Users,
|
|
19
|
+
Wallet,
|
|
20
|
+
Zap,
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import Image from "next/image";
|
|
23
|
+
import { usePathname, useRouter } from "next/navigation";
|
|
24
|
+
import { useState } from "react";
|
|
25
|
+
import { CreditBalanceBadge } from "@/components/billing/credit-balance-badge";
|
|
26
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
|
27
|
+
import {
|
|
28
|
+
DropdownMenu,
|
|
29
|
+
DropdownMenuContent,
|
|
30
|
+
DropdownMenuItem,
|
|
31
|
+
DropdownMenuLabel,
|
|
32
|
+
DropdownMenuSeparator,
|
|
33
|
+
DropdownMenuTrigger,
|
|
34
|
+
} from "@/components/ui/dropdown-menu";
|
|
35
|
+
import { Skeleton } from "@/components/ui/skeleton";
|
|
36
|
+
import { type SidebarAgent, type SidebarProject, useSidecarBridge } from "@/hooks/use-sidecar-bridge";
|
|
37
|
+
import { signOut, useSession } from "@/lib/auth-client";
|
|
38
|
+
import { productName } from "@/lib/brand-config";
|
|
39
|
+
import { getRouteType } from "@/lib/sidecar-routes";
|
|
40
|
+
import { cn } from "@/lib/utils";
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// Helpers
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
function getInitials(name: string): string {
|
|
47
|
+
return name
|
|
48
|
+
.split(" ")
|
|
49
|
+
.map((part) => part[0])
|
|
50
|
+
.filter(Boolean)
|
|
51
|
+
.slice(0, 2)
|
|
52
|
+
.join("")
|
|
53
|
+
.toUpperCase();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isActive(
|
|
57
|
+
href: string,
|
|
58
|
+
type: "iframe" | "native",
|
|
59
|
+
currentSidecarPath: string | null,
|
|
60
|
+
pathname: string,
|
|
61
|
+
): boolean {
|
|
62
|
+
if (type === "iframe") {
|
|
63
|
+
if (!currentSidecarPath) return false;
|
|
64
|
+
if (href === currentSidecarPath) return true;
|
|
65
|
+
return currentSidecarPath.startsWith(`${href}/`);
|
|
66
|
+
}
|
|
67
|
+
// Native routes
|
|
68
|
+
if (href === pathname) return true;
|
|
69
|
+
return pathname.startsWith(`${href}/`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Section header
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
function SectionLabel({ children }: { children: React.ReactNode }) {
|
|
77
|
+
return (
|
|
78
|
+
<div className="px-3 pt-4 pb-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60">
|
|
79
|
+
{children}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Nav item
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
interface NavItemProps {
|
|
89
|
+
icon: React.ElementType;
|
|
90
|
+
label: string;
|
|
91
|
+
href: string;
|
|
92
|
+
badge?: React.ReactNode;
|
|
93
|
+
onClick: () => void;
|
|
94
|
+
active: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function NavItem({ icon: Icon, label, href, badge, onClick, active }: NavItemProps) {
|
|
98
|
+
return (
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={onClick}
|
|
102
|
+
data-href={href}
|
|
103
|
+
className={cn(
|
|
104
|
+
"flex w-full items-center justify-between rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-foreground",
|
|
105
|
+
active ? "bg-terminal/5 border-l-2 border-terminal text-terminal" : "text-muted-foreground",
|
|
106
|
+
)}
|
|
107
|
+
>
|
|
108
|
+
<span className="flex items-center gap-2.5">
|
|
109
|
+
<Icon className="size-4 shrink-0 opacity-70" />
|
|
110
|
+
{label}
|
|
111
|
+
</span>
|
|
112
|
+
{badge}
|
|
113
|
+
</button>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Collapsible list section (Projects / Agents)
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
interface CollapsibleSectionProps {
|
|
122
|
+
title: string;
|
|
123
|
+
onAdd: () => void;
|
|
124
|
+
children: React.ReactNode;
|
|
125
|
+
defaultOpen?: boolean;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function CollapsibleSection({ title, onAdd, children, defaultOpen = true }: CollapsibleSectionProps) {
|
|
129
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
130
|
+
return (
|
|
131
|
+
<Collapsible open={open} onOpenChange={setOpen}>
|
|
132
|
+
<div className="flex items-center justify-between px-3 pt-4 pb-1">
|
|
133
|
+
<CollapsibleTrigger className="flex items-center gap-1 text-[11px] font-semibold uppercase tracking-wider text-muted-foreground/60 hover:text-muted-foreground transition-colors">
|
|
134
|
+
<ChevronRight className={cn("size-3 transition-transform", open && "rotate-90")} />
|
|
135
|
+
{title}
|
|
136
|
+
</CollapsibleTrigger>
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
onClick={(e) => {
|
|
140
|
+
e.stopPropagation();
|
|
141
|
+
onAdd();
|
|
142
|
+
}}
|
|
143
|
+
className="text-muted-foreground/60 hover:text-muted-foreground transition-colors"
|
|
144
|
+
>
|
|
145
|
+
<Plus className="size-3.5" />
|
|
146
|
+
</button>
|
|
147
|
+
</div>
|
|
148
|
+
<CollapsibleContent>{children}</CollapsibleContent>
|
|
149
|
+
</Collapsible>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// Agent status indicator
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
function AgentStatusBadge({ agent }: { agent: SidebarAgent }) {
|
|
158
|
+
if (agent.pauseReason === "budget") {
|
|
159
|
+
return (
|
|
160
|
+
<span className="flex items-center gap-1 text-[10px] font-mono text-amber-500">
|
|
161
|
+
<DollarSign className="size-3" />
|
|
162
|
+
</span>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
if (agent.liveRun) {
|
|
166
|
+
return (
|
|
167
|
+
<span className="flex items-center gap-1 text-[10px] font-mono text-blue-400">
|
|
168
|
+
<span className="relative flex size-2">
|
|
169
|
+
<span className="absolute inline-flex size-full animate-ping rounded-full bg-blue-400 opacity-75" />
|
|
170
|
+
<span className="relative inline-flex size-2 rounded-full bg-blue-500" />
|
|
171
|
+
</span>
|
|
172
|
+
{agent.liveRunCount > 1 ? agent.liveRunCount : "live"}
|
|
173
|
+
</span>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
if (agent.status === "idle") {
|
|
177
|
+
return <span className="text-[10px] font-mono text-muted-foreground/50">idle</span>;
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Badge helpers
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
function CountBadge({ count }: { count: number }) {
|
|
187
|
+
if (count <= 0) return null;
|
|
188
|
+
return (
|
|
189
|
+
<span className="flex size-5 items-center justify-center rounded-full bg-terminal/10 text-[10px] font-mono font-semibold text-terminal">
|
|
190
|
+
{count > 99 ? "99+" : count}
|
|
191
|
+
</span>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Main export
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
export function UnifiedSidebarContent({ onNavigate }: { onNavigate?: () => void }) {
|
|
200
|
+
const pathname = usePathname();
|
|
201
|
+
const router = useRouter();
|
|
202
|
+
const { data: session, isPending } = useSession();
|
|
203
|
+
const { sidebarData, navigate: sidecarNavigate, command, currentSidecarPath } = useSidecarBridge();
|
|
204
|
+
|
|
205
|
+
const user = session?.user;
|
|
206
|
+
const isAdmin = (user as { role?: string } | undefined)?.role === "platform_admin";
|
|
207
|
+
|
|
208
|
+
// Navigation handler — iframe routes go to sidecar, native routes use router
|
|
209
|
+
function handleNav(href: string) {
|
|
210
|
+
const type = getRouteType(href);
|
|
211
|
+
if (type === "iframe") {
|
|
212
|
+
sidecarNavigate(href);
|
|
213
|
+
window.history.pushState(null, "", href);
|
|
214
|
+
} else {
|
|
215
|
+
router.push(href);
|
|
216
|
+
}
|
|
217
|
+
onNavigate?.();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function handleCommand(action: string) {
|
|
221
|
+
command(action);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function checkActive(href: string): boolean {
|
|
225
|
+
const type = getRouteType(href);
|
|
226
|
+
return isActive(href, type, currentSidecarPath, pathname);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function handleSignOut() {
|
|
230
|
+
try {
|
|
231
|
+
await signOut();
|
|
232
|
+
} catch {
|
|
233
|
+
// Continue to redirect even if signOut throws
|
|
234
|
+
}
|
|
235
|
+
router.push("/login");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Dynamic data from sidecar
|
|
239
|
+
const projects: SidebarProject[] = sidebarData?.projects ?? [];
|
|
240
|
+
const agents: SidebarAgent[] = sidebarData?.agents ?? [];
|
|
241
|
+
const inboxBadge = sidebarData?.inboxBadge ?? 0;
|
|
242
|
+
const liveRunCount = sidebarData?.liveRunCount ?? 0;
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<div data-slot="sidebar" className="flex h-full flex-col">
|
|
246
|
+
{/* Brand header */}
|
|
247
|
+
<div className="flex h-14 items-center border-b border-sidebar-border px-6">
|
|
248
|
+
<span
|
|
249
|
+
className="text-lg font-semibold tracking-tight text-terminal"
|
|
250
|
+
style={{
|
|
251
|
+
textShadow: "0 0 12px var(--terminal-glow, rgba(0, 255, 65, 0.4))",
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
{productName()}
|
|
255
|
+
</span>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
{/* Scrollable nav body */}
|
|
259
|
+
<nav className="flex-1 overflow-y-auto px-3 py-2">
|
|
260
|
+
{/* Quick action */}
|
|
261
|
+
<div className="py-1">
|
|
262
|
+
<button
|
|
263
|
+
type="button"
|
|
264
|
+
onClick={() => handleCommand("openNewIssue")}
|
|
265
|
+
className="flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-sidebar-accent hover:text-foreground"
|
|
266
|
+
>
|
|
267
|
+
<PlusCircle className="size-4 shrink-0 opacity-70" />
|
|
268
|
+
New Issue
|
|
269
|
+
</button>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
{/* Primary nav */}
|
|
273
|
+
<div className="space-y-0.5 py-1">
|
|
274
|
+
<NavItem
|
|
275
|
+
icon={LayoutDashboard}
|
|
276
|
+
label="Dashboard"
|
|
277
|
+
href="/dashboard"
|
|
278
|
+
badge={<CountBadge count={liveRunCount} />}
|
|
279
|
+
onClick={() => handleNav("/dashboard")}
|
|
280
|
+
active={checkActive("/dashboard")}
|
|
281
|
+
/>
|
|
282
|
+
<NavItem
|
|
283
|
+
icon={Inbox}
|
|
284
|
+
label="Inbox"
|
|
285
|
+
href="/inbox"
|
|
286
|
+
badge={<CountBadge count={inboxBadge} />}
|
|
287
|
+
onClick={() => handleNav("/inbox")}
|
|
288
|
+
active={checkActive("/inbox")}
|
|
289
|
+
/>
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
{/* Work section */}
|
|
293
|
+
<SectionLabel>Work</SectionLabel>
|
|
294
|
+
<div className="space-y-0.5">
|
|
295
|
+
<NavItem
|
|
296
|
+
icon={FolderKanban}
|
|
297
|
+
label="Issues"
|
|
298
|
+
href="/issues"
|
|
299
|
+
onClick={() => handleNav("/issues")}
|
|
300
|
+
active={checkActive("/issues")}
|
|
301
|
+
/>
|
|
302
|
+
<NavItem
|
|
303
|
+
icon={Repeat}
|
|
304
|
+
label="Routines"
|
|
305
|
+
href="/routines"
|
|
306
|
+
onClick={() => handleNav("/routines")}
|
|
307
|
+
active={checkActive("/routines")}
|
|
308
|
+
/>
|
|
309
|
+
<NavItem
|
|
310
|
+
icon={Target}
|
|
311
|
+
label="Goals"
|
|
312
|
+
href="/goals"
|
|
313
|
+
onClick={() => handleNav("/goals")}
|
|
314
|
+
active={checkActive("/goals")}
|
|
315
|
+
/>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
{/* Projects — hide the whole section when the sidecar tells us the
|
|
319
|
+
feature is off (hosted mode). When showProjects is undefined
|
|
320
|
+
(older sidecar) or true, render normally including the
|
|
321
|
+
empty-state CTA so standalone users with zero projects still
|
|
322
|
+
have an entry point. */}
|
|
323
|
+
{sidebarData?.showProjects !== false && (
|
|
324
|
+
<CollapsibleSection title="Projects" onAdd={() => handleCommand("openNewProject")}>
|
|
325
|
+
<div className="space-y-0.5 pl-1">
|
|
326
|
+
{projects.map((project) => {
|
|
327
|
+
const href = `/projects/${project.urlKey}/issues`;
|
|
328
|
+
return (
|
|
329
|
+
<button
|
|
330
|
+
key={project.id}
|
|
331
|
+
type="button"
|
|
332
|
+
onClick={() => handleNav(href)}
|
|
333
|
+
className={cn(
|
|
334
|
+
"flex w-full items-center gap-2.5 rounded-md px-3 py-1.5 text-sm transition-colors hover:bg-sidebar-accent hover:text-foreground",
|
|
335
|
+
checkActive(href)
|
|
336
|
+
? "bg-terminal/5 border-l-2 border-terminal text-terminal"
|
|
337
|
+
: "text-muted-foreground",
|
|
338
|
+
)}
|
|
339
|
+
>
|
|
340
|
+
<span
|
|
341
|
+
className="size-2.5 rounded-full shrink-0"
|
|
342
|
+
style={{
|
|
343
|
+
backgroundColor: project.color ?? "var(--muted-foreground)",
|
|
344
|
+
}}
|
|
345
|
+
/>
|
|
346
|
+
<span className="truncate">{project.name}</span>
|
|
347
|
+
</button>
|
|
348
|
+
);
|
|
349
|
+
})}
|
|
350
|
+
{projects.length === 0 && (
|
|
351
|
+
<span className="block px-3 py-1.5 text-xs text-muted-foreground/50">No projects yet</span>
|
|
352
|
+
)}
|
|
353
|
+
</div>
|
|
354
|
+
</CollapsibleSection>
|
|
355
|
+
)}
|
|
356
|
+
|
|
357
|
+
{/* Agents — collapsible, dynamic from sidecar */}
|
|
358
|
+
<CollapsibleSection title="Agents" onAdd={() => handleCommand("openNewAgent")}>
|
|
359
|
+
<div className="space-y-0.5 pl-1">
|
|
360
|
+
{agents.map((agent) => {
|
|
361
|
+
const href = `/agents/${agent.id}`;
|
|
362
|
+
return (
|
|
363
|
+
<button
|
|
364
|
+
key={agent.id}
|
|
365
|
+
type="button"
|
|
366
|
+
onClick={() => handleNav(href)}
|
|
367
|
+
className={cn(
|
|
368
|
+
"flex w-full items-center justify-between rounded-md px-3 py-1.5 text-sm transition-colors hover:bg-sidebar-accent hover:text-foreground",
|
|
369
|
+
checkActive(href)
|
|
370
|
+
? "bg-terminal/5 border-l-2 border-terminal text-terminal"
|
|
371
|
+
: "text-muted-foreground",
|
|
372
|
+
)}
|
|
373
|
+
>
|
|
374
|
+
<span className="flex items-center gap-2.5 truncate">
|
|
375
|
+
<Bot className="size-4 shrink-0 opacity-70" />
|
|
376
|
+
<span className="truncate">{agent.name}</span>
|
|
377
|
+
</span>
|
|
378
|
+
<AgentStatusBadge agent={agent} />
|
|
379
|
+
</button>
|
|
380
|
+
);
|
|
381
|
+
})}
|
|
382
|
+
{agents.length === 0 && (
|
|
383
|
+
<span className="block px-3 py-1.5 text-xs text-muted-foreground/50">No agents yet</span>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
</CollapsibleSection>
|
|
387
|
+
|
|
388
|
+
{/* Company section */}
|
|
389
|
+
<SectionLabel>Company</SectionLabel>
|
|
390
|
+
<div className="space-y-0.5">
|
|
391
|
+
<NavItem
|
|
392
|
+
icon={Users}
|
|
393
|
+
label="Org"
|
|
394
|
+
href="/org"
|
|
395
|
+
onClick={() => handleNav("/org")}
|
|
396
|
+
active={checkActive("/org")}
|
|
397
|
+
/>
|
|
398
|
+
<NavItem
|
|
399
|
+
icon={Zap}
|
|
400
|
+
label="Skills"
|
|
401
|
+
href="/skills"
|
|
402
|
+
onClick={() => handleNav("/skills")}
|
|
403
|
+
active={checkActive("/skills")}
|
|
404
|
+
/>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
{/* Account section */}
|
|
408
|
+
<SectionLabel>Account</SectionLabel>
|
|
409
|
+
<div className="space-y-0.5">
|
|
410
|
+
<NavItem
|
|
411
|
+
icon={Wallet}
|
|
412
|
+
label="Credits"
|
|
413
|
+
href="/billing/credits"
|
|
414
|
+
badge={<CreditBalanceBadge />}
|
|
415
|
+
onClick={() => handleNav("/billing/credits")}
|
|
416
|
+
active={checkActive("/billing/credits")}
|
|
417
|
+
/>
|
|
418
|
+
<NavItem
|
|
419
|
+
icon={SettingsIcon}
|
|
420
|
+
label="Settings"
|
|
421
|
+
href="/settings"
|
|
422
|
+
onClick={() => handleNav("/settings")}
|
|
423
|
+
active={checkActive("/settings")}
|
|
424
|
+
/>
|
|
425
|
+
{isAdmin && (
|
|
426
|
+
<NavItem
|
|
427
|
+
icon={Shield}
|
|
428
|
+
label="Admin"
|
|
429
|
+
href="/admin"
|
|
430
|
+
onClick={() => handleNav("/admin")}
|
|
431
|
+
active={checkActive("/admin")}
|
|
432
|
+
/>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
</nav>
|
|
436
|
+
|
|
437
|
+
{/* User footer */}
|
|
438
|
+
<div className="border-t border-sidebar-border px-3 py-3">
|
|
439
|
+
{isPending ? (
|
|
440
|
+
<div className="flex items-center gap-3 px-3 py-2">
|
|
441
|
+
<Skeleton className="size-8 rounded-full" />
|
|
442
|
+
<Skeleton className="h-4 w-24" />
|
|
443
|
+
</div>
|
|
444
|
+
) : user ? (
|
|
445
|
+
<DropdownMenu>
|
|
446
|
+
<DropdownMenuTrigger className="flex w-full items-center gap-3 rounded-md px-3 py-2 text-sm font-medium transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground outline-none">
|
|
447
|
+
{user.image ? (
|
|
448
|
+
<Image
|
|
449
|
+
src={user.image}
|
|
450
|
+
alt={user.name ?? "User avatar"}
|
|
451
|
+
width={32}
|
|
452
|
+
height={32}
|
|
453
|
+
className="size-8 rounded-full object-cover"
|
|
454
|
+
/>
|
|
455
|
+
) : (
|
|
456
|
+
<span className="flex size-8 items-center justify-center rounded-full bg-sidebar-accent text-xs font-semibold ring-1 ring-terminal/20">
|
|
457
|
+
{user.name?.trim() ? getInitials(user.name) : <UserIcon className="size-4" />}
|
|
458
|
+
</span>
|
|
459
|
+
)}
|
|
460
|
+
<span className="truncate">{user.name ?? user.email}</span>
|
|
461
|
+
</DropdownMenuTrigger>
|
|
462
|
+
<DropdownMenuContent side="top" align="start" className="w-56">
|
|
463
|
+
<DropdownMenuLabel className="font-normal">
|
|
464
|
+
<div className="flex flex-col gap-1">
|
|
465
|
+
{user.name && <span className="text-sm font-medium">{user.name}</span>}
|
|
466
|
+
{user.email && <span className="text-xs text-muted-foreground">{user.email}</span>}
|
|
467
|
+
</div>
|
|
468
|
+
</DropdownMenuLabel>
|
|
469
|
+
<DropdownMenuSeparator />
|
|
470
|
+
<DropdownMenuItem onClick={() => router.push("/settings")}>
|
|
471
|
+
<SettingsIcon />
|
|
472
|
+
Settings
|
|
473
|
+
</DropdownMenuItem>
|
|
474
|
+
<DropdownMenuSeparator />
|
|
475
|
+
<DropdownMenuItem onClick={handleSignOut}>
|
|
476
|
+
<LogOutIcon />
|
|
477
|
+
Sign out
|
|
478
|
+
</DropdownMenuItem>
|
|
479
|
+
</DropdownMenuContent>
|
|
480
|
+
</DropdownMenu>
|
|
481
|
+
) : (
|
|
482
|
+
<button
|
|
483
|
+
type="button"
|
|
484
|
+
onClick={() => router.push("/login")}
|
|
485
|
+
className="flex items-center rounded-md px-3 py-2 text-sm font-medium text-sidebar-foreground/70 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
|
|
486
|
+
>
|
|
487
|
+
Sign in
|
|
488
|
+
</button>
|
|
489
|
+
)}
|
|
490
|
+
</div>
|
|
491
|
+
</div>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
@@ -140,10 +140,7 @@ describe("useFleetSSE", () => {
|
|
|
140
140
|
rerender({ cb: second });
|
|
141
141
|
|
|
142
142
|
act(() => {
|
|
143
|
-
latestES().emit(
|
|
144
|
-
"fleet",
|
|
145
|
-
JSON.stringify({ type: "bot.stopped", botId: "bot-2", timestamp: "t" }),
|
|
146
|
-
);
|
|
143
|
+
latestES().emit("fleet", JSON.stringify({ type: "bot.stopped", botId: "bot-2", timestamp: "t" }));
|
|
147
144
|
});
|
|
148
145
|
|
|
149
146
|
expect(first).not.toHaveBeenCalled();
|
|
@@ -74,10 +74,7 @@ describe("useSaveQueue", () => {
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
it("sets error on save failure and clears it on next successful save", async () => {
|
|
77
|
-
const saveFn = vi
|
|
78
|
-
.fn()
|
|
79
|
-
.mockRejectedValueOnce(new Error("network error"))
|
|
80
|
-
.mockResolvedValueOnce(undefined);
|
|
77
|
+
const saveFn = vi.fn().mockRejectedValueOnce(new Error("network error")).mockResolvedValueOnce(undefined);
|
|
81
78
|
|
|
82
79
|
const { result } = renderHook(() => useSaveQueue(saveFn));
|
|
83
80
|
|
|
@@ -102,10 +99,7 @@ describe("useSaveQueue", () => {
|
|
|
102
99
|
resolveSecond = resolve;
|
|
103
100
|
});
|
|
104
101
|
|
|
105
|
-
const saveFn = vi
|
|
106
|
-
.fn()
|
|
107
|
-
.mockRejectedValueOnce(new Error("oops"))
|
|
108
|
-
.mockReturnValueOnce(secondPromise);
|
|
102
|
+
const saveFn = vi.fn().mockRejectedValueOnce(new Error("oops")).mockReturnValueOnce(secondPromise);
|
|
109
103
|
|
|
110
104
|
const { result } = renderHook(() => useSaveQueue(saveFn));
|
|
111
105
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { trpc } from "@/lib/trpc";
|
|
4
|
+
|
|
5
|
+
/** Shared credit balance hook — backed by tRPC React Query.
|
|
6
|
+
* Invalidating queryKey [["billing"]] updates every consumer. */
|
|
7
|
+
export function useCreditBalance() {
|
|
8
|
+
const { data: raw, isLoading, error, refetch } = trpc.billing.creditsBalance.useQuery({});
|
|
9
|
+
|
|
10
|
+
const balance =
|
|
11
|
+
raw != null
|
|
12
|
+
? ((raw as { balance_credits?: number; balance_cents?: number }).balance_credits ??
|
|
13
|
+
(raw as { balance_cents?: number }).balance_cents ??
|
|
14
|
+
0) / 100
|
|
15
|
+
: null;
|
|
16
|
+
|
|
17
|
+
const dailyBurn =
|
|
18
|
+
raw != null
|
|
19
|
+
? ((raw as { daily_burn_credits?: number; daily_burn_cents?: number }).daily_burn_credits ??
|
|
20
|
+
(raw as { daily_burn_cents?: number }).daily_burn_cents ??
|
|
21
|
+
0) / 100
|
|
22
|
+
: null;
|
|
23
|
+
|
|
24
|
+
const runway = raw != null ? ((raw as { runway_days?: number | null }).runway_days ?? null) : null;
|
|
25
|
+
|
|
26
|
+
return { balance, dailyBurn, runway, isLoading, error, refetch };
|
|
27
|
+
}
|
|
@@ -15,9 +15,7 @@ export function useMyOrgRole(org: Organization | null): OrgMember["role"] | null
|
|
|
15
15
|
return useMemo(() => {
|
|
16
16
|
if (!org || !session?.user?.id) return null;
|
|
17
17
|
|
|
18
|
-
const me = org.members?.find(
|
|
19
|
-
(m: OrgMember) => m.userId === session.user.id || m.email === session.user.email,
|
|
20
|
-
);
|
|
18
|
+
const me = org.members?.find((m: OrgMember) => m.userId === session.user.id || m.email === session.user.email);
|
|
21
19
|
return me?.role ?? null;
|
|
22
20
|
}, [org, session?.user?.id, session?.user?.email]);
|
|
23
21
|
}
|
|
@@ -29,14 +29,14 @@ import {
|
|
|
29
29
|
|
|
30
30
|
// Re-export types so consumers only need one import source
|
|
31
31
|
export type {
|
|
32
|
-
PluginOption,
|
|
33
|
-
PluginCategory,
|
|
34
|
-
Superpower,
|
|
35
|
-
Personality,
|
|
36
|
-
Preset,
|
|
37
|
-
ModelOption,
|
|
38
32
|
ByokProvider,
|
|
33
|
+
ModelOption,
|
|
39
34
|
OnboardingConfigField,
|
|
35
|
+
Personality,
|
|
36
|
+
PluginCategory,
|
|
37
|
+
PluginOption,
|
|
38
|
+
Preset,
|
|
39
|
+
Superpower,
|
|
40
40
|
};
|
|
41
41
|
|
|
42
42
|
/**
|
|
@@ -102,11 +102,7 @@ export interface PluginRegistry {
|
|
|
102
102
|
selectedPlugins: string[],
|
|
103
103
|
) => OnboardingConfigField[];
|
|
104
104
|
/** Resolve plugin dependencies */
|
|
105
|
-
resolveDependencies: (
|
|
106
|
-
selectedChannels: string[],
|
|
107
|
-
selectedProviders: string[],
|
|
108
|
-
selectedPlugins: string[],
|
|
109
|
-
) => string[];
|
|
105
|
+
resolveDependencies: (selectedChannels: string[], selectedProviders: string[], selectedPlugins: string[]) => string[];
|
|
110
106
|
/** Validate a single config field value */
|
|
111
107
|
validateField: (field: OnboardingConfigField, value: string) => string | null;
|
|
112
108
|
}
|
|
@@ -224,9 +220,7 @@ export function usePluginRegistry(): PluginRegistry {
|
|
|
224
220
|
pluginOptions,
|
|
225
221
|
getAllPlugins: () => [...channels, ...providers, ...categories.flatMap((c) => c.plugins)],
|
|
226
222
|
getPluginById: (id: string) =>
|
|
227
|
-
[...channels, ...providers, ...categories.flatMap((c) => c.plugins)].find(
|
|
228
|
-
(p) => p.id === id,
|
|
229
|
-
),
|
|
223
|
+
[...channels, ...providers, ...categories.flatMap((c) => c.plugins)].find((p) => p.id === id),
|
|
230
224
|
collectConfigFields,
|
|
231
225
|
resolveDependencies,
|
|
232
226
|
validateField,
|
|
@@ -37,9 +37,7 @@ type SseEvent =
|
|
|
37
37
|
| { type: "tool_call"; tool: string; args: Record<string, unknown> }
|
|
38
38
|
| { type: "typing" };
|
|
39
39
|
|
|
40
|
-
export function usePluginSetupChat(
|
|
41
|
-
onComplete?: (pluginId: string) => void,
|
|
42
|
-
): UsePluginSetupChatReturn {
|
|
40
|
+
export function usePluginSetupChat(onComplete?: (pluginId: string) => void): UsePluginSetupChatReturn {
|
|
43
41
|
const [state, setState] = useState<PluginSetupState>(initialState);
|
|
44
42
|
const abortRef = useRef<AbortController | null>(null);
|
|
45
43
|
const botIdRef = useRef<string | null>(null);
|
|
@@ -138,8 +136,7 @@ export function usePluginSetupChat(
|
|
|
138
136
|
setState((s) => ({ ...s, isComplete: true }));
|
|
139
137
|
onComplete?.(pluginId);
|
|
140
138
|
} else if (event.type === "tool_call" && event.tool === "setup.rollback") {
|
|
141
|
-
const reason =
|
|
142
|
-
typeof event.args?.reason === "string" ? event.args.reason : "Setup failed";
|
|
139
|
+
const reason = typeof event.args?.reason === "string" ? event.args.reason : "Setup failed";
|
|
143
140
|
setState((s) => ({
|
|
144
141
|
...s,
|
|
145
142
|
messages: [
|