@wopr-network/platform-ui-core 1.27.7 → 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 +14 -17
- 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
|
@@ -8,6 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
8
8
|
import { Skeleton } from "@/components/ui/skeleton";
|
|
9
9
|
import type { CreditOption } from "@/lib/api";
|
|
10
10
|
import { createCreditCheckout, getCreditOptions } from "@/lib/api";
|
|
11
|
+
import { getBrandConfig } from "@/lib/brand-config";
|
|
11
12
|
import { logger } from "@/lib/logger";
|
|
12
13
|
import { cn } from "@/lib/utils";
|
|
13
14
|
import { isAllowedRedirectUrl } from "@/lib/validate-redirect-url";
|
|
@@ -84,7 +85,9 @@ export function BuyCreditsPanel() {
|
|
|
84
85
|
<CardHeader>
|
|
85
86
|
<CardTitle>Buy Credits</CardTitle>
|
|
86
87
|
<p className="text-xs text-muted-foreground">
|
|
87
|
-
|
|
88
|
+
{getBrandConfig().dividendsEnabled
|
|
89
|
+
? "Every purchase resets your 7-day dividend window"
|
|
90
|
+
: "Top up your credit balance"}
|
|
88
91
|
</p>
|
|
89
92
|
</CardHeader>
|
|
90
93
|
<CardContent>
|
|
@@ -104,7 +107,9 @@ export function BuyCreditsPanel() {
|
|
|
104
107
|
<CardHeader>
|
|
105
108
|
<CardTitle>Buy Credits</CardTitle>
|
|
106
109
|
<p className="text-xs text-muted-foreground">
|
|
107
|
-
|
|
110
|
+
{getBrandConfig().dividendsEnabled
|
|
111
|
+
? "Every purchase resets your 7-day dividend window"
|
|
112
|
+
: "Top up your credit balance"}
|
|
108
113
|
</p>
|
|
109
114
|
</CardHeader>
|
|
110
115
|
<CardContent className="space-y-3">
|
|
@@ -123,13 +128,13 @@ export function BuyCreditsPanel() {
|
|
|
123
128
|
<CardHeader>
|
|
124
129
|
<CardTitle>Buy Credits</CardTitle>
|
|
125
130
|
<p className="text-xs text-muted-foreground">
|
|
126
|
-
|
|
131
|
+
{getBrandConfig().dividendsEnabled
|
|
132
|
+
? "Every purchase resets your 7-day dividend window"
|
|
133
|
+
: "Top up your credit balance"}
|
|
127
134
|
</p>
|
|
128
135
|
</CardHeader>
|
|
129
136
|
<CardContent>
|
|
130
|
-
<p className="text-sm text-muted-foreground">
|
|
131
|
-
Credit purchases are not available at this time.
|
|
132
|
-
</p>
|
|
137
|
+
<p className="text-sm text-muted-foreground">Credit purchases are not available at this time.</p>
|
|
133
138
|
</CardContent>
|
|
134
139
|
</Card>
|
|
135
140
|
);
|
|
@@ -139,9 +144,7 @@ export function BuyCreditsPanel() {
|
|
|
139
144
|
<Card>
|
|
140
145
|
<CardHeader>
|
|
141
146
|
<CardTitle>Buy Credits</CardTitle>
|
|
142
|
-
<p className="text-xs text-muted-foreground">
|
|
143
|
-
Every purchase resets your 7-day dividend window
|
|
144
|
-
</p>
|
|
147
|
+
<p className="text-xs text-muted-foreground">Every purchase resets your 7-day dividend window</p>
|
|
145
148
|
</CardHeader>
|
|
146
149
|
<CardContent className="space-y-4">
|
|
147
150
|
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-5">
|
|
@@ -162,19 +165,13 @@ export function BuyCreditsPanel() {
|
|
|
162
165
|
>
|
|
163
166
|
<span className="text-lg font-bold">{tier.label}</span>
|
|
164
167
|
{tier.bonusPercent > 0 && (
|
|
165
|
-
<Badge className="bg-primary/15 text-primary border-primary/25 text-xs">
|
|
166
|
-
+{tier.bonusPercent}%
|
|
167
|
-
</Badge>
|
|
168
|
+
<Badge className="bg-primary/15 text-primary border-primary/25 text-xs">+{tier.bonusPercent}%</Badge>
|
|
168
169
|
)}
|
|
169
170
|
</motion.button>
|
|
170
171
|
))}
|
|
171
172
|
</div>
|
|
172
173
|
{error && <p className="text-sm text-destructive">{error}</p>}
|
|
173
|
-
<Button
|
|
174
|
-
onClick={handleCheckout}
|
|
175
|
-
disabled={selected === null || loading}
|
|
176
|
-
className="w-full sm:w-auto"
|
|
177
|
-
>
|
|
174
|
+
<Button onClick={handleCheckout} disabled={selected === null || loading} className="w-full sm:w-auto">
|
|
178
175
|
{loading ? "Redirecting..." : "Buy credits"}
|
|
179
176
|
</Button>
|
|
180
177
|
</CardContent>
|
|
@@ -34,8 +34,7 @@ export function ByokCallout({ compact }: { compact?: boolean }) {
|
|
|
34
34
|
if (compact) {
|
|
35
35
|
return (
|
|
36
36
|
<p className="text-xs text-muted-foreground">
|
|
37
|
-
All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your
|
|
38
|
-
inference.
|
|
37
|
+
All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your inference.
|
|
39
38
|
</p>
|
|
40
39
|
);
|
|
41
40
|
}
|
|
@@ -49,8 +48,8 @@ export function ByokCallout({ compact }: { compact?: boolean }) {
|
|
|
49
48
|
<div className="space-y-1">
|
|
50
49
|
<p className="text-sm font-medium">Bring Your Own Keys</p>
|
|
51
50
|
<p className="text-sm text-muted-foreground">
|
|
52
|
-
All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your
|
|
53
|
-
|
|
51
|
+
All plans are BYOK — you pay your AI provider directly. {brandName()} never touches your inference. We only
|
|
52
|
+
charge for the orchestration layer: containers, plugins, and support.
|
|
54
53
|
</p>
|
|
55
54
|
</div>
|
|
56
55
|
</CardContent>
|
|
@@ -62,8 +61,7 @@ function HostedCallout({ compact }: { compact?: boolean }) {
|
|
|
62
61
|
if (compact) {
|
|
63
62
|
return (
|
|
64
63
|
<p className="text-xs text-muted-foreground">
|
|
65
|
-
Hosted adapter — transparent per-use pricing with no markup surprises. Switch to BYOK
|
|
66
|
-
anytime in settings.
|
|
64
|
+
Hosted adapter — transparent per-use pricing with no markup surprises. Switch to BYOK anytime in settings.
|
|
67
65
|
</p>
|
|
68
66
|
);
|
|
69
67
|
}
|
|
@@ -77,8 +75,8 @@ function HostedCallout({ compact }: { compact?: boolean }) {
|
|
|
77
75
|
<div className="space-y-1">
|
|
78
76
|
<p className="text-sm font-medium">Hosted AI Adapter</p>
|
|
79
77
|
<p className="text-sm text-muted-foreground">
|
|
80
|
-
BYOK users pay their providers directly. Hosted users pay per use — transparent pricing,
|
|
81
|
-
|
|
78
|
+
BYOK users pay their providers directly. Hosted users pay per use — transparent pricing, no markup
|
|
79
|
+
surprises. Each plan includes a monthly hosted credit.
|
|
82
80
|
</p>
|
|
83
81
|
</div>
|
|
84
82
|
</CardContent>
|
|
@@ -17,17 +17,12 @@ export function ConfirmationTracker({
|
|
|
17
17
|
credited,
|
|
18
18
|
txHash,
|
|
19
19
|
}: ConfirmationTrackerProps) {
|
|
20
|
-
const pct =
|
|
21
|
-
confirmationsRequired > 0
|
|
22
|
-
? Math.min(100, Math.round((confirmations / confirmationsRequired) * 100))
|
|
23
|
-
: 0;
|
|
20
|
+
const pct = confirmationsRequired > 0 ? Math.min(100, Math.round((confirmations / confirmationsRequired) * 100)) : 0;
|
|
24
21
|
const detected = confirmations > 0 || credited;
|
|
25
22
|
|
|
26
23
|
return (
|
|
27
24
|
<div className="space-y-4 text-center">
|
|
28
|
-
<p className="text-sm text-muted-foreground">
|
|
29
|
-
{credited ? "Payment complete!" : "Payment received!"}
|
|
30
|
-
</p>
|
|
25
|
+
<p className="text-sm text-muted-foreground">{credited ? "Payment complete!" : "Payment received!"}</p>
|
|
31
26
|
<p className="text-xl font-semibold">{displayAmount}</p>
|
|
32
27
|
|
|
33
28
|
<div className="rounded-lg border border-border p-3 space-y-2">
|
|
@@ -44,10 +39,7 @@ export function ConfirmationTracker({
|
|
|
44
39
|
aria-valuemin={0}
|
|
45
40
|
aria-valuemax={100}
|
|
46
41
|
>
|
|
47
|
-
<div
|
|
48
|
-
className="h-full rounded-full bg-primary transition-all duration-500"
|
|
49
|
-
style={{ width: `${pct}%` }}
|
|
50
|
-
/>
|
|
42
|
+
<div className="h-full rounded-full bg-primary transition-all duration-500" style={{ width: `${pct}%` }} />
|
|
51
43
|
</div>
|
|
52
44
|
</div>
|
|
53
45
|
|
|
@@ -58,9 +50,7 @@ export function ConfirmationTracker({
|
|
|
58
50
|
>
|
|
59
51
|
{detected && <Check className="h-2.5 w-2.5" />}
|
|
60
52
|
</div>
|
|
61
|
-
<span
|
|
62
|
-
className={`text-xs ${detected ? "text-muted-foreground" : "text-muted-foreground/50"}`}
|
|
63
|
-
>
|
|
53
|
+
<span className={`text-xs ${detected ? "text-muted-foreground" : "text-muted-foreground/50"}`}>
|
|
64
54
|
Payment detected
|
|
65
55
|
</span>
|
|
66
56
|
</div>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCreditBalance } from "@/hooks/use-credit-balance";
|
|
4
|
+
import { formatCreditStandard } from "@/lib/format-credit";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function balanceColorClass(balance: number): string {
|
|
8
|
+
if (balance <= 0) return "text-red-500";
|
|
9
|
+
if (balance <= 2) return "text-amber-500";
|
|
10
|
+
return "text-emerald-500";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Compact credit balance badge — use in sidebar, nav, etc. */
|
|
14
|
+
export function CreditBalanceBadge({ className }: { className?: string }) {
|
|
15
|
+
const { balance } = useCreditBalance();
|
|
16
|
+
if (balance == null) return null;
|
|
17
|
+
return (
|
|
18
|
+
<span className={cn("text-xs font-mono", balanceColorClass(balance), className)}>
|
|
19
|
+
{formatCreditStandard(balance)}
|
|
20
|
+
</span>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -53,19 +53,13 @@ export function CreditBalance({ data }: { data: CreditBalanceData }) {
|
|
|
53
53
|
<CardTitle>Credit Balance</CardTitle>
|
|
54
54
|
</CardHeader>
|
|
55
55
|
<CardContent className="space-y-4">
|
|
56
|
-
<div
|
|
57
|
-
className={cn("text-4xl font-bold font-mono", balanceColor(data.balance, data.runway))}
|
|
58
|
-
>
|
|
56
|
+
<div className={cn("text-4xl font-bold font-mono", balanceColor(data.balance, data.runway))}>
|
|
59
57
|
{formatCreditStandard(animatedBalance)}
|
|
60
58
|
</div>
|
|
61
59
|
<div className="flex flex-wrap gap-6 text-sm text-muted-foreground">
|
|
62
60
|
<div>
|
|
63
|
-
<span className="block text-xs uppercase tracking-wider text-primary/60">
|
|
64
|
-
|
|
65
|
-
</span>
|
|
66
|
-
<span className="font-medium text-foreground">
|
|
67
|
-
{formatCreditStandard(data.dailyBurn)}/day
|
|
68
|
-
</span>
|
|
61
|
+
<span className="block text-xs uppercase tracking-wider text-primary/60">Daily burn</span>
|
|
62
|
+
<span className="font-medium text-foreground">{formatCreditStandard(data.dailyBurn)}/day</span>
|
|
69
63
|
</div>
|
|
70
64
|
<div>
|
|
71
65
|
<span className="block text-xs uppercase tracking-wider text-primary/60">Runway</span>
|
|
@@ -51,10 +51,7 @@ export function CryptoCheckout() {
|
|
|
51
51
|
} else if (res.status === "expired" || res.status === "failed") {
|
|
52
52
|
setStatus(res.status as PaymentStatus);
|
|
53
53
|
clearInterval(interval);
|
|
54
|
-
} else if (
|
|
55
|
-
res.amountReceivedCents > 0 &&
|
|
56
|
-
res.amountReceivedCents >= res.amountExpectedCents
|
|
57
|
-
) {
|
|
54
|
+
} else if (res.amountReceivedCents > 0 && res.amountReceivedCents >= res.amountExpectedCents) {
|
|
58
55
|
setStatus("confirming");
|
|
59
56
|
setStep("confirming");
|
|
60
57
|
} else if (res.amountReceivedCents > 0) {
|
|
@@ -100,11 +97,7 @@ export function CryptoCheckout() {
|
|
|
100
97
|
if (methods.length === 0) return null;
|
|
101
98
|
|
|
102
99
|
return (
|
|
103
|
-
<motion.div
|
|
104
|
-
initial={{ opacity: 0, y: 8 }}
|
|
105
|
-
animate={{ opacity: 1, y: 0 }}
|
|
106
|
-
transition={{ duration: 0.3 }}
|
|
107
|
-
>
|
|
100
|
+
<motion.div initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3 }}>
|
|
108
101
|
<Card>
|
|
109
102
|
<CardHeader>
|
|
110
103
|
<CardTitle className="flex items-center gap-2">
|
|
@@ -131,16 +124,8 @@ export function CryptoCheckout() {
|
|
|
131
124
|
animate={{ opacity: 1, x: 0 }}
|
|
132
125
|
exit={{ opacity: 0, x: 20 }}
|
|
133
126
|
>
|
|
134
|
-
<PaymentMethodPicker
|
|
135
|
-
|
|
136
|
-
onSelect={handleMethod}
|
|
137
|
-
onBack={() => setStep("amount")}
|
|
138
|
-
/>
|
|
139
|
-
{loading && (
|
|
140
|
-
<p className="mt-2 text-xs text-muted-foreground animate-pulse">
|
|
141
|
-
Creating checkout...
|
|
142
|
-
</p>
|
|
143
|
-
)}
|
|
127
|
+
<PaymentMethodPicker methods={methods} onSelect={handleMethod} onBack={() => setStep("amount")} />
|
|
128
|
+
{loading && <p className="mt-2 text-xs text-muted-foreground animate-pulse">Creating checkout...</p>}
|
|
144
129
|
</motion.div>
|
|
145
130
|
)}
|
|
146
131
|
{step === "deposit" && checkout && (
|
|
@@ -167,11 +152,7 @@ export function CryptoCheckout() {
|
|
|
167
152
|
credited={status === "credited"}
|
|
168
153
|
/>
|
|
169
154
|
{status === "credited" && (
|
|
170
|
-
<button
|
|
171
|
-
type="button"
|
|
172
|
-
onClick={handleReset}
|
|
173
|
-
className="mt-4 text-sm text-primary hover:underline"
|
|
174
|
-
>
|
|
155
|
+
<button type="button" onClick={handleReset} className="mt-4 text-sm text-primary hover:underline">
|
|
175
156
|
Done — buy more credits
|
|
176
157
|
</button>
|
|
177
158
|
)}
|
|
@@ -38,9 +38,7 @@ export function DegradedStateBanner() {
|
|
|
38
38
|
<Banner variant="warning" role="alert">
|
|
39
39
|
<span className="flex-1">
|
|
40
40
|
ACTION REQUIRED —{" "}
|
|
41
|
-
{days !== null
|
|
42
|
-
? `${days} day${days === 1 ? "" : "s"} to resolve`
|
|
43
|
-
: "resolve soon to avoid suspension"}
|
|
41
|
+
{days !== null ? `${days} day${days === 1 ? "" : "s"} to resolve` : "resolve soon to avoid suspension"}
|
|
44
42
|
{reason ? ` (${reason})` : ""}
|
|
45
43
|
</span>
|
|
46
44
|
<Link href="/billing" className="font-semibold underline underline-offset-4">
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Check, Copy } from "lucide-react";
|
|
3
|
+
import { Check, Copy, Wallet } from "lucide-react";
|
|
4
4
|
import { QRCodeSVG } from "qrcode.react";
|
|
5
|
-
import { useCallback, useEffect, useState } from "react";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { Button } from "@/components/ui/button";
|
|
7
7
|
import type { CheckoutResult } from "@/lib/api";
|
|
8
8
|
|
|
@@ -10,11 +10,200 @@ interface DepositViewProps {
|
|
|
10
10
|
checkout: CheckoutResult;
|
|
11
11
|
status: "waiting" | "partial" | "confirming" | "credited" | "expired" | "failed";
|
|
12
12
|
onBack: () => void;
|
|
13
|
+
expectedAmount?: string | null;
|
|
14
|
+
receivedAmount?: string | null;
|
|
15
|
+
token?: string;
|
|
16
|
+
decimals?: number;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
function formatCrypto(raw: string, decimals: number): string {
|
|
20
|
+
const n = Number(raw) / 10 ** decimals;
|
|
21
|
+
return n.toFixed(Math.min(decimals, 8)).replace(/\.?0+$/, "");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Payment URI builder — triggers wallet apps on QR scan
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
const CHAIN_URI_SCHEMES: Record<string, string> = {
|
|
29
|
+
bitcoin: "bitcoin",
|
|
30
|
+
litecoin: "litecoin",
|
|
31
|
+
dogecoin: "dogecoin",
|
|
32
|
+
ethereum: "ethereum",
|
|
33
|
+
base: "ethereum",
|
|
34
|
+
"base-sepolia": "ethereum",
|
|
35
|
+
sepolia: "ethereum",
|
|
36
|
+
arbitrum: "ethereum",
|
|
37
|
+
optimism: "ethereum",
|
|
38
|
+
polygon: "ethereum",
|
|
39
|
+
avalanche: "ethereum",
|
|
40
|
+
solana: "solana",
|
|
41
|
+
tron: "tron",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function buildPaymentUri(chain: string, address: string, nativeAmount?: string | null, decimals?: number): string {
|
|
45
|
+
const scheme = CHAIN_URI_SCHEMES[chain.toLowerCase()];
|
|
46
|
+
if (!scheme) return address;
|
|
47
|
+
|
|
48
|
+
if (scheme === "bitcoin" || scheme === "litecoin" || scheme === "dogecoin") {
|
|
49
|
+
const d = decimals ?? 8;
|
|
50
|
+
const human = nativeAmount ? formatCrypto(nativeAmount, d) : undefined;
|
|
51
|
+
return human ? `${scheme}:${address}?amount=${human}` : `${scheme}:${address}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (scheme === "ethereum") {
|
|
55
|
+
return nativeAmount ? `${scheme}:${address}?value=${nativeAmount}` : `${scheme}:${address}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (scheme === "solana") {
|
|
59
|
+
const d = decimals ?? 9;
|
|
60
|
+
const human = nativeAmount ? formatCrypto(nativeAmount, d) : undefined;
|
|
61
|
+
return human ? `${scheme}:${address}?amount=${human}` : `${scheme}:${address}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return `${scheme}:${address}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Wallet detection + direct transaction submission
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
type WalletType = "metamask" | "solana" | "tron" | null;
|
|
72
|
+
|
|
73
|
+
function detectWallet(chain: string): WalletType {
|
|
74
|
+
if (typeof window === "undefined") return null;
|
|
75
|
+
const scheme = CHAIN_URI_SCHEMES[chain.toLowerCase()];
|
|
76
|
+
if (scheme === "ethereum" && (window as { ethereum?: unknown }).ethereum) return "metamask";
|
|
77
|
+
if (scheme === "solana" && (window as { solana?: { isPhantom?: boolean } }).solana?.isPhantom) return "solana";
|
|
78
|
+
if (scheme === "tron" && (window as { tronWeb?: unknown }).tronWeb) return "tron";
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function walletLabel(type: WalletType): string {
|
|
83
|
+
switch (type) {
|
|
84
|
+
case "metamask":
|
|
85
|
+
return "Pay with Wallet";
|
|
86
|
+
case "solana":
|
|
87
|
+
return "Pay with Phantom";
|
|
88
|
+
case "tron":
|
|
89
|
+
return "Pay with TronLink";
|
|
90
|
+
default:
|
|
91
|
+
return "Pay with Wallet";
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ERC-20 transfer(address,uint256) function selector
|
|
96
|
+
const TRANSFER_SELECTOR = "0xa9059cbb";
|
|
97
|
+
|
|
98
|
+
function encodeErc20Transfer(to: string, amount: string): string {
|
|
99
|
+
const addr = to.slice(2).toLowerCase().padStart(64, "0");
|
|
100
|
+
const val = BigInt(amount).toString(16).padStart(64, "0");
|
|
101
|
+
return `${TRANSFER_SELECTOR}${addr}${val}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
interface WalletTxOpts {
|
|
105
|
+
walletType: WalletType;
|
|
106
|
+
depositAddress: string;
|
|
107
|
+
amount: string | null;
|
|
108
|
+
tokenType?: string;
|
|
109
|
+
contractAddress?: string | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function sendViaWallet(opts: WalletTxOpts): Promise<string | null> {
|
|
113
|
+
const { walletType, depositAddress, amount, tokenType, contractAddress } = opts;
|
|
114
|
+
if (!amount) return null;
|
|
115
|
+
|
|
116
|
+
if (walletType === "metamask") {
|
|
117
|
+
// Find the REAL MetaMask provider — TronLink and other extensions also inject window.ethereum
|
|
118
|
+
type EthProvider = {
|
|
119
|
+
request: (args: { method: string; params?: unknown[] }) => Promise<unknown>;
|
|
120
|
+
isMetaMask?: boolean;
|
|
121
|
+
};
|
|
122
|
+
const root = (window as { ethereum?: EthProvider & { providers?: EthProvider[] } }).ethereum;
|
|
123
|
+
if (!root) return null;
|
|
124
|
+
const eth: EthProvider = root.providers?.find((p) => p.isMetaMask) ?? root;
|
|
125
|
+
|
|
126
|
+
// Check if already connected (non-prompting), only request if needed
|
|
127
|
+
let accounts = (await eth.request({ method: "eth_accounts" })) as string[];
|
|
128
|
+
if (!accounts[0]) {
|
|
129
|
+
accounts = (await eth.request({ method: "eth_requestAccounts" })) as string[];
|
|
130
|
+
}
|
|
131
|
+
if (!accounts[0]) return null;
|
|
132
|
+
|
|
133
|
+
// Build the single transaction object
|
|
134
|
+
const tx: Record<string, string> = { from: accounts[0] };
|
|
135
|
+
if (tokenType === "erc20" && contractAddress) {
|
|
136
|
+
tx.to = contractAddress;
|
|
137
|
+
tx.value = "0x0";
|
|
138
|
+
tx.data = encodeErc20Transfer(depositAddress, amount);
|
|
139
|
+
} else {
|
|
140
|
+
tx.to = depositAddress;
|
|
141
|
+
tx.value = `0x${BigInt(amount).toString(16)}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const txHash = (await eth.request({
|
|
145
|
+
method: "eth_sendTransaction",
|
|
146
|
+
params: [tx],
|
|
147
|
+
})) as string;
|
|
148
|
+
return txHash;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (walletType === "solana") {
|
|
152
|
+
// Solana requires @solana/web3.js for tx construction — fall back to URI
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (walletType === "tron") {
|
|
157
|
+
type TronWeb = {
|
|
158
|
+
trx: { sendTransaction: (to: string, amount: number) => Promise<{ txid: string }> };
|
|
159
|
+
contract: () => {
|
|
160
|
+
at: (addr: string) => Promise<{ transfer: (to: string, amount: string) => { send: () => Promise<string> } }>;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
const tw = (window as { tronWeb?: TronWeb }).tronWeb;
|
|
164
|
+
if (!tw) return null;
|
|
165
|
+
|
|
166
|
+
// TRC-20: call transfer() on the contract via tronWeb.contract()
|
|
167
|
+
if (tokenType === "erc20" && contractAddress) {
|
|
168
|
+
const contract = await tw.contract().at(contractAddress);
|
|
169
|
+
const txHash = await contract.transfer(depositAddress, amount).send();
|
|
170
|
+
return txHash;
|
|
171
|
+
}
|
|
172
|
+
const result = await tw.trx.sendTransaction(depositAddress, Number(amount));
|
|
173
|
+
return result.txid;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
// Component
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
export function DepositView({
|
|
184
|
+
checkout,
|
|
185
|
+
status,
|
|
186
|
+
onBack,
|
|
187
|
+
expectedAmount,
|
|
188
|
+
receivedAmount,
|
|
189
|
+
token,
|
|
190
|
+
decimals,
|
|
191
|
+
}: DepositViewProps) {
|
|
16
192
|
const [copied, setCopied] = useState(false);
|
|
17
|
-
const [
|
|
193
|
+
const [walletType, setWalletType] = useState<WalletType>(null);
|
|
194
|
+
const [sending, setSending] = useState(false);
|
|
195
|
+
const sendingRef = useRef(false);
|
|
196
|
+
const [walletError, setWalletError] = useState<string | null>(null);
|
|
197
|
+
const [txSent, setTxSent] = useState(false);
|
|
198
|
+
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
setWalletType(detectWallet(checkout.chain));
|
|
201
|
+
}, [checkout.chain]);
|
|
202
|
+
|
|
203
|
+
const paymentUri = useMemo(
|
|
204
|
+
() => buildPaymentUri(checkout.chain, checkout.depositAddress, expectedAmount, decimals),
|
|
205
|
+
[checkout.chain, checkout.depositAddress, expectedAmount, decimals],
|
|
206
|
+
);
|
|
18
207
|
|
|
19
208
|
const handleCopy = useCallback(() => {
|
|
20
209
|
navigator.clipboard.writeText(checkout.depositAddress);
|
|
@@ -22,48 +211,108 @@ export function DepositView({ checkout, status, onBack }: DepositViewProps) {
|
|
|
22
211
|
setTimeout(() => setCopied(false), 2000);
|
|
23
212
|
}, [checkout.depositAddress]);
|
|
24
213
|
|
|
25
|
-
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
214
|
+
const handleWalletPay = useCallback(async () => {
|
|
215
|
+
if (sendingRef.current) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
sendingRef.current = true;
|
|
219
|
+
setSending(true);
|
|
220
|
+
setWalletError(null);
|
|
221
|
+
try {
|
|
222
|
+
const amountToSend =
|
|
223
|
+
expectedAmount && receivedAmount
|
|
224
|
+
? String(BigInt(expectedAmount) - BigInt(receivedAmount))
|
|
225
|
+
: (checkout.expectedAmount ?? expectedAmount);
|
|
226
|
+
const txHash = await sendViaWallet({
|
|
227
|
+
walletType,
|
|
228
|
+
depositAddress: checkout.depositAddress,
|
|
229
|
+
amount: amountToSend ?? null,
|
|
230
|
+
tokenType: checkout.type,
|
|
231
|
+
contractAddress: checkout.contractAddress,
|
|
232
|
+
});
|
|
233
|
+
if (txHash) {
|
|
234
|
+
setTxSent(true);
|
|
235
|
+
} else if (walletType === "solana") {
|
|
236
|
+
// Solana needs SDK — fall back to URI
|
|
237
|
+
window.open(paymentUri);
|
|
238
|
+
}
|
|
239
|
+
} catch (err) {
|
|
240
|
+
const msg = err instanceof Error ? err.message : "Transaction rejected";
|
|
241
|
+
if (!msg.includes("User denied") && !msg.includes("rejected")) {
|
|
242
|
+
setWalletError(msg);
|
|
243
|
+
}
|
|
244
|
+
} finally {
|
|
245
|
+
sendingRef.current = false;
|
|
246
|
+
setSending(false);
|
|
247
|
+
}
|
|
248
|
+
}, [
|
|
249
|
+
walletType,
|
|
250
|
+
checkout.depositAddress,
|
|
251
|
+
checkout.type,
|
|
252
|
+
checkout.contractAddress,
|
|
253
|
+
checkout.expectedAmount,
|
|
254
|
+
expectedAmount,
|
|
255
|
+
receivedAmount,
|
|
256
|
+
paymentUri,
|
|
257
|
+
]);
|
|
33
258
|
|
|
34
259
|
return (
|
|
35
260
|
<div className="space-y-4 text-center">
|
|
36
|
-
<button
|
|
37
|
-
type="button"
|
|
38
|
-
onClick={onBack}
|
|
39
|
-
className="text-sm text-muted-foreground hover:text-foreground self-start"
|
|
40
|
-
>
|
|
261
|
+
<button type="button" onClick={onBack} className="text-sm text-muted-foreground hover:text-foreground self-start">
|
|
41
262
|
← Back
|
|
42
263
|
</button>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
264
|
+
{status === "partial" && expectedAmount && receivedAmount && decimals != null && token ? (
|
|
265
|
+
BigInt(receivedAmount) >= BigInt(expectedAmount) ? (
|
|
266
|
+
<>
|
|
267
|
+
<p className="text-sm text-primary font-medium">Full amount received</p>
|
|
268
|
+
<p className="text-2xl font-semibold">
|
|
269
|
+
{formatCrypto(receivedAmount, decimals)} {token}
|
|
270
|
+
</p>
|
|
271
|
+
<p className="text-xs text-muted-foreground animate-pulse">Waiting for on-chain confirmation...</p>
|
|
272
|
+
</>
|
|
273
|
+
) : (
|
|
274
|
+
<>
|
|
275
|
+
<p className="text-sm text-muted-foreground">Send remaining</p>
|
|
276
|
+
<p className="text-2xl font-semibold">
|
|
277
|
+
{formatCrypto(String(BigInt(expectedAmount) - BigInt(receivedAmount)), decimals)} {token}
|
|
278
|
+
</p>
|
|
279
|
+
<p className="text-xs text-muted-foreground">
|
|
280
|
+
on {checkout.chain} · {formatCrypto(receivedAmount, decimals)} of{" "}
|
|
281
|
+
{formatCrypto(expectedAmount, decimals)} {token} received
|
|
282
|
+
</p>
|
|
283
|
+
</>
|
|
284
|
+
)
|
|
285
|
+
) : (
|
|
286
|
+
<>
|
|
287
|
+
<p className="text-sm text-muted-foreground">Send exactly</p>
|
|
288
|
+
<p className="text-2xl font-semibold">{checkout.displayAmount}</p>
|
|
289
|
+
<p className="text-xs text-muted-foreground">
|
|
290
|
+
on {checkout.chain} · ${checkout.amountUsd.toFixed(2)} USD
|
|
291
|
+
</p>
|
|
292
|
+
</>
|
|
293
|
+
)}
|
|
294
|
+
|
|
295
|
+
{/* Wallet button — primary action when wallet detected */}
|
|
296
|
+
{walletType && !txSent && (
|
|
297
|
+
<Button onClick={handleWalletPay} disabled={sending} className="w-full gap-2">
|
|
298
|
+
<Wallet className="h-4 w-4" />
|
|
299
|
+
{sending ? "Confirm in wallet..." : walletLabel(walletType)}
|
|
300
|
+
</Button>
|
|
301
|
+
)}
|
|
302
|
+
{txSent && <p className="text-sm text-primary font-medium">Transaction sent — waiting for confirmation...</p>}
|
|
303
|
+
{walletError && <p className="text-xs text-destructive">{walletError}</p>}
|
|
304
|
+
|
|
305
|
+
{/* QR + manual address — fallback or mobile */}
|
|
306
|
+
<div className="mx-auto w-fit rounded-lg border border-border bg-white p-3" aria-hidden="true">
|
|
307
|
+
<QRCodeSVG value={paymentUri} size={140} bgColor="#ffffff" fgColor="#000000" />
|
|
58
308
|
</div>
|
|
309
|
+
<p className="text-[10px] text-muted-foreground">
|
|
310
|
+
{walletType ? "Or scan with another wallet" : "Scan with your wallet app"}
|
|
311
|
+
</p>
|
|
59
312
|
<div className="flex items-center gap-2 rounded-lg border border-border bg-muted/50 px-3 py-2">
|
|
60
313
|
<code className="flex-1 truncate text-xs font-mono">{checkout.depositAddress}</code>
|
|
61
314
|
<Button variant="ghost" size="sm" onClick={handleCopy} aria-label="Copy address">
|
|
62
|
-
{copied ?
|
|
63
|
-
<Check className="h-3.5 w-3.5 text-primary" />
|
|
64
|
-
) : (
|
|
65
|
-
<Copy className="h-3.5 w-3.5" />
|
|
66
|
-
)}
|
|
315
|
+
{copied ? <Check className="h-3.5 w-3.5 text-primary" /> : <Copy className="h-3.5 w-3.5" />}
|
|
67
316
|
</Button>
|
|
68
317
|
</div>
|
|
69
318
|
<div className="flex items-center justify-center gap-2 rounded-lg border border-border p-2">
|
|
@@ -71,15 +320,26 @@ export function DepositView({ checkout, status, onBack }: DepositViewProps) {
|
|
|
71
320
|
<>
|
|
72
321
|
<span className="h-2 w-2 animate-pulse rounded-full bg-yellow-500" />
|
|
73
322
|
<span className="text-xs text-yellow-500">Waiting for payment...</span>
|
|
74
|
-
<span className="text-xs text-muted-foreground">
|
|
75
|
-
· {mins}:{secs.toString().padStart(2, "0")}
|
|
76
|
-
</span>
|
|
77
323
|
</>
|
|
78
324
|
)}
|
|
79
325
|
{status === "partial" && (
|
|
80
326
|
<>
|
|
81
327
|
<span className="h-2 w-2 rounded-full bg-blue-500" />
|
|
82
|
-
<span className="text-xs text-blue-500">
|
|
328
|
+
<span className="text-xs text-blue-500">
|
|
329
|
+
{expectedAmount && receivedAmount && decimals != null && token ? (
|
|
330
|
+
BigInt(receivedAmount) >= BigInt(expectedAmount) ? (
|
|
331
|
+
"Full amount received — confirming on chain"
|
|
332
|
+
) : (
|
|
333
|
+
<>
|
|
334
|
+
Received {formatCrypto(receivedAmount, decimals)} of {formatCrypto(expectedAmount, decimals)}{" "}
|
|
335
|
+
{token} — send{" "}
|
|
336
|
+
{formatCrypto(String(BigInt(expectedAmount) - BigInt(receivedAmount)), decimals)} more
|
|
337
|
+
</>
|
|
338
|
+
)
|
|
339
|
+
) : (
|
|
340
|
+
"Partial payment received"
|
|
341
|
+
)}
|
|
342
|
+
</span>
|
|
83
343
|
</>
|
|
84
344
|
)}
|
|
85
345
|
{status === "expired" && <span className="text-xs text-destructive">Payment expired</span>}
|