@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
|
@@ -1,14 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { AnimatePresence, motion } from "framer-motion";
|
|
4
|
-
import {
|
|
5
|
-
CheckIcon,
|
|
6
|
-
CopyIcon,
|
|
7
|
-
DownloadIcon,
|
|
8
|
-
MonitorIcon,
|
|
9
|
-
SmartphoneIcon,
|
|
10
|
-
TabletIcon,
|
|
11
|
-
} from "lucide-react";
|
|
4
|
+
import { CheckIcon, CopyIcon, DownloadIcon, MonitorIcon, SmartphoneIcon, TabletIcon } from "lucide-react";
|
|
12
5
|
import { QRCodeSVG } from "qrcode.react";
|
|
13
6
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
14
7
|
import { Badge } from "@/components/ui/badge";
|
|
@@ -25,14 +18,7 @@ import {
|
|
|
25
18
|
} from "@/components/ui/dialog";
|
|
26
19
|
import { Input } from "@/components/ui/input";
|
|
27
20
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
28
|
-
import {
|
|
29
|
-
Table,
|
|
30
|
-
TableBody,
|
|
31
|
-
TableCell,
|
|
32
|
-
TableHead,
|
|
33
|
-
TableHeader,
|
|
34
|
-
TableRow,
|
|
35
|
-
} from "@/components/ui/table";
|
|
21
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
36
22
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
37
23
|
import type { LoginAttempt, LoginHistoryResponse } from "@/lib/api";
|
|
38
24
|
import { fetchLoginHistory } from "@/lib/api";
|
|
@@ -105,11 +91,7 @@ function StepIndicator({ currentStep, steps }: { currentStep: number; steps: str
|
|
|
105
91
|
<div className="flex items-center justify-center gap-0 px-4 py-2">
|
|
106
92
|
{steps.map((label, i) => (
|
|
107
93
|
<div key={label} className="flex items-center">
|
|
108
|
-
{i > 0 &&
|
|
109
|
-
<div
|
|
110
|
-
className={`h-px w-8 sm:w-12 ${i <= currentStep ? "bg-terminal/40" : "bg-border"}`}
|
|
111
|
-
/>
|
|
112
|
-
)}
|
|
94
|
+
{i > 0 && <div className={`h-px w-8 sm:w-12 ${i <= currentStep ? "bg-terminal/40" : "bg-border"}`} />}
|
|
113
95
|
<div className="flex flex-col items-center gap-1">
|
|
114
96
|
<div
|
|
115
97
|
className={`flex size-8 items-center justify-center rounded-full text-xs font-medium transition-colors ${
|
|
@@ -122,9 +104,7 @@ function StepIndicator({ currentStep, steps }: { currentStep: number; steps: str
|
|
|
122
104
|
>
|
|
123
105
|
{i < currentStep ? <CheckIcon className="size-4" /> : i + 1}
|
|
124
106
|
</div>
|
|
125
|
-
<span
|
|
126
|
-
className={`text-xs ${i === currentStep ? "font-medium text-foreground" : "text-muted-foreground"}`}
|
|
127
|
-
>
|
|
107
|
+
<span className={`text-xs ${i === currentStep ? "font-medium text-foreground" : "text-muted-foreground"}`}>
|
|
128
108
|
{label}
|
|
129
109
|
</span>
|
|
130
110
|
</div>
|
|
@@ -185,9 +165,7 @@ function TwoFactorSection() {
|
|
|
185
165
|
}, []);
|
|
186
166
|
|
|
187
167
|
useEffect(() => {
|
|
188
|
-
setEnabled(
|
|
189
|
-
Boolean((profileData as { twoFactorEnabled?: boolean } | undefined)?.twoFactorEnabled),
|
|
190
|
-
);
|
|
168
|
+
setEnabled(Boolean((profileData as { twoFactorEnabled?: boolean } | undefined)?.twoFactorEnabled));
|
|
191
169
|
}, [profileData]);
|
|
192
170
|
|
|
193
171
|
function handleStartEnable() {
|
|
@@ -347,9 +325,7 @@ function TwoFactorSection() {
|
|
|
347
325
|
<>
|
|
348
326
|
<div className="flex items-center gap-2">
|
|
349
327
|
<span className="size-2 rounded-full bg-terminal animate-pulse" />
|
|
350
|
-
<span className="text-sm font-medium text-terminal">
|
|
351
|
-
Two-factor authentication is active
|
|
352
|
-
</span>
|
|
328
|
+
<span className="text-sm font-medium text-terminal">Two-factor authentication is active</span>
|
|
353
329
|
</div>
|
|
354
330
|
<div className="flex items-center gap-2">
|
|
355
331
|
<Button variant="outline" onClick={() => setDisableOpen(true)}>
|
|
@@ -369,23 +345,17 @@ function TwoFactorSection() {
|
|
|
369
345
|
</Button>
|
|
370
346
|
</div>
|
|
371
347
|
{codesRemaining <= 2 && (
|
|
372
|
-
<p className="text-xs text-chart-3">
|
|
373
|
-
{codesRemaining} of 8 recovery codes remaining
|
|
374
|
-
</p>
|
|
348
|
+
<p className="text-xs text-chart-3">{codesRemaining} of 8 recovery codes remaining</p>
|
|
375
349
|
)}
|
|
376
350
|
{codesRemaining > 2 && (
|
|
377
|
-
<p className="text-xs text-muted-foreground">
|
|
378
|
-
{codesRemaining} of 8 recovery codes remaining
|
|
379
|
-
</p>
|
|
351
|
+
<p className="text-xs text-muted-foreground">{codesRemaining} of 8 recovery codes remaining</p>
|
|
380
352
|
)}
|
|
381
353
|
</>
|
|
382
354
|
) : (
|
|
383
355
|
<>
|
|
384
356
|
<div className="flex items-center gap-2">
|
|
385
357
|
<span className="size-2 rounded-full bg-chart-3" />
|
|
386
|
-
<span className="text-sm font-medium text-chart-3">
|
|
387
|
-
Two-factor authentication is not enabled
|
|
388
|
-
</span>
|
|
358
|
+
<span className="text-sm font-medium text-chart-3">Two-factor authentication is not enabled</span>
|
|
389
359
|
</div>
|
|
390
360
|
<Button variant="terminal" onClick={handleStartEnable}>
|
|
391
361
|
Enable 2FA
|
|
@@ -398,17 +368,13 @@ function TwoFactorSection() {
|
|
|
398
368
|
{/* Enable 2FA Dialog */}
|
|
399
369
|
<Dialog open={enableOpen} onOpenChange={setEnableOpen}>
|
|
400
370
|
<DialogContent className="max-w-md">
|
|
401
|
-
{enableStep >= 0 &&
|
|
402
|
-
<StepIndicator currentStep={enableStep} steps={["Scan", "Verify", "Backup"]} />
|
|
403
|
-
)}
|
|
371
|
+
{enableStep >= 0 && <StepIndicator currentStep={enableStep} steps={["Scan", "Verify", "Backup"]} />}
|
|
404
372
|
|
|
405
373
|
{enableStep === -1 && (
|
|
406
374
|
<>
|
|
407
375
|
<DialogHeader>
|
|
408
376
|
<DialogTitle>Confirm your password</DialogTitle>
|
|
409
|
-
<DialogDescription>
|
|
410
|
-
Enter your password to set up two-factor authentication
|
|
411
|
-
</DialogDescription>
|
|
377
|
+
<DialogDescription>Enter your password to set up two-factor authentication</DialogDescription>
|
|
412
378
|
</DialogHeader>
|
|
413
379
|
<div className="flex flex-col items-center gap-3">
|
|
414
380
|
<Input
|
|
@@ -457,8 +423,7 @@ function TwoFactorSection() {
|
|
|
457
423
|
<DialogHeader>
|
|
458
424
|
<DialogTitle>Set up authenticator</DialogTitle>
|
|
459
425
|
<DialogDescription>
|
|
460
|
-
Scan this QR code with your authenticator app (Google Authenticator, Authy,
|
|
461
|
-
1Password)
|
|
426
|
+
Scan this QR code with your authenticator app (Google Authenticator, Authy, 1Password)
|
|
462
427
|
</DialogDescription>
|
|
463
428
|
</DialogHeader>
|
|
464
429
|
<div className="flex flex-col items-center gap-4">
|
|
@@ -467,18 +432,12 @@ function TwoFactorSection() {
|
|
|
467
432
|
<QRCodeSVG value={totpUri} size={192} />
|
|
468
433
|
</div>
|
|
469
434
|
<div className="w-full space-y-2">
|
|
470
|
-
<p className="text-xs text-muted-foreground">
|
|
471
|
-
Can't scan? Enter this key manually:
|
|
472
|
-
</p>
|
|
435
|
+
<p className="text-xs text-muted-foreground">Can't scan? Enter this key manually:</p>
|
|
473
436
|
<div className="flex items-center gap-2">
|
|
474
437
|
<code className="flex-1 rounded-sm bg-muted px-3 py-2 text-xs font-mono tracking-widest">
|
|
475
438
|
{totpSecret}
|
|
476
439
|
</code>
|
|
477
|
-
<Button
|
|
478
|
-
variant="ghost"
|
|
479
|
-
size="icon-sm"
|
|
480
|
-
onClick={() => copyToClipboard(totpSecret)}
|
|
481
|
-
>
|
|
440
|
+
<Button variant="ghost" size="icon-sm" onClick={() => copyToClipboard(totpSecret)}>
|
|
482
441
|
<CopyIcon className="size-4" />
|
|
483
442
|
</Button>
|
|
484
443
|
</div>
|
|
@@ -496,9 +455,7 @@ function TwoFactorSection() {
|
|
|
496
455
|
<>
|
|
497
456
|
<DialogHeader>
|
|
498
457
|
<DialogTitle>Verify your code</DialogTitle>
|
|
499
|
-
<DialogDescription>
|
|
500
|
-
Enter the 6-digit code from your authenticator app
|
|
501
|
-
</DialogDescription>
|
|
458
|
+
<DialogDescription>Enter the 6-digit code from your authenticator app</DialogDescription>
|
|
502
459
|
</DialogHeader>
|
|
503
460
|
<div className="flex flex-col items-center gap-3">
|
|
504
461
|
<Input
|
|
@@ -526,11 +483,7 @@ function TwoFactorSection() {
|
|
|
526
483
|
<Button variant="ghost" onClick={() => setEnableStep(0)}>
|
|
527
484
|
Back
|
|
528
485
|
</Button>
|
|
529
|
-
<Button
|
|
530
|
-
variant="terminal"
|
|
531
|
-
disabled={verifyCode.length !== 6 || verifying}
|
|
532
|
-
onClick={handleVerify}
|
|
533
|
-
>
|
|
486
|
+
<Button variant="terminal" disabled={verifyCode.length !== 6 || verifying} onClick={handleVerify}>
|
|
534
487
|
{verifying ? "Verifying..." : "Verify"}
|
|
535
488
|
</Button>
|
|
536
489
|
</DialogFooter>
|
|
@@ -542,8 +495,8 @@ function TwoFactorSection() {
|
|
|
542
495
|
<DialogHeader>
|
|
543
496
|
<DialogTitle>Save your recovery codes</DialogTitle>
|
|
544
497
|
<DialogDescription>
|
|
545
|
-
Store these codes in a safe place. Each code can only be used once. You won't
|
|
546
|
-
|
|
498
|
+
Store these codes in a safe place. Each code can only be used once. You won't be able to see them
|
|
499
|
+
again.
|
|
547
500
|
</DialogDescription>
|
|
548
501
|
</DialogHeader>
|
|
549
502
|
<div className="grid grid-cols-2 gap-2">
|
|
@@ -557,11 +510,7 @@ function TwoFactorSection() {
|
|
|
557
510
|
))}
|
|
558
511
|
</div>
|
|
559
512
|
<div className="flex items-center gap-2">
|
|
560
|
-
<Button
|
|
561
|
-
variant="outline"
|
|
562
|
-
size="sm"
|
|
563
|
-
onClick={() => copyToClipboard(recoveryCodes.join("\n"))}
|
|
564
|
-
>
|
|
513
|
+
<Button variant="outline" size="sm" onClick={() => copyToClipboard(recoveryCodes.join("\n"))}>
|
|
565
514
|
<CopyIcon className="mr-1 size-4" />
|
|
566
515
|
{copied ? "Copied" : "Copy all"}
|
|
567
516
|
</Button>
|
|
@@ -589,8 +538,7 @@ function TwoFactorSection() {
|
|
|
589
538
|
<DialogHeader>
|
|
590
539
|
<DialogTitle>Disable two-factor authentication</DialogTitle>
|
|
591
540
|
<DialogDescription>
|
|
592
|
-
Enter your current authenticator code to confirm. This will remove 2FA protection from
|
|
593
|
-
your account.
|
|
541
|
+
Enter your current authenticator code to confirm. This will remove 2FA protection from your account.
|
|
594
542
|
</DialogDescription>
|
|
595
543
|
</DialogHeader>
|
|
596
544
|
<div className="rounded-sm border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
|
|
@@ -622,11 +570,7 @@ function TwoFactorSection() {
|
|
|
622
570
|
<DialogClose asChild>
|
|
623
571
|
<Button variant="outline">Cancel</Button>
|
|
624
572
|
</DialogClose>
|
|
625
|
-
<Button
|
|
626
|
-
variant="destructive"
|
|
627
|
-
disabled={disableCode.length !== 6 || disabling}
|
|
628
|
-
onClick={handleDisable}
|
|
629
|
-
>
|
|
573
|
+
<Button variant="destructive" disabled={disableCode.length !== 6 || disabling} onClick={handleDisable}>
|
|
630
574
|
{disabling ? "Disabling..." : "Disable 2FA"}
|
|
631
575
|
</Button>
|
|
632
576
|
</DialogFooter>
|
|
@@ -641,8 +585,7 @@ function TwoFactorSection() {
|
|
|
641
585
|
<DialogHeader>
|
|
642
586
|
<DialogTitle>Regenerate recovery codes</DialogTitle>
|
|
643
587
|
<DialogDescription>
|
|
644
|
-
Enter your authenticator code to generate new recovery codes. This will invalidate
|
|
645
|
-
all previous codes.
|
|
588
|
+
Enter your authenticator code to generate new recovery codes. This will invalidate all previous codes.
|
|
646
589
|
</DialogDescription>
|
|
647
590
|
</DialogHeader>
|
|
648
591
|
<div className="flex flex-col items-center gap-3">
|
|
@@ -701,11 +644,7 @@ function TwoFactorSection() {
|
|
|
701
644
|
))}
|
|
702
645
|
</div>
|
|
703
646
|
<div className="flex items-center gap-2">
|
|
704
|
-
<Button
|
|
705
|
-
variant="outline"
|
|
706
|
-
size="sm"
|
|
707
|
-
onClick={() => copyToClipboard(regenCodes.join("\n"))}
|
|
708
|
-
>
|
|
647
|
+
<Button variant="outline" size="sm" onClick={() => copyToClipboard(regenCodes.join("\n"))}>
|
|
709
648
|
<CopyIcon className="mr-1 size-4" />
|
|
710
649
|
{copied ? "Copied" : "Copy all"}
|
|
711
650
|
</Button>
|
|
@@ -811,12 +750,7 @@ function SessionsSection() {
|
|
|
811
750
|
<CardDescription>Devices currently signed into your account</CardDescription>
|
|
812
751
|
</div>
|
|
813
752
|
{!loading && sessions.length > 1 && (
|
|
814
|
-
<Button
|
|
815
|
-
variant="destructive"
|
|
816
|
-
size="sm"
|
|
817
|
-
disabled={revokingAll}
|
|
818
|
-
onClick={handleRevokeAll}
|
|
819
|
-
>
|
|
753
|
+
<Button variant="destructive" size="sm" disabled={revokingAll} onClick={handleRevokeAll}>
|
|
820
754
|
{revokingAll ? "Revoking..." : "Revoke all other sessions"}
|
|
821
755
|
</Button>
|
|
822
756
|
)}
|
|
@@ -883,20 +817,14 @@ function SessionsSection() {
|
|
|
883
817
|
{session.current && <Badge variant="terminal">Current</Badge>}
|
|
884
818
|
</div>
|
|
885
819
|
</TableCell>
|
|
886
|
-
<TableCell className="text-sm text-muted-foreground">
|
|
887
|
-
{session.ipAddress ?? "\u2014"}
|
|
888
|
-
</TableCell>
|
|
820
|
+
<TableCell className="text-sm text-muted-foreground">{session.ipAddress ?? "\u2014"}</TableCell>
|
|
889
821
|
<TableCell>
|
|
890
822
|
<Tooltip>
|
|
891
823
|
<TooltipTrigger className="text-xs text-muted-foreground">
|
|
892
|
-
{relativeTime(
|
|
893
|
-
String(session.updatedAt ?? session.createdAt ?? session.expiresAt),
|
|
894
|
-
)}
|
|
824
|
+
{relativeTime(String(session.updatedAt ?? session.createdAt ?? session.expiresAt))}
|
|
895
825
|
</TooltipTrigger>
|
|
896
826
|
<TooltipContent>
|
|
897
|
-
{new Date(
|
|
898
|
-
session.updatedAt ?? session.createdAt ?? session.expiresAt,
|
|
899
|
-
).toLocaleString()}
|
|
827
|
+
{new Date(session.updatedAt ?? session.createdAt ?? session.expiresAt).toLocaleString()}
|
|
900
828
|
</TooltipContent>
|
|
901
829
|
</Tooltip>
|
|
902
830
|
</TableCell>
|
|
@@ -1003,9 +931,7 @@ function LoginHistorySection() {
|
|
|
1003
931
|
<Card>
|
|
1004
932
|
<CardHeader>
|
|
1005
933
|
<CardTitle>Login History</CardTitle>
|
|
1006
|
-
<CardDescription>
|
|
1007
|
-
{data ? `${data.total} total events` : "Recent authentication events"}
|
|
1008
|
-
</CardDescription>
|
|
934
|
+
<CardDescription>{data ? `${data.total} total events` : "Recent authentication events"}</CardDescription>
|
|
1009
935
|
</CardHeader>
|
|
1010
936
|
<CardContent>
|
|
1011
937
|
{loading ? (
|
|
@@ -1057,18 +983,12 @@ function LoginHistorySection() {
|
|
|
1057
983
|
<TooltipTrigger className="text-xs text-muted-foreground">
|
|
1058
984
|
{relativeTime(attempt.timestamp)}
|
|
1059
985
|
</TooltipTrigger>
|
|
1060
|
-
<TooltipContent>
|
|
1061
|
-
{new Date(attempt.timestamp).toLocaleString()}
|
|
1062
|
-
</TooltipContent>
|
|
986
|
+
<TooltipContent>{new Date(attempt.timestamp).toLocaleString()}</TooltipContent>
|
|
1063
987
|
</Tooltip>
|
|
1064
988
|
</TableCell>
|
|
1065
|
-
<TableCell className="text-sm text-muted-foreground">
|
|
1066
|
-
{parseBrowser(attempt.userAgent)}
|
|
1067
|
-
</TableCell>
|
|
989
|
+
<TableCell className="text-sm text-muted-foreground">{parseBrowser(attempt.userAgent)}</TableCell>
|
|
1068
990
|
<TableCell className="text-sm text-muted-foreground">{attempt.ip}</TableCell>
|
|
1069
|
-
<TableCell className="text-sm text-muted-foreground">
|
|
1070
|
-
{attempt.location ?? "\u2014"}
|
|
1071
|
-
</TableCell>
|
|
991
|
+
<TableCell className="text-sm text-muted-foreground">{attempt.location ?? "\u2014"}</TableCell>
|
|
1072
992
|
<TableCell>
|
|
1073
993
|
{attempt.success ? (
|
|
1074
994
|
<Badge variant="terminal">Success</Badge>
|
|
@@ -11,14 +11,7 @@ import { Label } from "@/components/ui/label";
|
|
|
11
11
|
import { Separator } from "@/components/ui/separator";
|
|
12
12
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
13
13
|
import { Switch } from "@/components/ui/switch";
|
|
14
|
-
import {
|
|
15
|
-
Table,
|
|
16
|
-
TableBody,
|
|
17
|
-
TableCell,
|
|
18
|
-
TableHead,
|
|
19
|
-
TableHeader,
|
|
20
|
-
TableRow,
|
|
21
|
-
} from "@/components/ui/table";
|
|
14
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
22
15
|
import { Textarea } from "@/components/ui/textarea";
|
|
23
16
|
import { trpc } from "@/lib/trpc";
|
|
24
17
|
import { cn } from "@/lib/utils";
|
|
@@ -171,10 +164,7 @@ export function EmailTemplatesClient() {
|
|
|
171
164
|
);
|
|
172
165
|
}, [templates, search]);
|
|
173
166
|
|
|
174
|
-
const selectedTemplate = useMemo(
|
|
175
|
-
() => templates.find((t) => t.id === selectedId) ?? null,
|
|
176
|
-
[templates, selectedId],
|
|
177
|
-
);
|
|
167
|
+
const selectedTemplate = useMemo(() => templates.find((t) => t.id === selectedId) ?? null, [templates, selectedId]);
|
|
178
168
|
|
|
179
169
|
// --- Handlers ---
|
|
180
170
|
|
|
@@ -201,9 +191,7 @@ export function EmailTemplatesClient() {
|
|
|
201
191
|
editActive !== selectedTemplate.active;
|
|
202
192
|
|
|
203
193
|
if (isDirty) {
|
|
204
|
-
const confirmed = window.confirm(
|
|
205
|
-
"You have unsaved changes. Are you sure you want to close without saving?",
|
|
206
|
-
);
|
|
194
|
+
const confirmed = window.confirm("You have unsaved changes. Are you sure you want to close without saving?");
|
|
207
195
|
if (!confirmed) return;
|
|
208
196
|
}
|
|
209
197
|
}
|
|
@@ -224,15 +212,7 @@ export function EmailTemplatesClient() {
|
|
|
224
212
|
textBody: editTextBody,
|
|
225
213
|
active: editActive,
|
|
226
214
|
});
|
|
227
|
-
}, [
|
|
228
|
-
selectedId,
|
|
229
|
-
editDescription,
|
|
230
|
-
editSubject,
|
|
231
|
-
editHtmlBody,
|
|
232
|
-
editTextBody,
|
|
233
|
-
editActive,
|
|
234
|
-
updateMutation,
|
|
235
|
-
]);
|
|
215
|
+
}, [selectedId, editDescription, editSubject, editHtmlBody, editTextBody, editActive, updateMutation]);
|
|
236
216
|
|
|
237
217
|
const handlePreview = useCallback(() => {
|
|
238
218
|
if (!selectedTemplate) return;
|
|
@@ -289,9 +269,7 @@ export function EmailTemplatesClient() {
|
|
|
289
269
|
if (listQuery.isError) {
|
|
290
270
|
return (
|
|
291
271
|
<div className="flex h-40 flex-col items-center justify-center gap-3">
|
|
292
|
-
<p className="text-sm text-destructive font-mono">
|
|
293
|
-
Failed to load email templates. Please try again.
|
|
294
|
-
</p>
|
|
272
|
+
<p className="text-sm text-destructive font-mono">Failed to load email templates. Please try again.</p>
|
|
295
273
|
<Button variant="outline" size="sm" onClick={() => listQuery.refetch()}>
|
|
296
274
|
Retry
|
|
297
275
|
</Button>
|
|
@@ -321,9 +299,7 @@ export function EmailTemplatesClient() {
|
|
|
321
299
|
<Separator orientation="vertical" className="h-6" />
|
|
322
300
|
<div className="flex items-center gap-2">
|
|
323
301
|
<MailCheck className="size-4 text-amber-400" />
|
|
324
|
-
<h2 className="text-lg font-bold uppercase tracking-wider">
|
|
325
|
-
{selectedTemplate.name}
|
|
326
|
-
</h2>
|
|
302
|
+
<h2 className="text-lg font-bold uppercase tracking-wider">{selectedTemplate.name}</h2>
|
|
327
303
|
</div>
|
|
328
304
|
</div>
|
|
329
305
|
<div className="flex items-center gap-2">
|
|
@@ -356,8 +332,7 @@ export function EmailTemplatesClient() {
|
|
|
356
332
|
<CardTitle className="text-base">Template Settings</CardTitle>
|
|
357
333
|
<CardDescription>
|
|
358
334
|
Edit the template content. Use Handlebars syntax (
|
|
359
|
-
<code className="text-xs font-mono text-amber-400">{"{{variableName}}"}</code>)
|
|
360
|
-
for dynamic values.
|
|
335
|
+
<code className="text-xs font-mono text-amber-400">{"{{variableName}}"}</code>) for dynamic values.
|
|
361
336
|
</CardDescription>
|
|
362
337
|
</CardHeader>
|
|
363
338
|
<CardContent className="space-y-5">
|
|
@@ -413,9 +388,7 @@ export function EmailTemplatesClient() {
|
|
|
413
388
|
<div className="flex items-center justify-between rounded-md border border-border p-3">
|
|
414
389
|
<div className="space-y-0.5">
|
|
415
390
|
<Label>Active</Label>
|
|
416
|
-
<p className="text-xs text-muted-foreground">
|
|
417
|
-
Inactive templates will not be sent.
|
|
418
|
-
</p>
|
|
391
|
+
<p className="text-xs text-muted-foreground">Inactive templates will not be sent.</p>
|
|
419
392
|
</div>
|
|
420
393
|
<Switch
|
|
421
394
|
checked={editActive}
|
|
@@ -430,18 +403,12 @@ export function EmailTemplatesClient() {
|
|
|
430
403
|
<Card className="border-border">
|
|
431
404
|
<CardHeader>
|
|
432
405
|
<CardTitle className="text-base">Available Variables</CardTitle>
|
|
433
|
-
<CardDescription>
|
|
434
|
-
Use these in your template with Handlebars syntax.
|
|
435
|
-
</CardDescription>
|
|
406
|
+
<CardDescription>Use these in your template with Handlebars syntax.</CardDescription>
|
|
436
407
|
</CardHeader>
|
|
437
408
|
<CardContent>
|
|
438
409
|
<div className="flex flex-wrap gap-2">
|
|
439
410
|
{variables.map((v) => (
|
|
440
|
-
<Badge
|
|
441
|
-
key={v}
|
|
442
|
-
variant="outline"
|
|
443
|
-
className="border-amber-500/30 text-amber-400 font-mono text-xs"
|
|
444
|
-
>
|
|
411
|
+
<Badge key={v} variant="outline" className="border-amber-500/30 text-amber-400 font-mono text-xs">
|
|
445
412
|
{`{{${v}}}`}
|
|
446
413
|
</Badge>
|
|
447
414
|
))}
|
|
@@ -452,25 +419,19 @@ export function EmailTemplatesClient() {
|
|
|
452
419
|
|
|
453
420
|
{/* Right: preview panel */}
|
|
454
421
|
<div className="w-[480px] shrink-0 border-l border-border overflow-auto p-6 space-y-4">
|
|
455
|
-
<h3 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
|
|
456
|
-
Preview
|
|
457
|
-
</h3>
|
|
422
|
+
<h3 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">Preview</h3>
|
|
458
423
|
|
|
459
424
|
{previewHtml === null && previewSubject === null ? (
|
|
460
425
|
<div className="flex flex-col items-center justify-center h-64 text-muted-foreground">
|
|
461
426
|
<Eye className="size-8 mb-3 opacity-40" />
|
|
462
|
-
<p className="text-sm font-mono">
|
|
463
|
-
Click "Preview" to render the template
|
|
464
|
-
</p>
|
|
427
|
+
<p className="text-sm font-mono">Click "Preview" to render the template</p>
|
|
465
428
|
</div>
|
|
466
429
|
) : (
|
|
467
430
|
<div className="space-y-4">
|
|
468
431
|
{/* Rendered subject */}
|
|
469
432
|
{previewSubject !== null && (
|
|
470
433
|
<div className="space-y-1">
|
|
471
|
-
<span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
472
|
-
Subject
|
|
473
|
-
</span>
|
|
434
|
+
<span className="text-xs font-medium uppercase tracking-wider text-muted-foreground">Subject</span>
|
|
474
435
|
<p className="text-sm font-medium rounded-md border border-border bg-black/20 px-3 py-2">
|
|
475
436
|
{previewSubject}
|
|
476
437
|
</p>
|
|
@@ -580,9 +541,7 @@ export function EmailTemplatesClient() {
|
|
|
580
541
|
<span className="text-sm font-medium font-mono">{template.name}</span>
|
|
581
542
|
</TableCell>
|
|
582
543
|
<TableCell>
|
|
583
|
-
<span className="text-sm text-muted-foreground line-clamp-1">
|
|
584
|
-
{template.description ?? "--"}
|
|
585
|
-
</span>
|
|
544
|
+
<span className="text-sm text-muted-foreground line-clamp-1">{template.description ?? "--"}</span>
|
|
586
545
|
</TableCell>
|
|
587
546
|
<TableCell>
|
|
588
547
|
<span className="text-sm text-muted-foreground font-mono line-clamp-1 max-w-[200px]">
|
|
@@ -594,9 +553,7 @@ export function EmailTemplatesClient() {
|
|
|
594
553
|
variant="outline"
|
|
595
554
|
className={cn(
|
|
596
555
|
"text-xs",
|
|
597
|
-
template.active
|
|
598
|
-
? "border-amber-500/30 text-amber-400"
|
|
599
|
-
: "border-border text-muted-foreground",
|
|
556
|
+
template.active ? "border-amber-500/30 text-amber-400" : "border-border text-muted-foreground",
|
|
600
557
|
)}
|
|
601
558
|
>
|
|
602
559
|
{template.active ? "Active" : "Inactive"}
|
package/src/app/admin/error.tsx
CHANGED
|
@@ -8,13 +8,7 @@ import { logger } from "@/lib/logger";
|
|
|
8
8
|
|
|
9
9
|
const log = logger("error-boundary:admin");
|
|
10
10
|
|
|
11
|
-
export default function AdminError({
|
|
12
|
-
error,
|
|
13
|
-
reset,
|
|
14
|
-
}: {
|
|
15
|
-
error: Error & { digest?: string };
|
|
16
|
-
reset: () => void;
|
|
17
|
-
}) {
|
|
11
|
+
export default function AdminError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
18
12
|
const [showDetails, setShowDetails] = useState(false);
|
|
19
13
|
const isDev = process.env.NODE_ENV === "development";
|
|
20
14
|
|
|
@@ -8,13 +8,7 @@ import { logger } from "@/lib/logger";
|
|
|
8
8
|
|
|
9
9
|
const log = logger("error-boundary:fleet-updates");
|
|
10
10
|
|
|
11
|
-
export default function FleetUpdatesError({
|
|
12
|
-
error,
|
|
13
|
-
reset,
|
|
14
|
-
}: {
|
|
15
|
-
error: Error & { digest?: string };
|
|
16
|
-
reset: () => void;
|
|
17
|
-
}) {
|
|
11
|
+
export default function FleetUpdatesError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
18
12
|
const [showDetails, setShowDetails] = useState(false);
|
|
19
13
|
const isDev = process.env.NODE_ENV === "development";
|
|
20
14
|
|
|
@@ -5,22 +5,9 @@ import { toast } from "sonner";
|
|
|
5
5
|
import { Badge } from "@/components/ui/badge";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
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 { Skeleton } from "@/components/ui/skeleton";
|
|
16
|
-
import {
|
|
17
|
-
Table,
|
|
18
|
-
TableBody,
|
|
19
|
-
TableCell,
|
|
20
|
-
TableHead,
|
|
21
|
-
TableHeader,
|
|
22
|
-
TableRow,
|
|
23
|
-
} from "@/components/ui/table";
|
|
10
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
24
11
|
import { trpc } from "@/lib/trpc";
|
|
25
12
|
|
|
26
13
|
/* ------------------------------------------------------------------ */
|
|
@@ -121,11 +108,7 @@ function RolloutStatusCard() {
|
|
|
121
108
|
disabled={forceRolloutMutation.isPending || isRolling}
|
|
122
109
|
className="font-mono text-xs ml-auto"
|
|
123
110
|
>
|
|
124
|
-
{forceRolloutMutation.isPending ?
|
|
125
|
-
<Loader2 size={12} className="animate-spin" />
|
|
126
|
-
) : (
|
|
127
|
-
<Rocket size={12} />
|
|
128
|
-
)}
|
|
111
|
+
{forceRolloutMutation.isPending ? <Loader2 size={12} className="animate-spin" /> : <Rocket size={12} />}
|
|
129
112
|
Force Rollout
|
|
130
113
|
</Button>
|
|
131
114
|
</div>
|
|
@@ -139,13 +122,7 @@ function RolloutStatusCard() {
|
|
|
139
122
|
/* Tenant Config Row */
|
|
140
123
|
/* ------------------------------------------------------------------ */
|
|
141
124
|
|
|
142
|
-
function TenantConfigRow({
|
|
143
|
-
config,
|
|
144
|
-
onModeChanged,
|
|
145
|
-
}: {
|
|
146
|
-
config: TenantConfig;
|
|
147
|
-
onModeChanged: () => void;
|
|
148
|
-
}) {
|
|
125
|
+
function TenantConfigRow({ config, onModeChanged }: { config: TenantConfig; onModeChanged: () => void }) {
|
|
149
126
|
const setConfigMutation = trpc.adminFleetUpdate.setTenantConfig.useMutation({
|
|
150
127
|
onSuccess: () => {
|
|
151
128
|
toast.success(`Tenant ${config.tenantId} config updated.`);
|
|
@@ -169,11 +146,7 @@ function TenantConfigRow({
|
|
|
169
146
|
<code className="text-xs text-muted-foreground">{config.tenantId}</code>
|
|
170
147
|
</TableCell>
|
|
171
148
|
<TableCell>
|
|
172
|
-
<Select
|
|
173
|
-
value={config.mode}
|
|
174
|
-
onValueChange={handleModeChange}
|
|
175
|
-
disabled={setConfigMutation.isPending}
|
|
176
|
-
>
|
|
149
|
+
<Select value={config.mode} onValueChange={handleModeChange} disabled={setConfigMutation.isPending}>
|
|
177
150
|
<SelectTrigger className="h-7 w-28 text-xs">
|
|
178
151
|
<SelectValue />
|
|
179
152
|
</SelectTrigger>
|
|
@@ -193,9 +166,7 @@ function TenantConfigRow({
|
|
|
193
166
|
</span>
|
|
194
167
|
</TableCell>
|
|
195
168
|
<TableCell>
|
|
196
|
-
<span className="text-xs text-muted-foreground">
|
|
197
|
-
{new Date(config.updatedAt).toLocaleDateString()}
|
|
198
|
-
</span>
|
|
169
|
+
<span className="text-xs text-muted-foreground">{new Date(config.updatedAt).toLocaleDateString()}</span>
|
|
199
170
|
</TableCell>
|
|
200
171
|
</TableRow>
|
|
201
172
|
);
|
|
@@ -237,9 +208,7 @@ export function FleetUpdatesClient() {
|
|
|
237
208
|
{/* Tenant Configs Table */}
|
|
238
209
|
<div className="mx-6 bg-card border border-border rounded-sm">
|
|
239
210
|
<div className="px-4 py-3 border-b border-border flex items-center justify-between">
|
|
240
|
-
<div className="text-xs uppercase tracking-widest text-muted-foreground">
|
|
241
|
-
Tenant Update Configs
|
|
242
|
-
</div>
|
|
211
|
+
<div className="text-xs uppercase tracking-widest text-muted-foreground">Tenant Update Configs</div>
|
|
243
212
|
<Button
|
|
244
213
|
type="button"
|
|
245
214
|
variant="ghost"
|
|
@@ -273,29 +242,20 @@ export function FleetUpdatesClient() {
|
|
|
273
242
|
<TableRow className="bg-muted/40">
|
|
274
243
|
<TableHead className="text-xs uppercase tracking-wider">Tenant ID</TableHead>
|
|
275
244
|
<TableHead className="text-xs uppercase tracking-wider">Mode</TableHead>
|
|
276
|
-
<TableHead className="text-xs uppercase tracking-wider">
|
|
277
|
-
Preferred Hour (UTC)
|
|
278
|
-
</TableHead>
|
|
245
|
+
<TableHead className="text-xs uppercase tracking-wider">Preferred Hour (UTC)</TableHead>
|
|
279
246
|
<TableHead className="text-xs uppercase tracking-wider">Last Updated</TableHead>
|
|
280
247
|
</TableRow>
|
|
281
248
|
</TableHeader>
|
|
282
249
|
<TableBody>
|
|
283
250
|
{configs.length === 0 ? (
|
|
284
251
|
<TableRow>
|
|
285
|
-
<TableCell
|
|
286
|
-
colSpan={4}
|
|
287
|
-
className="text-center text-muted-foreground py-8 text-sm font-mono"
|
|
288
|
-
>
|
|
252
|
+
<TableCell colSpan={4} className="text-center text-muted-foreground py-8 text-sm font-mono">
|
|
289
253
|
> No tenant configs found.
|
|
290
254
|
</TableCell>
|
|
291
255
|
</TableRow>
|
|
292
256
|
) : (
|
|
293
257
|
configs.map((cfg) => (
|
|
294
|
-
<TenantConfigRow
|
|
295
|
-
key={cfg.tenantId}
|
|
296
|
-
config={cfg}
|
|
297
|
-
onModeChanged={handleConfigChanged}
|
|
298
|
-
/>
|
|
258
|
+
<TenantConfigRow key={cfg.tenantId} config={cfg} onModeChanged={handleConfigChanged} />
|
|
299
259
|
))
|
|
300
260
|
)}
|
|
301
261
|
</TableBody>
|
package/src/app/admin/layout.tsx
CHANGED
|
@@ -5,10 +5,14 @@ import { usePathname } from "next/navigation";
|
|
|
5
5
|
import { AdminGuard } from "@/components/admin/admin-guard";
|
|
6
6
|
import { AdminNav } from "@/components/admin/admin-nav";
|
|
7
7
|
import { Sidebar } from "@/components/sidebar";
|
|
8
|
+
import { useRequireAdmin } from "@/lib/require-auth";
|
|
8
9
|
|
|
9
10
|
export default function AdminLayout({ children }: { children: React.ReactNode }) {
|
|
11
|
+
const { isPending, isAuthed } = useRequireAdmin();
|
|
10
12
|
const pathname = usePathname();
|
|
11
13
|
|
|
14
|
+
if (isPending || !isAuthed) return null;
|
|
15
|
+
|
|
12
16
|
return (
|
|
13
17
|
<>
|
|
14
18
|
{/* Desktop only -- admin requires 1024px+ */}
|