diff --git a/fluxapay_frontend/src/app/dashboard/payments/page.tsx b/fluxapay_frontend/src/app/dashboard/payments/page.tsx index 5cab506..51bae2a 100644 --- a/fluxapay_frontend/src/app/dashboard/payments/page.tsx +++ b/fluxapay_frontend/src/app/dashboard/payments/page.tsx @@ -19,6 +19,7 @@ import { Download, Plus } from "lucide-react"; import { Suspense } from "react"; import toast from "react-hot-toast"; import { api } from "@/lib/api"; +import { QRCodeCanvas } from "qrcode.react"; interface BackendRefund { id: string; @@ -53,7 +54,20 @@ function PaymentsContent() { const [linkAmount, setLinkAmount] = useState("100"); const [linkCurrency, setLinkCurrency] = useState("USD"); const [linkDescription, setLinkDescription] = useState("Invoice payment"); + const [linkSuccessUrl, setLinkSuccessUrl] = useState(""); + const [linkCancelUrl, setLinkCancelUrl] = useState(""); const [generatedLink, setGeneratedLink] = useState(""); + const [isGeneratingLink, setIsGeneratingLink] = useState(false); + const [recentLinks, setRecentLinks] = useState< + { + id: string; + url: string; + amount: number; + currency: string; + description?: string; + createdAt: string; + }[] + >([]); const filteredPayments = useMemo(() => { return MOCK_PAYMENTS.filter((payment) => { @@ -136,15 +150,71 @@ function PaymentsContent() { } }; - const handleGenerateLink = () => { - const paymentId = `PAY-${Date.now()}`; - const link = `${window.location.origin}/pay/${paymentId}`; - setGeneratedLink(link); + const handleGenerateLink = async () => { + const amountNumber = Number(linkAmount); + if (!amountNumber || amountNumber <= 0) { + toast.error("Please enter a valid amount."); + return; + } + + setIsGeneratingLink(true); + try { + const payload = { + amount: amountNumber, + currency: linkCurrency, + description: linkDescription || undefined, + success_url: linkSuccessUrl || undefined, + cancel_url: linkCancelUrl || undefined, + }; + + const response = (await api.payments.create(payload)) as { + payment?: { + id: string; + checkoutUrl?: string; + checkout_url?: string; + status?: string; + }; + }; + + const payment = response?.payment; + if (!payment?.id) { + throw new Error("Payment link could not be created."); + } + + const url = + payment.checkoutUrl ?? + payment.checkout_url ?? + `${window.location.origin}/pay/${payment.id}`; + + setGeneratedLink(url); + setRecentLinks((prev) => [ + { + id: payment.id, + url, + amount: amountNumber, + currency: linkCurrency, + description: linkDescription || undefined, + createdAt: new Date().toISOString(), + }, + ...prev, + ].slice(0, 5)); + + toast.success("Payment link created successfully."); + } catch (error) { + const message = + error instanceof Error + ? error.message + : "Unable to create payment link. Please try again."; + toast.error(message); + } finally { + setIsGeneratingLink(false); + } }; const handleCopyLink = async () => { if (!generatedLink) return; await navigator.clipboard.writeText(generatedLink); + toast.success("Payment link copied to clipboard."); }; const handleInitiateRefund = async (payload: { @@ -223,6 +293,49 @@ function PaymentsContent() {
+ {recentLinks.length > 0 && ( +
+
+
+

Recent payment links

+

+ Links you've generated in this session. +

+
+
+
+ {recentLinks.map((link) => ( +
+
+

+ {link.url} +

+

+ {link.amount} {link.currency} + {link.description ? ` • ${link.description}` : ""} •{" "} + {new Date(link.createdAt).toLocaleString()} +

+
+ +
+ ))} +
+
+ )} +
+
+ + setLinkSuccessUrl(e.target.value)} + placeholder="https://your-site.com/checkout/success" + className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm" + /> +
+
+ + setLinkCancelUrl(e.target.value)} + placeholder="https://your-site.com/checkout/cancel" + className="h-10 w-full rounded-md border border-input bg-background px-3 text-sm" + /> +
-