@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
|
@@ -164,20 +164,14 @@ export function FieldQR({ field, value: _value, onChange, error, botId }: FieldQ
|
|
|
164
164
|
{/* bg-white is intentional -- QR codes require white background for scanability */}
|
|
165
165
|
<div className="h-40 w-40 rounded-sm bg-white p-3 min-[375px]:h-48 min-[375px]:w-48">
|
|
166
166
|
{/* biome-ignore lint/performance/noImgElement: QR PNG is a base64 data URI from the API — next/image does not support data: URIs (cannot optimize, resize, or lazy-load inline blobs). Raw <img> is the only option here. */}
|
|
167
|
-
<img
|
|
168
|
-
src={qrPng}
|
|
169
|
-
alt="Scan this QR code with your phone"
|
|
170
|
-
className="h-full w-full"
|
|
171
|
-
/>
|
|
167
|
+
<img src={qrPng} alt="Scan this QR code with your phone" className="h-full w-full" />
|
|
172
168
|
</div>
|
|
173
169
|
</div>
|
|
174
170
|
|
|
175
171
|
{/* Scanning indicator */}
|
|
176
172
|
<div className="flex items-center gap-2">
|
|
177
173
|
<div className="h-1.5 w-1.5 rounded-full bg-terminal motion-safe:animate-[pulse-dot_2s_ease-in-out_infinite]" />
|
|
178
|
-
<span className="text-xs font-medium uppercase tracking-wider text-terminal">
|
|
179
|
-
WAITING FOR SCAN
|
|
180
|
-
</span>
|
|
174
|
+
<span className="text-xs font-medium uppercase tracking-wider text-terminal">WAITING FOR SCAN</span>
|
|
181
175
|
</div>
|
|
182
176
|
|
|
183
177
|
{/* Instructions */}
|
|
@@ -186,9 +180,7 @@ export function FieldQR({ field, value: _value, onChange, error, botId }: FieldQ
|
|
|
186
180
|
</p>
|
|
187
181
|
|
|
188
182
|
{/* Countdown */}
|
|
189
|
-
<span className={`text-xs tabular-nums ${countdownColor()}`}>
|
|
190
|
-
Expires in {secondsLeft}s
|
|
191
|
-
</span>
|
|
183
|
+
<span className={`text-xs tabular-nums ${countdownColor()}`}>Expires in {secondsLeft}s</span>
|
|
192
184
|
</div>
|
|
193
185
|
)}
|
|
194
186
|
|
|
@@ -215,21 +207,13 @@ export function FieldQR({ field, value: _value, onChange, error, botId }: FieldQ
|
|
|
215
207
|
</div>
|
|
216
208
|
</div>
|
|
217
209
|
|
|
218
|
-
<span className="text-xs font-medium uppercase tracking-wider text-amber-500">
|
|
219
|
-
QR CODE EXPIRED
|
|
220
|
-
</span>
|
|
210
|
+
<span className="text-xs font-medium uppercase tracking-wider text-amber-500">QR CODE EXPIRED</span>
|
|
221
211
|
|
|
222
212
|
<p className="text-center text-sm text-muted-foreground">
|
|
223
213
|
The code expired. Tap below to generate a fresh one.
|
|
224
214
|
</p>
|
|
225
215
|
|
|
226
|
-
<Button
|
|
227
|
-
type="button"
|
|
228
|
-
variant="terminal"
|
|
229
|
-
size="sm"
|
|
230
|
-
className="h-10 px-4"
|
|
231
|
-
onClick={handleRefresh}
|
|
232
|
-
>
|
|
216
|
+
<Button type="button" variant="terminal" size="sm" className="h-10 px-4" onClick={handleRefresh}>
|
|
233
217
|
<RefreshCw className="size-3.5" />
|
|
234
218
|
Generate New Code
|
|
235
219
|
</Button>
|
|
@@ -243,9 +227,7 @@ export function FieldQR({ field, value: _value, onChange, error, botId }: FieldQ
|
|
|
243
227
|
<Check className="size-16 text-emerald-500" />
|
|
244
228
|
</div>
|
|
245
229
|
|
|
246
|
-
<span className="text-xs font-medium uppercase tracking-wider text-emerald-500">
|
|
247
|
-
LINKED SUCCESSFULLY
|
|
248
|
-
</span>
|
|
230
|
+
<span className="text-xs font-medium uppercase tracking-wider text-emerald-500">LINKED SUCCESSFULLY</span>
|
|
249
231
|
|
|
250
232
|
<p className="text-center text-sm text-muted-foreground">
|
|
251
233
|
{field.label ? `${field.label} connected` : "Connected"}. You can continue setup.
|
|
@@ -258,22 +240,13 @@ export function FieldQR({ field, value: _value, onChange, error, botId }: FieldQ
|
|
|
258
240
|
<div className="flex flex-col items-center gap-4">
|
|
259
241
|
<AlertTriangle className="size-10 text-destructive" />
|
|
260
242
|
|
|
261
|
-
<span className="text-xs font-medium uppercase tracking-wider text-destructive">
|
|
262
|
-
CONNECTION ERROR
|
|
263
|
-
</span>
|
|
243
|
+
<span className="text-xs font-medium uppercase tracking-wider text-destructive">CONNECTION ERROR</span>
|
|
264
244
|
|
|
265
245
|
<p className="max-w-[280px] text-center text-sm text-muted-foreground">
|
|
266
|
-
{errorMsg ||
|
|
267
|
-
"Could not reach the server. Check that your bot is running and try again."}
|
|
246
|
+
{errorMsg || "Could not reach the server. Check that your bot is running and try again."}
|
|
268
247
|
</p>
|
|
269
248
|
|
|
270
|
-
<Button
|
|
271
|
-
type="button"
|
|
272
|
-
variant="terminal"
|
|
273
|
-
size="sm"
|
|
274
|
-
className="h-10 px-4"
|
|
275
|
-
onClick={handleRefresh}
|
|
276
|
-
>
|
|
249
|
+
<Button type="button" variant="terminal" size="sm" className="h-10 px-4" onClick={handleRefresh}>
|
|
277
250
|
<RefreshCw className="size-3.5" />
|
|
278
251
|
Try Again
|
|
279
252
|
</Button>
|
|
@@ -285,9 +258,7 @@ export function FieldQR({ field, value: _value, onChange, error, botId }: FieldQ
|
|
|
285
258
|
<div className="flex flex-col items-center gap-4">
|
|
286
259
|
<WifiOff className="size-10 text-muted-foreground" />
|
|
287
260
|
|
|
288
|
-
<span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
289
|
-
BOT OFFLINE
|
|
290
|
-
</span>
|
|
261
|
+
<span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">BOT OFFLINE</span>
|
|
291
262
|
|
|
292
263
|
<p className="max-w-[280px] text-center text-sm text-muted-foreground">
|
|
293
264
|
Your bot is currently offline. Start it from the fleet dashboard to link your account.
|
|
@@ -24,34 +24,13 @@ function renderField(
|
|
|
24
24
|
) {
|
|
25
25
|
switch (field.setupFlow) {
|
|
26
26
|
case "oauth":
|
|
27
|
-
return
|
|
28
|
-
<FieldOAuth key={field.key} field={field} value={value} onChange={onChange} error={error} />
|
|
29
|
-
);
|
|
27
|
+
return <FieldOAuth key={field.key} field={field} value={value} onChange={onChange} error={error} />;
|
|
30
28
|
case "qr":
|
|
31
|
-
return
|
|
32
|
-
<FieldQR
|
|
33
|
-
key={field.key}
|
|
34
|
-
field={field}
|
|
35
|
-
value={value}
|
|
36
|
-
onChange={onChange}
|
|
37
|
-
error={error}
|
|
38
|
-
botId={botId}
|
|
39
|
-
/>
|
|
40
|
-
);
|
|
29
|
+
return <FieldQR key={field.key} field={field} value={value} onChange={onChange} error={error} botId={botId} />;
|
|
41
30
|
case "interactive":
|
|
42
|
-
return
|
|
43
|
-
<FieldInteractive
|
|
44
|
-
key={field.key}
|
|
45
|
-
field={field}
|
|
46
|
-
value={value}
|
|
47
|
-
onChange={onChange}
|
|
48
|
-
error={error}
|
|
49
|
-
/>
|
|
50
|
-
);
|
|
31
|
+
return <FieldInteractive key={field.key} field={field} value={value} onChange={onChange} error={error} />;
|
|
51
32
|
default:
|
|
52
|
-
return
|
|
53
|
-
<FieldPaste key={field.key} field={field} value={value} onChange={onChange} error={error} />
|
|
54
|
-
);
|
|
33
|
+
return <FieldPaste key={field.key} field={field} value={value} onChange={onChange} error={error} />;
|
|
55
34
|
}
|
|
56
35
|
}
|
|
57
36
|
|
|
@@ -93,9 +72,7 @@ export function StepRenderer({ step, values, errors, onChange, botId }: StepRend
|
|
|
93
72
|
|
|
94
73
|
{hasFields && (
|
|
95
74
|
<div className="space-y-4">
|
|
96
|
-
{step.fields.map((field) =>
|
|
97
|
-
renderField(field, values[field.key] || "", onChange, errors[field.key], botId),
|
|
98
|
-
)}
|
|
75
|
+
{step.fields.map((field) => renderField(field, values[field.key] || "", onChange, errors[field.key], botId))}
|
|
99
76
|
</div>
|
|
100
77
|
)}
|
|
101
78
|
</div>
|
|
@@ -3,14 +3,7 @@
|
|
|
3
3
|
import { useCallback, useState } from "react";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { Button } from "@/components/ui/button";
|
|
6
|
-
import {
|
|
7
|
-
Card,
|
|
8
|
-
CardContent,
|
|
9
|
-
CardDescription,
|
|
10
|
-
CardFooter,
|
|
11
|
-
CardHeader,
|
|
12
|
-
CardTitle,
|
|
13
|
-
} from "@/components/ui/card";
|
|
6
|
+
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
|
14
7
|
import { Progress } from "@/components/ui/progress";
|
|
15
8
|
import { testChannelConnection } from "@/lib/api";
|
|
16
9
|
import type { ChannelManifest } from "@/lib/channel-manifests";
|
|
@@ -55,10 +48,7 @@ export function Wizard({ manifest, onComplete, onCancel, submitting, botId }: Wi
|
|
|
55
48
|
strSchema = strSchema.min(1, `${field.label} is required`);
|
|
56
49
|
}
|
|
57
50
|
if (field.validation?.pattern) {
|
|
58
|
-
strSchema = strSchema.regex(
|
|
59
|
-
new RegExp(field.validation.pattern),
|
|
60
|
-
field.validation.message || "Invalid format",
|
|
61
|
-
);
|
|
51
|
+
strSchema = strSchema.regex(new RegExp(field.validation.pattern), field.validation.message || "Invalid format");
|
|
62
52
|
}
|
|
63
53
|
shape[field.key] = field.required ? strSchema : strSchema.optional().or(z.literal(""));
|
|
64
54
|
}
|
|
@@ -149,31 +139,16 @@ export function Wizard({ manifest, onComplete, onCancel, submitting, botId }: Wi
|
|
|
149
139
|
</CardHeader>
|
|
150
140
|
|
|
151
141
|
<CardContent>
|
|
152
|
-
<StepRenderer
|
|
153
|
-
step={step}
|
|
154
|
-
values={values}
|
|
155
|
-
errors={errors}
|
|
156
|
-
onChange={handleChange}
|
|
157
|
-
botId={botId}
|
|
158
|
-
/>
|
|
142
|
+
<StepRenderer step={step} values={values} errors={errors} onChange={handleChange} botId={botId} />
|
|
159
143
|
|
|
160
144
|
{manifest.connectionTest && isLastStep && (
|
|
161
145
|
<div className="mt-6 flex flex-col items-center gap-2">
|
|
162
|
-
<Button
|
|
163
|
-
type="button"
|
|
164
|
-
variant="outline"
|
|
165
|
-
onClick={handleTestConnection}
|
|
166
|
-
disabled={testing}
|
|
167
|
-
>
|
|
146
|
+
<Button type="button" variant="outline" onClick={handleTestConnection} disabled={testing}>
|
|
168
147
|
{testing ? "Testing..." : manifest.connectionTest.label}
|
|
169
148
|
</Button>
|
|
170
|
-
{testResult === "success" &&
|
|
171
|
-
<p className="text-sm text-emerald-500">Connection successful</p>
|
|
172
|
-
)}
|
|
149
|
+
{testResult === "success" && <p className="text-sm text-emerald-500">Connection successful</p>}
|
|
173
150
|
{testResult === "failure" && (
|
|
174
|
-
<p className="text-sm text-destructive">
|
|
175
|
-
{testError || "Connection failed. Check your settings."}
|
|
176
|
-
</p>
|
|
151
|
+
<p className="text-sm text-destructive">{testError || "Connection failed. Check your settings."}</p>
|
|
177
152
|
)}
|
|
178
153
|
</div>
|
|
179
154
|
)}
|
|
@@ -18,10 +18,7 @@ export function ChatMessage({ message }: ChatMessageProps) {
|
|
|
18
18
|
const isUser = message.role === "user";
|
|
19
19
|
|
|
20
20
|
return (
|
|
21
|
-
<div
|
|
22
|
-
className={`flex ${isUser ? "justify-end" : "justify-start"}`}
|
|
23
|
-
data-testid={`chat-message-${message.role}`}
|
|
24
|
-
>
|
|
21
|
+
<div className={`flex ${isUser ? "justify-end" : "justify-start"}`} data-testid={`chat-message-${message.role}`}>
|
|
25
22
|
<div
|
|
26
23
|
className={`max-w-[80%] rounded-lg px-3 py-2 ${
|
|
27
24
|
isUser
|
|
@@ -36,15 +36,7 @@ const fullscreenVariants = {
|
|
|
36
36
|
exit: { opacity: 0, transition: { duration: 0.15 } },
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
export function ChatPanel({
|
|
40
|
-
messages,
|
|
41
|
-
mode,
|
|
42
|
-
isConnected,
|
|
43
|
-
isTyping,
|
|
44
|
-
onSend,
|
|
45
|
-
onClose,
|
|
46
|
-
onFullscreen,
|
|
47
|
-
}: ChatPanelProps) {
|
|
39
|
+
export function ChatPanel({ messages, mode, isConnected, isTyping, onSend, onClose, onFullscreen }: ChatPanelProps) {
|
|
48
40
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
|
49
41
|
const isFullscreen = mode === "fullscreen";
|
|
50
42
|
|
|
@@ -81,9 +73,7 @@ export function ChatPanel({
|
|
|
81
73
|
className={`h-2 w-2 rounded-full inline-block ${isConnected ? "bg-terminal" : "bg-destructive"}`}
|
|
82
74
|
aria-label={isConnected ? "Connected" : "Disconnected"}
|
|
83
75
|
/>
|
|
84
|
-
<span className="text-xs font-mono uppercase tracking-wider text-muted-foreground">
|
|
85
|
-
{brandName()}
|
|
86
|
-
</span>
|
|
76
|
+
<span className="text-xs font-mono uppercase tracking-wider text-muted-foreground">{brandName()}</span>
|
|
87
77
|
</div>
|
|
88
78
|
<div className="flex items-center gap-1">
|
|
89
79
|
{!isFullscreen && (
|
|
@@ -112,13 +102,9 @@ export function ChatPanel({
|
|
|
112
102
|
</div>
|
|
113
103
|
|
|
114
104
|
{/* Messages */}
|
|
115
|
-
<div
|
|
116
|
-
className={`flex-1 overflow-y-auto px-4 py-3 space-y-2 ${isFullscreen ? "max-w-2xl mx-auto w-full" : ""}`}
|
|
117
|
-
>
|
|
105
|
+
<div className={`flex-1 overflow-y-auto px-4 py-3 space-y-2 ${isFullscreen ? "max-w-2xl mx-auto w-full" : ""}`}>
|
|
118
106
|
{messages.length === 0 && !isConnected && (
|
|
119
|
-
<p className="text-center text-xs text-muted-foreground animate-ellipsis">
|
|
120
|
-
Connecting to {brandName()}
|
|
121
|
-
</p>
|
|
107
|
+
<p className="text-center text-xs text-muted-foreground animate-ellipsis">Connecting to {brandName()}</p>
|
|
122
108
|
)}
|
|
123
109
|
{messages.map((msg) => (
|
|
124
110
|
<ChatMessage key={msg.id} message={msg} />
|
|
@@ -6,23 +6,12 @@ import { AmbientDot } from "./ambient-dot";
|
|
|
6
6
|
import { ChatPanel } from "./chat-panel";
|
|
7
7
|
|
|
8
8
|
export function ChatWidget() {
|
|
9
|
-
const {
|
|
10
|
-
|
|
11
|
-
mode,
|
|
12
|
-
isConnected,
|
|
13
|
-
isTyping,
|
|
14
|
-
hasUnread,
|
|
15
|
-
expand,
|
|
16
|
-
collapse,
|
|
17
|
-
fullscreen,
|
|
18
|
-
sendMessage,
|
|
19
|
-
} = useChatContext();
|
|
9
|
+
const { messages, mode, isConnected, isTyping, hasUnread, expand, collapse, fullscreen, sendMessage } =
|
|
10
|
+
useChatContext();
|
|
20
11
|
|
|
21
12
|
return (
|
|
22
13
|
<>
|
|
23
|
-
<AnimatePresence>
|
|
24
|
-
{mode === "collapsed" && <AmbientDot hasUnread={hasUnread} onClick={expand} />}
|
|
25
|
-
</AnimatePresence>
|
|
14
|
+
<AnimatePresence>{mode === "collapsed" && <AmbientDot hasUnread={hasUnread} onClick={expand} />}</AnimatePresence>
|
|
26
15
|
<AnimatePresence>
|
|
27
16
|
{(mode === "expanded" || mode === "fullscreen") && (
|
|
28
17
|
<ChatPanel
|
|
@@ -9,19 +9,8 @@ import { Button } from "@/components/ui/button";
|
|
|
9
9
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
10
10
|
import { useFleetSSE } from "@/hooks/use-fleet-sse";
|
|
11
11
|
import { usePaginationParams } from "@/hooks/use-pagination-params";
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
BotStatusResponse,
|
|
15
|
-
DividendWalletStats,
|
|
16
|
-
FleetInstance,
|
|
17
|
-
FleetResources,
|
|
18
|
-
} from "@/lib/api";
|
|
19
|
-
import {
|
|
20
|
-
getActivityFeed,
|
|
21
|
-
getDividendStats,
|
|
22
|
-
getFleetResources,
|
|
23
|
-
mapBotStatusToFleetInstance,
|
|
24
|
-
} from "@/lib/api";
|
|
12
|
+
import type { ActivityEvent, BotStatusResponse, DividendWalletStats, FleetInstance, FleetResources } from "@/lib/api";
|
|
13
|
+
import { getActivityFeed, getDividendStats, getFleetResources, mapBotStatusToFleetInstance } from "@/lib/api";
|
|
25
14
|
import { productName } from "@/lib/brand-config";
|
|
26
15
|
import { toUserMessage } from "@/lib/errors";
|
|
27
16
|
import { formatRelativeTime } from "@/lib/format";
|
|
@@ -32,9 +21,7 @@ import { cn } from "@/lib/utils";
|
|
|
32
21
|
function computeFleetStats(instances: FleetInstance[], resources: FleetResources | null) {
|
|
33
22
|
const running = instances.filter((i) => i.status === "running").length;
|
|
34
23
|
const stopped = instances.filter((i) => i.status === "stopped").length;
|
|
35
|
-
const degraded = instances.filter(
|
|
36
|
-
(i) => i.health === "degraded" || i.health === "unhealthy",
|
|
37
|
-
).length;
|
|
24
|
+
const degraded = instances.filter((i) => i.health === "degraded" || i.health === "unhealthy").length;
|
|
38
25
|
const totalCpu = resources?.totalCpuPercent ?? 0;
|
|
39
26
|
const totalMemory = resources?.totalMemoryMb ?? 0;
|
|
40
27
|
const memoryCapacity = resources?.memoryCapacityMb ?? 2048;
|
|
@@ -236,12 +223,7 @@ export function CommandCenter() {
|
|
|
236
223
|
className="flex items-center justify-between rounded-md border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-400"
|
|
237
224
|
>
|
|
238
225
|
<span>{error}</span>
|
|
239
|
-
<Button
|
|
240
|
-
data-onboarding-id="dashboard.retry"
|
|
241
|
-
variant="ghost"
|
|
242
|
-
size="sm"
|
|
243
|
-
onClick={() => loadNonFleet()}
|
|
244
|
-
>
|
|
226
|
+
<Button data-onboarding-id="dashboard.retry" variant="ghost" size="sm" onClick={() => loadNonFleet()}>
|
|
245
227
|
Retry
|
|
246
228
|
</Button>
|
|
247
229
|
</div>
|
|
@@ -249,10 +231,7 @@ export function CommandCenter() {
|
|
|
249
231
|
|
|
250
232
|
{/* Fleet Summary Cards */}
|
|
251
233
|
<motion.div
|
|
252
|
-
className={cn(
|
|
253
|
-
"grid gap-4 sm:grid-cols-2",
|
|
254
|
-
dividendStats ? "lg:grid-cols-5" : "lg:grid-cols-4",
|
|
255
|
-
)}
|
|
234
|
+
className={cn("grid gap-4 sm:grid-cols-2", dividendStats ? "lg:grid-cols-5" : "lg:grid-cols-4")}
|
|
256
235
|
variants={staggerContainer}
|
|
257
236
|
initial="hidden"
|
|
258
237
|
animate="show"
|
|
@@ -268,9 +247,7 @@ export function CommandCenter() {
|
|
|
268
247
|
<p className="mt-2 text-3xl font-bold tabular-nums" data-testid="running-count">
|
|
269
248
|
{loading ? "--" : <CountUp value={stats.running} />}
|
|
270
249
|
</p>
|
|
271
|
-
<p className="text-xs text-muted-foreground">
|
|
272
|
-
{stats.running === 1 ? "instance" : "instances"}
|
|
273
|
-
</p>
|
|
250
|
+
<p className="text-xs text-muted-foreground">{stats.running === 1 ? "instance" : "instances"}</p>
|
|
274
251
|
</CardContent>
|
|
275
252
|
</Card>
|
|
276
253
|
</motion.div>
|
|
@@ -286,9 +263,7 @@ export function CommandCenter() {
|
|
|
286
263
|
<p className="mt-2 text-3xl font-bold tabular-nums" data-testid="stopped-count">
|
|
287
264
|
{loading ? "--" : <CountUp value={stats.stopped} />}
|
|
288
265
|
</p>
|
|
289
|
-
<p className="text-xs text-muted-foreground">
|
|
290
|
-
{stats.stopped === 1 ? "instance" : "instances"}
|
|
291
|
-
</p>
|
|
266
|
+
<p className="text-xs text-muted-foreground">{stats.stopped === 1 ? "instance" : "instances"}</p>
|
|
292
267
|
</CardContent>
|
|
293
268
|
</Card>
|
|
294
269
|
</motion.div>
|
|
@@ -308,9 +283,7 @@ export function CommandCenter() {
|
|
|
308
283
|
<p className="mt-2 text-3xl font-bold tabular-nums" data-testid="degraded-count">
|
|
309
284
|
{loading ? "--" : <CountUp value={stats.degraded} />}
|
|
310
285
|
</p>
|
|
311
|
-
<p className="text-xs text-muted-foreground">
|
|
312
|
-
{stats.degraded > 0 ? "need attention" : "all clear"}
|
|
313
|
-
</p>
|
|
286
|
+
<p className="text-xs text-muted-foreground">{stats.degraded > 0 ? "need attention" : "all clear"}</p>
|
|
314
287
|
</CardContent>
|
|
315
288
|
</Card>
|
|
316
289
|
</motion.div>
|
|
@@ -349,9 +322,7 @@ export function CommandCenter() {
|
|
|
349
322
|
className="h-full rounded-full bg-primary"
|
|
350
323
|
initial={{ width: "0%" }}
|
|
351
324
|
animate={{
|
|
352
|
-
width: loading
|
|
353
|
-
? "0%"
|
|
354
|
-
: `${Math.min(100, (stats.totalMemory / stats.memoryCapacity) * 100)}%`,
|
|
325
|
+
width: loading ? "0%" : `${Math.min(100, (stats.totalMemory / stats.memoryCapacity) * 100)}%`,
|
|
355
326
|
}}
|
|
356
327
|
transition={{ duration: 0.8, ease: "easeOut", delay: 0.3 }}
|
|
357
328
|
/>
|
|
@@ -372,10 +343,7 @@ export function CommandCenter() {
|
|
|
372
343
|
<p className="text-sm text-muted-foreground">Today's Dividend</p>
|
|
373
344
|
<TrendingUpIcon className="size-4 text-terminal" />
|
|
374
345
|
</div>
|
|
375
|
-
<p
|
|
376
|
-
className="mt-2 text-3xl font-bold tabular-nums text-terminal"
|
|
377
|
-
data-testid="dividend-amount"
|
|
378
|
-
>
|
|
346
|
+
<p className="mt-2 text-3xl font-bold tabular-nums text-terminal" data-testid="dividend-amount">
|
|
379
347
|
{loading ? "--" : formatCreditStandard((dividendStats?.perUserCents ?? 0) / 100)}
|
|
380
348
|
</p>
|
|
381
349
|
<p className="text-xs text-muted-foreground">
|
|
@@ -407,9 +375,7 @@ export function CommandCenter() {
|
|
|
407
375
|
) : activity.length === 0 ? (
|
|
408
376
|
<div className="flex flex-col items-center gap-2 py-8 text-center">
|
|
409
377
|
<p className="font-mono text-sm text-terminal">> STANDING BY</p>
|
|
410
|
-
<p className="font-mono text-xs text-muted-foreground">
|
|
411
|
-
NO EVENTS LOGGED. AWAITING ACTIVITY.
|
|
412
|
-
</p>
|
|
378
|
+
<p className="font-mono text-xs text-muted-foreground">NO EVENTS LOGGED. AWAITING ACTIVITY.</p>
|
|
413
379
|
</div>
|
|
414
380
|
) : (
|
|
415
381
|
<motion.div
|
|
@@ -475,11 +441,7 @@ export function CommandCenter() {
|
|
|
475
441
|
<motion.div
|
|
476
442
|
className="h-full rounded-sm border border-dashed border-terminal/20"
|
|
477
443
|
animate={{
|
|
478
|
-
borderColor: [
|
|
479
|
-
"hsl(var(--terminal) / 0.2)",
|
|
480
|
-
"hsl(var(--terminal) / 0.5)",
|
|
481
|
-
"hsl(var(--terminal) / 0.2)",
|
|
482
|
-
],
|
|
444
|
+
borderColor: ["hsl(var(--terminal) / 0.2)", "hsl(var(--terminal) / 0.5)", "hsl(var(--terminal) / 0.2)"],
|
|
483
445
|
}}
|
|
484
446
|
transition={{ duration: 3, repeat: Infinity, ease: "easeInOut" }}
|
|
485
447
|
>
|
|
@@ -497,9 +459,7 @@ export function CommandCenter() {
|
|
|
497
459
|
<Plus size={24} />
|
|
498
460
|
</motion.div>
|
|
499
461
|
<p className="font-semibold">Add another {productName()}</p>
|
|
500
|
-
<p className="mt-1 text-xs text-muted-foreground">
|
|
501
|
-
Name it. Teach it. Give it superpowers.
|
|
502
|
-
</p>
|
|
462
|
+
<p className="mt-1 text-xs text-muted-foreground">Name it. Teach it. Give it superpowers.</p>
|
|
503
463
|
</CardContent>
|
|
504
464
|
</Card>
|
|
505
465
|
</motion.div>
|
|
@@ -511,16 +471,10 @@ export function CommandCenter() {
|
|
|
511
471
|
{fleetTotal > FLEET_PAGE_SIZE && (
|
|
512
472
|
<div className="flex items-center justify-between text-xs text-muted-foreground font-mono">
|
|
513
473
|
<span>
|
|
514
|
-
Showing {fleetStart + 1}-{Math.min(fleetStart + FLEET_PAGE_SIZE, fleetTotal)} of{
|
|
515
|
-
{fleetTotal}
|
|
474
|
+
Showing {fleetStart + 1}-{Math.min(fleetStart + FLEET_PAGE_SIZE, fleetTotal)} of {fleetTotal}
|
|
516
475
|
</span>
|
|
517
476
|
<div className="flex gap-2">
|
|
518
|
-
<Button
|
|
519
|
-
variant="ghost"
|
|
520
|
-
size="xs"
|
|
521
|
-
disabled={fleetPage <= 1}
|
|
522
|
-
onClick={() => setFleetPage(fleetPage - 1)}
|
|
523
|
-
>
|
|
477
|
+
<Button variant="ghost" size="xs" disabled={fleetPage <= 1} onClick={() => setFleetPage(fleetPage - 1)}>
|
|
524
478
|
Previous
|
|
525
479
|
</Button>
|
|
526
480
|
<Button
|
|
@@ -5,20 +5,13 @@ import { useState } from "react";
|
|
|
5
5
|
import { toast } from "sonner";
|
|
6
6
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
7
7
|
import { Label } from "@/components/ui/label";
|
|
8
|
-
import {
|
|
9
|
-
Select,
|
|
10
|
-
SelectContent,
|
|
11
|
-
SelectItem,
|
|
12
|
-
SelectTrigger,
|
|
13
|
-
SelectValue,
|
|
14
|
-
} from "@/components/ui/select";
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
15
9
|
import { Switch } from "@/components/ui/switch";
|
|
16
10
|
import { useTenant } from "@/lib/tenant-context";
|
|
17
11
|
import { trpc } from "@/lib/trpc";
|
|
18
12
|
|
|
19
13
|
const HOUR_OPTIONS = Array.from({ length: 24 }, (_, i) => {
|
|
20
|
-
const label =
|
|
21
|
-
i === 0 ? "12:00 AM" : i < 12 ? `${i}:00 AM` : i === 12 ? "12:00 PM" : `${i - 12}:00 PM`;
|
|
14
|
+
const label = i === 0 ? "12:00 AM" : i < 12 ? `${i}:00 AM` : i === 12 ? "12:00 PM" : `${i - 12}:00 PM`;
|
|
22
15
|
return { value: String(i), label };
|
|
23
16
|
});
|
|
24
17
|
|
|
@@ -26,10 +19,7 @@ export function UpdateSettingsCard() {
|
|
|
26
19
|
const { activeTenantId: tenantId } = useTenant();
|
|
27
20
|
const [saving, setSaving] = useState(false);
|
|
28
21
|
|
|
29
|
-
const configQuery = trpc.fleetUpdateConfig.getUpdateConfig.useQuery(
|
|
30
|
-
{ tenantId },
|
|
31
|
-
{ enabled: !!tenantId },
|
|
32
|
-
);
|
|
22
|
+
const configQuery = trpc.fleetUpdateConfig.getUpdateConfig.useQuery({ tenantId }, { enabled: !!tenantId });
|
|
33
23
|
|
|
34
24
|
const setConfigMutation = trpc.fleetUpdateConfig.setUpdateConfig.useMutation();
|
|
35
25
|
|
|
@@ -82,8 +72,8 @@ export function UpdateSettingsCard() {
|
|
|
82
72
|
Auto-Update Settings
|
|
83
73
|
</CardTitle>
|
|
84
74
|
<CardDescription>
|
|
85
|
-
Control how your fleet receives updates. Auto mode applies updates during your preferred
|
|
86
|
-
|
|
75
|
+
Control how your fleet receives updates. Auto mode applies updates during your preferred maintenance window.
|
|
76
|
+
Manual mode shows an update badge — you choose when to apply.
|
|
87
77
|
</CardDescription>
|
|
88
78
|
</CardHeader>
|
|
89
79
|
<CardContent className="space-y-6">
|
|
@@ -129,15 +119,9 @@ export function UpdateSettingsCard() {
|
|
|
129
119
|
<Clock className="h-4 w-4 text-muted-foreground" />
|
|
130
120
|
Maintenance Window
|
|
131
121
|
</Label>
|
|
132
|
-
<p className="text-sm text-muted-foreground">
|
|
133
|
-
Preferred hour for applying updates (UTC)
|
|
134
|
-
</p>
|
|
122
|
+
<p className="text-sm text-muted-foreground">Preferred hour for applying updates (UTC)</p>
|
|
135
123
|
</div>
|
|
136
|
-
<Select
|
|
137
|
-
value={String(preferredHourUtc)}
|
|
138
|
-
onValueChange={handleHourChange}
|
|
139
|
-
disabled={saving}
|
|
140
|
-
>
|
|
124
|
+
<Select value={String(preferredHourUtc)} onValueChange={handleHourChange} disabled={saving}>
|
|
141
125
|
<SelectTrigger className="w-[140px]">
|
|
142
126
|
<SelectValue />
|
|
143
127
|
</SelectTrigger>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import { controlInstance, type InstanceVersionCheck, instanceVersionCheck } from "@/lib/api";
|
|
6
|
+
import { trpcVanilla } from "@/lib/trpc";
|
|
7
|
+
|
|
8
|
+
type Status = "idle" | "rolling" | "done";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Banner that appears when the user's sidecar container is running an
|
|
12
|
+
* older image than the one currently pulled on the host node. Offers an
|
|
13
|
+
* opt-in "Update now" button that triggers a fleet.controlInstance roll.
|
|
14
|
+
*
|
|
15
|
+
* Polls every 60s. During roll the banner shows "Updating…" and expects
|
|
16
|
+
* a brief iframe/sidebar reconnect; on success it reloads the page so
|
|
17
|
+
* the fresh container serves the iframe from its new image.
|
|
18
|
+
*/
|
|
19
|
+
export function InstanceUpdateBanner() {
|
|
20
|
+
const [instanceId, setInstanceId] = useState<string | null>(null);
|
|
21
|
+
const [check, setCheck] = useState<InstanceVersionCheck | null>(null);
|
|
22
|
+
const [status, setStatus] = useState<Status>("idle");
|
|
23
|
+
const [dismissed, setDismissed] = useState(false);
|
|
24
|
+
const reloadTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
let cancelled = false;
|
|
28
|
+
// Go direct to the raw tRPC response so we can read platformInstanceId
|
|
29
|
+
// (the bot_instances UUID). `listInstances()` in api.ts maps to a
|
|
30
|
+
// client-friendly shape but reads `bot.id` which is the docker
|
|
31
|
+
// container hash in the success path — not what the fleet APIs want.
|
|
32
|
+
(async () => {
|
|
33
|
+
try {
|
|
34
|
+
const data = (await trpcVanilla.fleet.listInstances.query(undefined)) as {
|
|
35
|
+
bots?: Array<{ platformInstanceId?: string }>;
|
|
36
|
+
};
|
|
37
|
+
// The hosted shell currently provisions exactly one sidecar per
|
|
38
|
+
// user, so bots[0] is the workspace the shell is backing. If
|
|
39
|
+
// multi-instance support lands, pick the active instance here.
|
|
40
|
+
if (!cancelled) setInstanceId(data.bots?.[0]?.platformInstanceId ?? null);
|
|
41
|
+
} catch {
|
|
42
|
+
if (!cancelled) setInstanceId(null);
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
// Clear any in-flight reload timer if the component unmounts, to avoid
|
|
51
|
+
// a setState on an unmounted component and a surprise reload firing
|
|
52
|
+
// after navigation.
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
return () => {
|
|
55
|
+
if (reloadTimeoutRef.current) {
|
|
56
|
+
clearTimeout(reloadTimeoutRef.current);
|
|
57
|
+
reloadTimeoutRef.current = null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!instanceId) return;
|
|
64
|
+
let cancelled = false;
|
|
65
|
+
async function poll() {
|
|
66
|
+
if (!instanceId) return;
|
|
67
|
+
try {
|
|
68
|
+
const result = await instanceVersionCheck(instanceId);
|
|
69
|
+
if (!cancelled) setCheck(result);
|
|
70
|
+
} catch {
|
|
71
|
+
// Transient errors are fine — we'll retry on the next tick.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
void poll();
|
|
75
|
+
const handle = window.setInterval(poll, 60_000);
|
|
76
|
+
return () => {
|
|
77
|
+
cancelled = true;
|
|
78
|
+
window.clearInterval(handle);
|
|
79
|
+
};
|
|
80
|
+
}, [instanceId]);
|
|
81
|
+
|
|
82
|
+
const onUpdate = useCallback(async () => {
|
|
83
|
+
if (!instanceId) return;
|
|
84
|
+
setStatus("rolling");
|
|
85
|
+
try {
|
|
86
|
+
await controlInstance(instanceId, "roll");
|
|
87
|
+
// Give the container ~25s to come back up before reloading. A tighter
|
|
88
|
+
// loop that polls health would be nicer, but reload is simpler and
|
|
89
|
+
// matches how the shell already recovers from routeChanged.
|
|
90
|
+
reloadTimeoutRef.current = setTimeout(() => {
|
|
91
|
+
setStatus("done");
|
|
92
|
+
window.location.reload();
|
|
93
|
+
}, 25_000);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
96
|
+
toast.error(`Update failed: ${msg}`);
|
|
97
|
+
setStatus("idle");
|
|
98
|
+
}
|
|
99
|
+
}, [instanceId]);
|
|
100
|
+
|
|
101
|
+
if (dismissed) return null;
|
|
102
|
+
if (!check) return null;
|
|
103
|
+
if (check.upToDate) return null;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="flex items-center gap-3 border-b border-amber-500/30 bg-amber-500/10 px-4 py-2 text-sm text-amber-100">
|
|
107
|
+
<span className="font-medium">New version available</span>
|
|
108
|
+
<span className="text-amber-100/70">Your workspace will briefly disconnect while we swap it in.</span>
|
|
109
|
+
<div className="ml-auto flex items-center gap-2">
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
onClick={onUpdate}
|
|
113
|
+
disabled={status !== "idle"}
|
|
114
|
+
className="rounded-md bg-amber-500 px-3 py-1 text-xs font-medium text-amber-950 transition-colors hover:bg-amber-400 disabled:cursor-not-allowed disabled:opacity-60"
|
|
115
|
+
>
|
|
116
|
+
{status === "rolling" ? "Updating…" : status === "done" ? "Reloading…" : "Update now"}
|
|
117
|
+
</button>
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
onClick={() => setDismissed(true)}
|
|
121
|
+
disabled={status !== "idle"}
|
|
122
|
+
className="rounded-md px-2 py-1 text-xs text-amber-100/70 hover:bg-amber-500/10 hover:text-amber-50 disabled:cursor-not-allowed"
|
|
123
|
+
aria-label="Dismiss update banner"
|
|
124
|
+
>
|
|
125
|
+
Later
|
|
126
|
+
</button>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|