diff --git a/frontend/src/app/(authenticated)/dashboard/page.tsx b/frontend/src/app/(authenticated)/dashboard/page.tsx index 03cbca63..bc124ec1 100644 --- a/frontend/src/app/(authenticated)/dashboard/page.tsx +++ b/frontend/src/app/(authenticated)/dashboard/page.tsx @@ -1,17 +1,14 @@ "use client"; import React, { useState, useEffect } from "react"; -import AnalyticsCards from "@/components/AnalyticsCards"; -import ActivityFeed from "@/components/ActivityFeed"; + import DashboardSkeleton from "@/components/DashboardSkeleton"; import Link from "next/link"; import { useMerchantHydrated, useHydrateMerchantStore, useMerchantApiKey, - useMerchantMetadata, } from "@/lib/merchant-store"; -import { useTranslations } from "next-intl"; import FirstApiKeyModal from "@/components/FirstApiKeyModal"; import PaymentMetrics from "@/components/PaymentMetrics"; import RecentPayments from "@/components/RecentPayments"; @@ -19,12 +16,10 @@ import WithdrawModal from "@/components/WithdrawModal"; import FirstPaymentCelebration from "@/components/FirstPaymentCelebration"; export default function DashboardPage() { - const t = useTranslations("dashboardPage"); const [isFirstKeyModalOpen, setIsFirstKeyModalOpen] = useState(false); const [isWithdrawOpen, setIsWithdrawOpen] = useState(false); const hydrated = useMerchantHydrated(); const apiKey = useMerchantApiKey(); - const merchant = useMerchantMetadata(); const [loading, setLoading] = useState(true); useHydrateMerchantStore(); @@ -138,7 +133,7 @@ export default function DashboardPage() { setIsFirstKeyModalOpen(false)} /> - setIsWithdrawOpen(false)} /> + setIsWithdrawOpen(false)} /> ); } diff --git a/frontend/src/components/PaymentMetrics.tsx b/frontend/src/components/PaymentMetrics.tsx index de227d1f..d8da78d2 100644 --- a/frontend/src/components/PaymentMetrics.tsx +++ b/frontend/src/components/PaymentMetrics.tsx @@ -26,7 +26,6 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { localeToLanguageTag } from "@/i18n/config"; -import MetricsSkeleton from "@/components/MetricsSkeleton"; import DensityGrid from "@/components/DensityGrid"; type TimeRange = "7D" | "30D" | "1Y"; @@ -268,6 +267,18 @@ export default function PaymentMetrics({ const hydrated = useMerchantHydrated(); const chartContainerRef = useRef(null); + const handleExport = async (format: ExportFormat) => { + if (!chartContainerRef.current) return; + try { + setExporting(true); + await exportChart(chartContainerRef, format, `metrics-export-${new Date().getTime()}`); + } catch (err) { + toast.error(err instanceof Error ? err.message : t("exportFailed")); + } finally { + setExporting(false); + } + }; + useHydrateMerchantStore(); useEffect(() => { @@ -343,7 +354,7 @@ export default function PaymentMetrics({ }); }; - if (loading || !hydrated) { + if (showSkeleton || loading || !hydrated) { return (
diff --git a/frontend/src/components/RecentPayments.tsx b/frontend/src/components/RecentPayments.tsx index 95e6de25..45098aee 100644 --- a/frontend/src/components/RecentPayments.tsx +++ b/frontend/src/components/RecentPayments.tsx @@ -150,7 +150,8 @@ function SortBtn({
); } @@ -443,12 +444,14 @@ export default function RecentPayments({ {/* Filters */}
+ setSearchInput(e.target.value)} placeholder="Search by ID, description or recipient…" - className="w-full rounded-xl border border-[#E8E8E8] bg-[#F9F9F9] py-2.5 pl-10 pr-4 text-sm text-[#0A0A0A] placeholder-[#A0A0A0] focus:border-[#0A0A0A] focus:bg-white focus:outline-none transition-all touch-manipulation" + className="w-full rounded-xl border border-[#E8E8E8] bg-[#F9F9F9] py-2.5 pl-10 pr-4 text-sm text-[#0A0A0A] placeholder-[#A0A0A0] focus:border-[#0A0A0A] focus:bg-white focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pluto-500)] focus-visible:ring-offset-2 transition-all touch-manipulation" />
- - - handleFilterChange("dateFrom", e.target.value)} - className="rounded-xl border border-[#E8E8E8] bg-[#F9F9F9] px-3 py-2.5 text-sm text-[#0A0A0A] focus:border-[#0A0A0A] focus:outline-none transition-all [color-scheme:light] touch-manipulation" - /> - handleFilterChange("dateTo", e.target.value)} - className="rounded-xl border border-[#E8E8E8] bg-[#F9F9F9] px-3 py-2.5 text-sm text-[#0A0A0A] focus:border-[#0A0A0A] focus:outline-none transition-all [color-scheme:light] touch-manipulation" - /> +
+ + +
+
+ + +
+
+ + handleFilterChange("dateFrom", e.target.value)} + className="rounded-xl border border-[#E8E8E8] bg-[#F9F9F9] px-3 py-2.5 text-sm text-[#0A0A0A] focus:border-[#0A0A0A] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pluto-500)] focus-visible:ring-offset-2 transition-all [color-scheme:light] touch-manipulation" + /> +
+
+ + handleFilterChange("dateTo", e.target.value)} + className="rounded-xl border border-[#E8E8E8] bg-[#F9F9F9] px-3 py-2.5 text-sm text-[#0A0A0A] focus:border-[#0A0A0A] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--pluto-500)] focus-visible:ring-offset-2 transition-all [color-scheme:light] touch-manipulation" + /> +
{hasActiveFilters && ( @@ -496,7 +515,7 @@ export default function RecentPayments({ {filters.asset !== "all" && clearFilter("asset")} />} {filters.dateFrom && clearFilter("dateFrom")} />} {filters.dateTo && clearFilter("dateTo")} />} - +
)}
@@ -506,8 +525,8 @@ export default function RecentPayments({

{t("showingResults", { shown: sortedPayments.length, total: totalCount })} {hasActiveFilters ? t("filteredSuffix") : ""}

- @@ -515,15 +534,16 @@ export default function RecentPayments({ {/* Table */}
- +
+ - - - - - - + + + + + + @@ -532,7 +552,9 @@ export default function RecentPayments({ ) : sortedPayments.map(payment => ( { setSelectedPayment(payment.id); setIsSheetOpen(true); }} - className={`group cursor-pointer transition-all duration-200 ease-in-out hover:bg-[#F9F9F9] hover:shadow-sm hover:border-l-2 hover:border-l-[var(--pluto-500)] active:bg-[#F5F5F5] active:scale-[0.995] ${flashedIds.has(payment.id) ? "bg-emerald-50" : ""}`} + tabIndex={0} + onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); setSelectedPayment(payment.id); setIsSheetOpen(true); } }} + className={`group cursor-pointer transition-all duration-200 ease-in-out hover:bg-[#F9F9F9] hover:shadow-sm hover:border-l-2 hover:border-l-[var(--pluto-500)] focus-visible:outline-none focus-visible:bg-[#F9F9F9] focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--pluto-500)] active:bg-[#F5F5F5] active:scale-[0.995] ${flashedIds.has(payment.id) ? "bg-emerald-50" : ""}`} > @@ -564,13 +587,15 @@ export default function RecentPayments({ {totalCount > 0 && (
- Rows: +
- @@ -595,7 +620,8 @@ export default function RecentPayments({ - ); - }); - }, [providers, connecting, installed, handleSelect, t]); if (activeProvider) return null; return ( -
+
-

{t("chooseWallet")}

-

{t("description")}

+

{t("chooseWallet")}

+

{t("description")}

- {providerButtons} + {providers.map((p) => { + const isWc = p.id === "walletconnect"; + const isConnecting = connecting === p.id; + const isInstalled = installed[p.id] ?? false; + const isDisabled = !isInstalled; + + return ( + + ); + })}
{/* WalletConnect QR */} {wcUri && ( -
-

{t("scanTitle")}

-
- +
+

{t("scanTitle")}

+
+
-

{t("scanDescription")}

+

{t("scanDescription")}

)} diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx index 05ada4e6..9841abd6 100644 --- a/frontend/src/components/ui/Button.tsx +++ b/frontend/src/components/ui/Button.tsx @@ -7,12 +7,13 @@ interface ButtonProps extends React.ButtonHTMLAttributes { } const BASE_CLASSES = - "group relative flex items-center justify-center rounded-xl px-6 font-bold transition-colors duration-200 disabled:cursor-not-allowed disabled:opacity-50 focus-visible:ring-2 focus-visible:ring-pluto-300 focus-visible:ring-offset-2 focus-visible:ring-offset-white"; + "group relative flex items-center justify-center rounded-xl px-6 font-bold transition-all duration-300 ease-out disabled:cursor-not-allowed disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-pluto-500 focus-visible:ring-offset-2 hover:-translate-y-[1px] active:translate-y-0 active:scale-[0.98]"; const VARIANT_CLASSES: Record, string> = { - primary: "h-12 bg-pluto-500 text-white hover:bg-pluto-600", + primary: + "h-12 bg-pluto-500 text-white hover:bg-pluto-600 hover:shadow-lg hover:shadow-pluto-500/30", secondary: - "h-12 border border-pluto-200 bg-pluto-50 text-pluto-700 hover:border-pluto-500 hover:bg-pluto-500 hover:text-white", + "h-12 border border-pluto-200 bg-pluto-50 text-pluto-700 hover:border-pluto-300 hover:bg-pluto-100 hover:text-pluto-800 hover:shadow-md hover:shadow-pluto-500/5", }; const ButtonBase = React.forwardRef( @@ -49,7 +50,7 @@ const ButtonBase = React.forwardRef( children )} {showPrimaryGlow && ( -
+
)} );
Recent payments transaction history
handleSort("status")}>{t("tableStatus")} handleSort("amount")}>{t("tableAmount")} handleSort("recipient")}>{t("tableRecipient")}Description handleSort("created_at")}>{t("tableDate")}Actions handleSort("status")}>{t("tableStatus")} handleSort("amount")}>{t("tableAmount")} handleSort("recipient")}>{t("tableRecipient")}Description handleSort("created_at")}>{t("tableDate")}Actions
@@ -550,7 +572,8 @@ export default function RecentPayments({