+ {description} +
+ {actionLabel && onAction && ( + + )} +
- {this.state.error?.stack}
-
- + An unexpected error occurred. Please try again or contact support if the problem persists. +
+ {process.env.NODE_ENV !== "production" && this.state.error && ( +
+ {this.state.error.message}
+
+ )}
+ ( + WrappedComponent: React.ComponentType
, + fallback?: ReactNode +): React.FC
{ + const name = WrappedComponent.displayName || WrappedComponent.name || "Component"; + const Wrapped: React.FC
= (props) => (
+
+ {isOffline + ? "Check your internet connection and try again." + : description} +
+ {onRetry && ( + + )} ++ {section.label} +
+ {section.items.map((item) => { + const Icon = item.icon; + const isActive = location === item.path; + return ( + + ); + })} ++ {doneCount} of {steps.length} complete +
++ Your session will expire in{" "} + {countdown}s. + Tap below to stay logged in. +
+Threshold: {drift.metrics.driftThreshold}
P-Value
-{drift.metrics.pValue.toFixed(3)}
-{drift.metrics.pValue > 0.05 ? "Not significant" : "Significant"}
+Baseline Tx Count
+{drift.metrics.baselineTxCount.toLocaleString()}
+Recent: {drift.metrics.recentTxCount.toLocaleString()}
Generate your first code to start onboarding partners
No tenants yet
Platinum Tier
-{pending?.filter(a => a.tier === "platinum").length ?? 0}
+{pending?.filter((a: any) => a.tier === "platinum").length ?? 0}
Gold Tier
-{pending?.filter(a => a.tier === "gold").length ?? 0}
+{pending?.filter((a: any) => a.tier === "gold").length ?? 0}
Basic/Silver
-{pending?.filter(a => ["basic", "silver"].includes(a.tier)).length ?? 0}
+{pending?.filter((a: any) => ["basic", "silver"].includes(a.tier)).length ?? 0}
{rec.suggested_allocation_pct.toFixed(1)}%
{exp.exportType.replace(/_/g, " ")}
@@ -862,7 +864,7 @@ export default function CbnComplianceDashboard() {No data yet
) : ( - leaderboard.slice(0, 10).map((entry, i) => entry && ( + leaderboard.slice(0, 10).map((entry: any, i: any) => entry && (No votes cast yet. Be the first!
No proposals submitted yet.
No contributions yet. Start contributing!
{note.content}
@@ -246,7 +247,7 @@ function AlertDetailDrawer({ alertId, open, onClose }: { alertId: number | null; {detail.notes && detail.notes.length > 0 ? (Compliance Review
Reviewed: {new Date(review.reviewed_at as string).toLocaleString()}
Status set to: {String(review.new_status)}
- {review.note &&Note: {String(review.note)}
} + {review.note ?Note: {String(review.note)}
: null}{inv.description}
@@ -289,7 +291,7 @@ export default function ContractorPayments() {{c.name}
diff --git a/client/src/pages/CorrespondentBankAdmin.tsx b/client/src/pages/CorrespondentBankAdmin.tsx index cc7d7779..3aa62d75 100644 --- a/client/src/pages/CorrespondentBankAdmin.tsx +++ b/client/src/pages/CorrespondentBankAdmin.tsx @@ -12,8 +12,10 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Loader2, Plus, RefreshCw, Building2, ShieldAlert } from "lucide-react"; +import { useTranslation } from 'react-i18next'; export default function CorrespondentBankAdmin() { + const { t } = useTranslation(); const { user } = useAuth(); const [addOpen, setAddOpen] = useState(false); const [newBank, setNewBank] = useState({ bankName: "", swiftCode: "", countryCode: "", currency: "USD", clearingLineUsd: 10000000, feeBps: 50, settlementRail: "swift" }); diff --git a/client/src/pages/CorridorPricing.tsx b/client/src/pages/CorridorPricing.tsx index 1a05960d..c14eda89 100644 --- a/client/src/pages/CorridorPricing.tsx +++ b/client/src/pages/CorridorPricing.tsx @@ -22,13 +22,13 @@ export default function CorridorPricing() { const [days, setDays] = useState(30); const [selectedPair, setSelectedPair] = useState({ fromCurrency: "USD", toCurrency: "NGN" }); - const { data: topCorridors } = trpc.corridorAnalytics.topCorridors.useQuery( + const { data: topCorridors, isLoading, isError } = trpc.corridorAnalytics.topCorridors.useQuery( { days, limit: 10 }, { enabled: isAdmin } ); const { data: performance } = trpc.corridorAnalytics.performance.useQuery( { ...selectedPair, days }, { enabled: isAdmin } ); - const { data: liveRatesRaw } = trpc.fx.liveRates.useQuery(); + const { data: liveRatesRaw } = trpc.fx.liveRates.useQuery({}); const liveRates = liveRatesRaw ? Object.entries((liveRatesRaw as any)?.rates ?? {}).map(([currency, rate]) => ({ to_currency: currency, rate, from_currency: "USD" })) : []; if (!isAdmin) { diff --git a/client/src/pages/CorridorPricingAdmin.tsx b/client/src/pages/CorridorPricingAdmin.tsx index e281ab94..4466900a 100644 --- a/client/src/pages/CorridorPricingAdmin.tsx +++ b/client/src/pages/CorridorPricingAdmin.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from 'react-i18next'; import { useState } from "react"; import { trpc } from "@/lib/trpc"; import DashboardLayout from "@/components/DashboardLayout"; @@ -104,6 +105,7 @@ function StatCard({ icon: Icon, label, value, sub, color = "text-primary" }: { // ─── Main Page ──────────────────────────────────────────────────────────────── export default function CorridorPricingAdmin() { + const { t } = useTranslation(); // ── State ────────────────────────────────────────────────────────────────── const [editingCorridor, setEditingCorridor] = useState{txn.type === "receive" || txn.type === "topup" ? "+" : "-"} - {txn.currency === "NGN" ? "₦" : txn.currency + " "} - {Number(txn.amount).toLocaleString()} + {(txn.fromCurrency ?? txn.currency ?? "NGN") === "NGN" ? "₦" : (txn.fromCurrency ?? txn.currency ?? "") + " "} + {Number(txn.fromAmount ?? txn.amount ?? 0).toLocaleString()}
Est. LTV: ${(crossSellQuery.data as any)?.expected_ltv_usd?.toFixed(0)}
{exp.merchant || exp.description}
@@ -235,7 +237,7 @@ export default function ExpenseManagement() {{p.name}
diff --git a/client/src/pages/FCACompliance.tsx b/client/src/pages/FCACompliance.tsx index bd596547..6230af8c 100644 --- a/client/src/pages/FCACompliance.tsx +++ b/client/src/pages/FCACompliance.tsx @@ -11,7 +11,7 @@ import { useTranslation } from 'react-i18next'; export default function FCACompliance() { const { t } = useTranslation(); - const { data: report } = trpc.compliance.fca.useQuery(); + const { data: report, isLoading, isError } = trpc.compliance.fca.useQuery(); const checks = [ { label: "AML Policy", status: "compliant", lastReview: "Jan 2024", nextReview: "Jan 2025" }, diff --git a/client/src/pages/FXAlerts.tsx b/client/src/pages/FXAlerts.tsx index 38dfb676..812cc1e7 100644 --- a/client/src/pages/FXAlerts.tsx +++ b/client/src/pages/FXAlerts.tsx @@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next'; export default function FXAlerts() { const { t } = useTranslation(); - const { data: alerts, refetch } = trpc.fx.alerts.useQuery(); + const { data: alerts, refetch, isError } = trpc.fx.alerts.useQuery(); const createMutation = trpc.fx.createAlert.useMutation({ onSuccess: () => { toast.success("Alert created!"); refetch(); setOpen(false); } }); const deleteMutation = trpc.fx.deleteAlert.useMutation({ onSuccess: () => { toast.success("Alert deleted"); refetch(); } }); const [open, setOpen] = useState(false); diff --git a/client/src/pages/FXHedging.tsx b/client/src/pages/FXHedging.tsx index a175cadb..02f3ee63 100644 --- a/client/src/pages/FXHedging.tsx +++ b/client/src/pages/FXHedging.tsx @@ -10,10 +10,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { TrendingUp, TrendingDown, Plus, Shield, DollarSign, BarChart3, Clock } from "lucide-react"; import { toast } from "sonner"; import DashboardLayout from "@/components/DashboardLayout"; +import { useTranslation } from 'react-i18next'; const CURRENCIES = ["USD", "NGN", "GBP", "EUR", "KES", "GHS", "ZAR", "TZS", "UGX"]; export default function FXHedging() { + const { t } = useTranslation(); const utils = trpc.useUtils(); const [open, setOpen] = useState(false); const [form, setForm] = useState({ fromCurrency: "USD", toCurrency: "NGN", amount: "", settlementDays: "30" }); diff --git a/client/src/pages/FXHedgingPage.tsx b/client/src/pages/FXHedgingPage.tsx index 3f560092..58a81893 100644 --- a/client/src/pages/FXHedgingPage.tsx +++ b/client/src/pages/FXHedgingPage.tsx @@ -10,8 +10,10 @@ import { Label } from "@/components/ui/label"; import { toast } from "sonner"; import { TrendingUp, TrendingDown, Plus, X, BarChart3, DollarSign, Activity } from "lucide-react"; import DashboardLayout from "@/components/DashboardLayout"; +import { useTranslation } from 'react-i18next'; export default function FXHedgingPage() { + const { t } = useTranslation(); const [openDialog, setOpenDialog] = useState(false); const [pair, setPair] = useState("USD/NGN"); const [direction, setDirection] = useState<"long" | "short">("long"); diff --git a/client/src/pages/FXOptionsPricingPage.tsx b/client/src/pages/FXOptionsPricingPage.tsx index 68806150..eef9f3ae 100644 --- a/client/src/pages/FXOptionsPricingPage.tsx +++ b/client/src/pages/FXOptionsPricingPage.tsx @@ -8,8 +8,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Badge } from "@/components/ui/badge"; import { TrendingUp, TrendingDown, Calculator, DollarSign } from "lucide-react"; import DashboardLayout from "@/components/DashboardLayout"; +import { useTranslation } from 'react-i18next'; export default function FXOptionsPricingPage() { + const { t } = useTranslation(); const [params, setParams] = useState({ baseCurrency: "USD", quoteCurrency: "NGN", @@ -21,7 +23,7 @@ export default function FXOptionsPricingPage() { }); const [submitted, setSubmitted] = useState(false); - const { data, isLoading } = trpc.v101.fxOptions.price.useQuery(params, { enabled: submitted }); + const { data, isLoading, isError } = trpc.v101.fxOptions.price.useQuery(params, { enabled: submitted }); const currencies = ["USD", "GBP", "EUR", "NGN", "KES", "GHS", "ZAR", "TZS", "UGX", "XOF"]; diff --git a/client/src/pages/FXStreamingPage.tsx b/client/src/pages/FXStreamingPage.tsx index 8173aa38..577332a4 100644 --- a/client/src/pages/FXStreamingPage.tsx +++ b/client/src/pages/FXStreamingPage.tsx @@ -8,10 +8,12 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { TrendingUp, TrendingDown, RefreshCw, Bell } from "lucide-react"; import { toast } from "sonner"; import DashboardLayout from "@/components/DashboardLayout"; +import { useTranslation } from 'react-i18next'; const CURRENCIES = ["NGN", "GHS", "KES", "ZAR", "UGX", "TZS", "XOF", "MAD", "EGP", "ETB", "EUR", "GBP", "CAD", "AUD"]; export default function FXStreamingPage() { + const { t } = useTranslation(); const [base, setBase] = useState("USD"); const [alertCurrency, setAlertCurrency] = useState("NGN"); const [alertRate, setAlertRate] = useState(""); diff --git a/client/src/pages/FeatureFlagAdmin.tsx b/client/src/pages/FeatureFlagAdmin.tsx index 2ac05cb5..bbecb6dc 100644 --- a/client/src/pages/FeatureFlagAdmin.tsx +++ b/client/src/pages/FeatureFlagAdmin.tsx @@ -11,6 +11,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { trpc } from "@/lib/trpc"; import { toast } from "sonner"; import { Flag, Plus, Trash2, Edit } from "lucide-react"; +import { useTranslation } from 'react-i18next'; interface FlagForm { id?: number; @@ -30,6 +31,7 @@ const defaultForm: FlagForm = { }; export default function FeatureFlagAdmin() { + const { t } = useTranslation(); const [search, setSearch] = useState(""); const [showDialog, setShowDialog] = useState(false); const [form, setForm] = useStatePlease log in.
+ {devices.length} device(s) detected — {gpuCount} GPU(s) + CPU +
+Training in progress...
+Model is training on the best available GPU
+Device
+
+ {(lastResult.device as Record
+ {(lastResult.device as Record
Data Source
+{lastResult.data_source}
+{lastResult.training_samples} samples
+Training Time
+{lastResult.training_time_s}s
+{lastResult.epochs_trained} epochs
+Best Accuracy
++ {((lastResult.metrics?.best_val_accuracy || 0) * 100).toFixed(1)}% +
+Epoch {lastResult.best_epoch}
+Training History (last 5 epochs)
+No training results yet
+Configure and start a training job
+Device
+{String(result.device_used)}
+{String(result.provider_used)}
+Latency
+{String(result.latency_ms)} ms
+Batch: {String(result.batch_size)}
+Predictions
+{JSON.stringify(result.predictions)}
+Probabilities
+{JSON.stringify(result.probabilities)}
+No inference results yet
+Select a model and run inference
+Connect to GPU Engine to see providers
+ )} +No models loaded — train a model first
+ )} +Train GPU
++ {trainDevice ? trainDevice.toUpperCase() : "Auto"} +
+ONNX Model
+Portable
+Infer GPU
++ {inferDevice ? inferDevice.toUpperCase() : "Auto"} +
+Data Source
+{String(result.data_source)}
+Training Device
+
+ {String(((result.training as Record
Inference Device
+
+ {String((result.inference as Record
Training Time
+
+ {String((result.training as Record
Test Prediction Verified
+
+ Latency: {String((result.test_prediction as Record
+ Register remote machines for distributed training & inference +
+No remote nodes registered
+Add a remote GPU machine to enable distributed training
++ {String(exportResult.model_name)} → {String(exportResult.target_format)} +
++ Size: {String(exportResult.size_mb)} MB +
+Throughput
++ {String(benchResult.throughput_samples_per_sec)} samples/sec +
++ Train on any GPU — NVIDIA, AMD, Intel, Huawei, Apple — infer on any other +
+Best Device
+{health.best_device.device_name}
+{health?.total ?? "—"}
+Devices
+{health?.gpu_count ?? 0}
+GPUs
+5
+Model Types
+10
+Inference Providers
+
{h.description}
No notifications
No notifications
You're all caught up!
Admin access required.
Recommended: {(crossSellQuery.data as any)?.recommended_product}
{(crossSellQuery.data as any)?.next_best_action}
No leaderboard data yet
- ) : leaderboardData.leaders.map(l => ( + ) : leaderboardData.leaders.map((l: any) => ({s.description}
} - {s.tags &&No security events found
You send
NGN {parseFloat(amountNgn).toLocaleString()}
{quoteQuery.error.message}
}| Segment | Fee % | Spread (bps) | Min Fee |
|---|
{amount} {fromCurrency} → {selected?.name}
Recipient receives approximately {quote?.toAmount?.toFixed(2)} {toCurrency}
@@ -385,7 +389,7 @@ export default function SendMoney() {Add a recipient to get started.
Reviewed: {new Date(review.reviewed_at as string).toLocaleString()}
Decision: {String(review.new_status).toUpperCase()}
- {review.note &&"{String(review.note)}"
} + {review.note ?"{String(review.note)}"
: null} )} @@ -164,6 +165,7 @@ function ValidationDetail({ result }: { result: Record- 📊 {deal.metrics.map(m => `${m.label}: ${m.value}`).join(" · ")} + 📊 {deal.metrics.map((m: any) => `${m.label}: ${m.value}`).join(" · ")}
)} @@ -293,7 +295,7 @@ export default function StartupDealRoom() { ) : (Key Metrics
-No tenants found
- ) : tenants.map((t) => ( + ) : tenants.map((t: any) => (Daily Volume & Transactions
Completed vs Failed