diff --git a/components/auth/protected-route.tsx b/components/auth/protected-route.tsx index 34bf437..1225cd0 100644 --- a/components/auth/protected-route.tsx +++ b/components/auth/protected-route.tsx @@ -27,10 +27,10 @@ export default function ProtectedRoute({ children }: ProtectedRouteProps) { setIsChecking(false); } else { // Only redirect if we're not already on the auth page - if (!pathname.includes('/auth')) { + if (!pathname.includes('/auth/login')) { console.log('Not authenticated - redirecting to auth'); // Use replace: false to allow back button to work properly - router.push("/auth"); + router.push("/auth/login"); } setIsChecking(false); } diff --git a/components/context/AuthContext.tsx b/components/context/AuthContext.tsx index 7adfa02..bbc2b3f 100644 --- a/components/context/AuthContext.tsx +++ b/components/context/AuthContext.tsx @@ -30,13 +30,11 @@ import { ExecutionHistoryResponse, TemplatesResponse, ToggleSplitPaymentResponse, - GenerateQRRequest, - GenerateQRResponse, - ParseQRRequest, - ParseQRResponse, - ExecuteQRRequest, - ExecuteQRResponse, - GetQRStatusResponse, + CreateMerchantPaymentRequest, + CreateMerchantPaymentResponse, + GetMerchantPaymentHistoryResponse, + GetMerchantPaymentStatusResponse, + PayMerchantInvoiceResponse, } from "@/types/authContext"; interface AuthContextType { @@ -104,10 +102,23 @@ interface AuthContextType { params?: { page?: number; limit?: number } ) => Promise; toggleSplitPaymentStatus: (id: string) => Promise; - generateQRCode: (data: GenerateQRRequest) => Promise; - parseQRCode: (data: ParseQRRequest) => Promise; - executeQRPayment: (data: ExecuteQRRequest) => Promise; - getQRPaymentStatus: (paymentId: string) => Promise; + createMerchantPayment: ( + requestBody: CreateMerchantPaymentRequest + ) => Promise; + getMerchantPaymentStatus: ( + paymentId: string + ) => Promise; + payMerchantInvoice: ( + paymentId: string, + fromAddress: string + ) => Promise; + getMerchantPaymentHistory: (params?: { + merchantId?: string; + status?: string; + page?: number; + limit?: number; + }) => Promise; + fetchStatus: () => Promise; } const AuthContext = createContext(undefined); @@ -268,7 +279,7 @@ export const AuthProvider: React.FC = ({ children }) => { setToken(null); setUser(null); setIsLoading(false); - router.push("/auth"); + router.push("/auth/login"); }; const register = async ( @@ -915,79 +926,92 @@ export const AuthProvider: React.FC = ({ children }) => { if (!token) { throw new Error("Authentication required"); } - + try { - const response = await fetch("https://velo-node-backend.onrender.com/split-payment/create", { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - + const response = await fetch( + "https://velo-node-backend.onrender.com/split-payment/create", + { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + } + ); + if (!response.ok) { throw new Error(`Failed to create split payment: ${response.status}`); } - + return await response.json(); } catch (error) { console.error("Error creating split payment:", error); throw error; } }; - - const executeSplitPayment = async (id: string): Promise => { + + const executeSplitPayment = async ( + id: string + ): Promise => { if (!token) { throw new Error("Authentication required"); } - + try { - const response = await fetch(`https://velo-node-backend.onrender.com/split-payment/${id}/execute`, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - + const response = await fetch( + `https://velo-node-backend.onrender.com/split-payment/${id}/execute`, + { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { throw new Error(`Failed to execute split payment: ${response.status}`); } - + return await response.json(); } catch (error) { console.error("Error executing split payment:", error); throw error; } }; - - const getSplitPaymentTemplates = async (params?: { status?: string }): Promise => { + + const getSplitPaymentTemplates = async (params?: { + status?: string; + }): Promise => { if (!token) { throw new Error("Authentication required"); } - + try { - const queryParams = params?.status ? `?status=${params.status}` : ''; - const response = await fetch(`https://velo-node-backend.onrender.com/split-payment/templates${queryParams}`, { - method: "GET", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - + const queryParams = params?.status ? `?status=${params.status}` : ""; + const response = await fetch( + `https://velo-node-backend.onrender.com/split-payment/templates${queryParams}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { throw new Error(`Failed to get templates: ${response.status}`); } - + return await response.json(); } catch (error) { console.error("Error getting templates:", error); throw error; } }; - + const getExecutionHistory = async ( id: string, params?: { page?: number; limit?: number } @@ -995,49 +1019,57 @@ export const AuthProvider: React.FC = ({ children }) => { if (!token) { throw new Error("Authentication required"); } - + try { const urlParams = new URLSearchParams(); - if (params?.page) urlParams.append('page', params.page.toString()); - if (params?.limit) urlParams.append('limit', params.limit.toString()); - const query = urlParams.toString() ? `?${urlParams.toString()}` : ''; - const response = await fetch(`https://velo-node-backend.onrender.com/split-payment/${id}/executions${query}`, { - method: "GET", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - + if (params?.page) urlParams.append("page", params.page.toString()); + if (params?.limit) urlParams.append("limit", params.limit.toString()); + const query = urlParams.toString() ? `?${urlParams.toString()}` : ""; + const response = await fetch( + `https://velo-node-backend.onrender.com/split-payment/${id}/executions${query}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { throw new Error(`Failed to get execution history: ${response.status}`); } - + return await response.json(); } catch (error) { console.error("Error getting execution history:", error); throw error; } }; - - const toggleSplitPaymentStatus = async (id: string): Promise => { + + const toggleSplitPaymentStatus = async ( + id: string + ): Promise => { if (!token) { throw new Error("Authentication required"); } - + try { - const response = await fetch(`https://velo-node-backend.onrender.com/split-payment/${id}/toggle`, { - method: "PATCH", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - }); - + const response = await fetch( + `https://velo-node-backend.onrender.com/split-payment/${id}/toggle`, + { + method: "PATCH", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + if (!response.ok) { throw new Error(`Failed to toggle status: ${response.status}`); } - + return await response.json(); } catch (error) { console.error("Error toggling status:", error); @@ -1045,111 +1077,121 @@ export const AuthProvider: React.FC = ({ children }) => { } }; - - // QR Payment functions - const generateQRCode = async ( - data: GenerateQRRequest - ): Promise => { + const createMerchantPayment = async (requestBody: any): Promise => { if (!token) { - throw new Error("Authentication required to generate QR code"); + throw new Error("Authentication required to create merchant payment"); } try { const response = await fetch( - "https://velo-node-backend.onrender.com/qr/generate", + "https://velo-node-backend.onrender.com/merchant/create", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - body: JSON.stringify(data), + body: JSON.stringify(requestBody), } ); if (!response.ok) { - throw new Error(`Failed to generate QR code: ${response.status}`); + throw new Error( + `Failed to create merchant payment: ${response.status}` + ); } return await response.json(); } catch (error) { - console.error("Error generating QR code:", error); + console.error("Error creating merchant payment:", error); throw error; } }; - const parseQRCode = async ( - data: ParseQRRequest - ): Promise => { + const getMerchantPaymentStatus = async (paymentId: string): Promise => { if (!token) { - throw new Error("Authentication required to parse QR code"); + throw new Error("Authentication required to get merchant payment status"); } try { const response = await fetch( - "https://velo-node-backend.onrender.com/qr/parse", + `https://velo-node-backend.onrender.com/merchant/payment/${paymentId}`, { - method: "POST", + method: "GET", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - body: JSON.stringify(data), } ); if (!response.ok) { - throw new Error(`Failed to parse QR code: ${response.status}`); + throw new Error( + `Failed to get merchant payment status: ${response.status}` + ); } return await response.json(); } catch (error) { - console.error("Error parsing QR code:", error); + console.error("Error getting merchant payment status:", error); throw error; } }; - const executeQRPayment = async ( - data: ExecuteQRRequest - ): Promise => { + const payMerchantInvoice = async ( + paymentId: string, + fromAddress: string + ): Promise => { if (!token) { - throw new Error("Authentication required to execute QR payment"); + throw new Error("Authentication required to pay merchant invoice"); } try { const response = await fetch( - "https://velo-node-backend.onrender.com/qr/pay", + "https://velo-node-backend.onrender.com/merchant/pay", { method: "POST", headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json", }, - body: JSON.stringify(data), + body: JSON.stringify({ paymentId, fromAddress }), } ); if (!response.ok) { - throw new Error(`Failed to execute QR payment: ${response.status}`); + throw new Error(`Failed to pay merchant invoice: ${response.status}`); } return await response.json(); } catch (error) { - console.error("Error executing QR payment:", error); + console.error("Error paying merchant invoice:", error); throw error; } }; - const getQRPaymentStatus = async ( - paymentId: string - ): Promise => { + const getMerchantPaymentHistory = async (params?: { + merchantId?: string; + status?: string; + page?: number; + limit?: number; + }): Promise => { if (!token) { - throw new Error("Authentication required to get QR payment status"); + throw new Error( + "Authentication required to get merchant payment history" + ); } try { + const urlParams = new URLSearchParams(); + if (params?.merchantId) urlParams.append("merchantId", params.merchantId); + if (params?.status) urlParams.append("status", params.status); + if (params?.page) urlParams.append("page", params.page.toString()); + if (params?.limit) urlParams.append("limit", params.limit.toString()); + const query = urlParams.toString() ? `?${urlParams.toString()}` : ""; + const response = await fetch( - `https://velo-node-backend.onrender.com/qr/status/${paymentId}`, + `https://velo-node-backend.onrender.com/merchant/payments${query}`, { method: "GET", headers: { @@ -1160,15 +1202,46 @@ export const AuthProvider: React.FC = ({ children }) => { ); if (!response.ok) { - throw new Error(`Failed to get QR payment status: ${response.status}`); + throw new Error( + `Failed to get merchant payment history: ${response.status}` + ); } return await response.json(); } catch (error) { - console.error("Error getting QR payment status:", error); + console.error("Error getting merchant payment history:", error); throw error; } }; + + const fetchStatus = async (): Promise => { + if (!token) { + throw new Error("Authentication required to check deposits"); + } + + try { + const response = await fetch( + "https://velo-node-backend.onrender.com/merchant/my-payments", + { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + throw new Error(`Failed to check Status: ${response.status}`); + } + console.log("Payment Status", response); + return await response.json(); + } catch (error) { + console.error("Error checking deposits:", error); + throw error; + } + }; + const value: AuthContextType = { user, token, @@ -1197,10 +1270,11 @@ export const AuthProvider: React.FC = ({ children }) => { getSplitPaymentTemplates, getExecutionHistory, toggleSplitPaymentStatus, - generateQRCode, - parseQRCode, - executeQRPayment, - getQRPaymentStatus, + createMerchantPayment, + getMerchantPaymentStatus, + payMerchantInvoice, + getMerchantPaymentHistory, + fetchStatus, }; return {children}; diff --git a/components/dashboard/quick-actions.tsx b/components/dashboard/quick-actions.tsx index 446c2ec..27537e5 100644 --- a/components/dashboard/quick-actions.tsx +++ b/components/dashboard/quick-actions.tsx @@ -2,35 +2,39 @@ import { QrCode, Users, Send, ArrowDownToLine } from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "../ui/cards" import { Button } from "../ui/buttons" +import { Dispatch, SetStateAction } from "react" +interface quickActionProps { +setTab: Dispatch> +} const actions = [ { - title: "QR Payment", + title: "Qr Payment", description: "Scan or generate QR codes", icon: QrCode, gradient: "from-primary to-accent", }, { - title: "Payment Split", + title: "Payment split", description: "Split payments with others", icon: Users, gradient: "from-success to-chart-2", }, { - title: "Send Money", + title: "Send", description: "Transfer to any wallet", icon: Send, gradient: "from-chart-3 to-chart-4", }, { - title: "Receive Funds", + title: "Receive funds", description: "Get paid instantly", icon: ArrowDownToLine, gradient: "from-accent to-primary", }, ] -export function QuickActions() { +export function QuickActions({setTab}: quickActionProps) { return ( @@ -42,6 +46,7 @@ export function QuickActions() { const Icon = action.icon return ( diff --git a/components/dashboard/stats-cards.tsx b/components/dashboard/stats-cards.tsx index 7b15945..8d5083d 100644 --- a/components/dashboard/stats-cards.tsx +++ b/components/dashboard/stats-cards.tsx @@ -1,15 +1,33 @@ -import { TrendingUp, TrendingDown, Activity, Users } from "lucide-react"; +import { + TrendingUp, + TrendingDown, + Activity, + Users, + Eye, + EyeClosed, +} from "lucide-react"; import { useTotalBalance } from "../hooks/useTotalBalance"; import { Skeleton } from "@/components/ui/skeleton"; import { Card } from "../ui/cards"; import { useNotifications } from "../hooks/useNotifications"; +import { Button } from "../ui/buttons"; - -export function StatsCards() { +interface WalletOverviewProps { + handleViewBalance: () => void; + hideBalalance: boolean; +} +export function StatsCards({ + hideBalalance, + handleViewBalance, +}: WalletOverviewProps) { const { totalBalance, loading, error } = useTotalBalance(); const { notifications } = useNotifications(); const totalTransactions = notifications.filter((notification) => { - return notification.title === "Deposit Received"; + return notification.title === "Deposit Received" || notification.title === "Token Sent"; + }); + + const split = notifications.filter((notification) => { + return notification.title === "Split Payment Created"; }); const stats = [ @@ -31,7 +49,7 @@ export function StatsCards() { }, { title: "Active Splits", - value: "3", + value: split.length | 0, change: "-2.1%", trend: "down" as const, icon: Users, @@ -67,9 +85,20 @@ export function StatsCards() {
{updatedStats.map((stat, index) => ( + {stat.title === "Total Balance" && ( + + )} +
@@ -81,12 +110,20 @@ export function StatsCards() {

- {loading && stat.title === "Total Balance" ? ( + {loading ? ( ) : ( -

- {stat.value} -

+ <> + {hideBalalance ? ( +
+ ------- +
+ ) : ( +

+ {stat.value} +

+ )} + )} {stat.change && (
diff --git a/components/dashboard/tabs/dashboard.tsx b/components/dashboard/tabs/dashboard.tsx index 20e05fb..706174f 100644 --- a/components/dashboard/tabs/dashboard.tsx +++ b/components/dashboard/tabs/dashboard.tsx @@ -9,6 +9,7 @@ import { QuickActions } from "../quick-actions"; import { RecentActivity } from "../recent-activity"; import { WalletOverview } from "../wallet-overview"; import { useAuth } from "@/components/context/AuthContext"; +import { useState } from "react"; interface RecentActivity { id: string; @@ -24,26 +25,22 @@ export interface DashboardProps { activeTab: React.Dispatch>; } -export default function DashboardHome({ activeTab }: DashboardProps) { +export default function DashboardHome({ activeTab, }: DashboardProps) { const { user } = useAuth(); const { addresses, loading: addressesLoading } = useWalletAddresses(); const { loading: balanceLoading } = useTotalBalance(); + const [hideBalalance, setHideBalance] = useState(false); const isLoading = addressesLoading || balanceLoading ; + + const handleViewBalance = () => { + setHideBalance(!hideBalalance); + }; + - if (isLoading) { - return ( -
-
-
-

Loading dashboard...

-
-
- ); - } return (
@@ -58,10 +55,10 @@ export default function DashboardHome({ activeTab }: DashboardProps) {
{/* Stats Grid */} - + {/* Quick Actions */} - + {/* Main Content Grid */} @@ -70,7 +67,7 @@ export default function DashboardHome({ activeTab }: DashboardProps) {
- +
diff --git a/components/dashboard/tabs/qr-payment.tsx b/components/dashboard/tabs/qr-payment.tsx index 88fff3c..e5175d0 100644 --- a/components/dashboard/tabs/qr-payment.tsx +++ b/components/dashboard/tabs/qr-payment.tsx @@ -1,10 +1,10 @@ "use client"; import { Card } from "@/components/ui/Card"; -import { ChevronDown, Loader2 } from "lucide-react"; -import { useCallback, useEffect, useMemo, useState, useRef } from "react"; -import QRCodeLib from "qrcode"; +import { ChevronDown } from "lucide-react"; +import { useCallback, useMemo, useState, useEffect } from "react"; import Image from "next/image"; +import QRCodeLib from "qrcode"; import { useWalletAddresses } from "@/components/hooks/useAddresses"; import useExchangeRates from "@/components/hooks/useExchangeRate"; import { QRCodeDisplay } from "@/components/modals/qr-code-display"; @@ -20,40 +20,203 @@ const normalizeStarknetAddress = (address: string, chain: string): string => { return address; }; +// QR code format generators for different cryptocurrencies +const generateQRData = (chain: string, address: string, amount: string | null = null, label: string | null = null): string => { + switch (chain.toLowerCase()) { + case 'bitcoin': + case 'btc': + let bitcoinUri = `bitcoin:${address}`; + const bitcoinParams = []; + if (amount) bitcoinParams.push(`amount=${amount}`); + if (label) bitcoinParams.push(`label=${encodeURIComponent(label)}`); + if (bitcoinParams.length > 0) { + bitcoinUri += `?${bitcoinParams.join('&')}`; + } + return bitcoinUri; + + case 'ethereum': + case 'eth': + let ethereumUri = `ethereum:${address}`; + const ethereumParams = []; + if (amount) { + const weiAmount = (parseFloat(amount) * Math.pow(10, 18)).toString(); + ethereumParams.push(`value=${weiAmount}`); + } + if (label) ethereumParams.push(`label=${encodeURIComponent(label)}`); + if (ethereumParams.length > 0) { + ethereumUri += `?${ethereumParams.join('&')}`; + } + return ethereumUri; + + case 'solana': + case 'sol': + let solanaUri = `solana:${address}`; + const solanaParams = []; + if (amount) solanaParams.push(`amount=${amount}`); + if (label) solanaParams.push(`label=${encodeURIComponent(label)}`); + if (solanaParams.length > 0) { + solanaUri += `?${solanaParams.join('&')}`; + } + return solanaUri; + + case 'starknet': + case 'strk': + let starknetUri = `starknet:${address}`; + const starknetParams = []; + if (amount) { + const weiAmount = (parseFloat(amount) * Math.pow(10, 18)).toString(); + starknetParams.push(`value=${weiAmount}`); + } + if (label) starknetParams.push(`label=${encodeURIComponent(label)}`); + if (starknetParams.length > 0) { + starknetUri += `?${starknetParams.join('&')}`; + } + return starknetUri; + + case 'usdt_erc20': + return `ethereum:${address}`; + case 'usdt_trc20': + return `tron:${address}`; + + default: + return address; + } +}; + +// Enhanced QR code generation function +const generateCompatibleQRCode = async ( + chain: string, + address: string, + options: { + amount?: string | null; + label?: string | null; + width?: number; + margin?: number; + darkColor?: string; + lightColor?: string; + errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'; + } = {} +) => { + const { + amount = null, + label = null, + width = 200, + margin = 2, + darkColor = "#000000", + lightColor = "#FFFFFF", + errorCorrectionLevel = 'M' + } = options; + + try { + const qrData = generateQRData(chain, address, amount, label); + const qrCodeDataUrl = await QRCodeLib.toDataURL(qrData, { + width, + margin, + errorCorrectionLevel, + type: 'image/png' as 'image/png' | 'image/jpeg' | 'image/webp', + color: { + dark: darkColor, + light: lightColor, + }, + }); + + return { + dataUrl: qrCodeDataUrl, + rawData: qrData, + format: getQRFormat(chain) + }; + } catch (error) { + console.error("Error generating compatible QR code:", error); + throw error; + } +}; + +// Helper function to get the format description +const getQRFormat = (chain: string): string => { + switch (chain.toLowerCase()) { + case 'bitcoin': + case 'btc': + return 'BIP21 Bitcoin URI'; + case 'ethereum': + case 'eth': + return 'EIP681 Ethereum URI'; + case 'solana': + case 'sol': + return 'Solana URI Scheme'; + case 'starknet': + case 'strk': + return 'Ethereum-compatible URI'; + case 'usdt_erc20': + return 'ERC-20 Token URI'; + case 'usdt_trc20': + return 'TRC-20 Token URI'; + default: + return 'Plain Address'; + } +}; + export default function QrPayment() { - const [token, setToken] = useState("STRK"); + const [token, setToken] = useState("STARKNET"); const [amount, setAmount] = useState(""); + const [customerEmail, setCustomerEmail] = useState(""); + const [description, setDescription] = useState(""); const [showTokenDropdown, setShowTokenDropdown] = useState(false); const [showQR, setShowQR] = useState(false); const [qrData, setQrData] = useState(""); const [isProcessing, setIsProcessing] = useState(false); - const [loading, setLoading] = useState(true); + const [loading, setLoading] = useState(false); const [paymentId, setPaymentId] = useState(""); - const [localPaymentStatus, setLocalPaymentStatus] = useState<"idle" | "pending" | "success" | "error">("idle"); + const [merchantId, setMerchantId] = useState(""); const [localError, setLocalError] = useState(null); - + const [paymentStatus, setPaymentStatus] = useState<"pending" | "idle" | "success" | "error">("pending") + + console.log("payment Status 02", paymentStatus) + const tokenRate = (token: string) => { + if (token === "ETHEREUM") return "ETH"; + if (token === "BITCOIN") return "BTC"; + if (token === "SOLANA") return "SOL"; + if (token === "STARKNET") return "STRK"; + if (token === "USDT_TRC20") return "USDT"; + if (token === "USDT_ERC20") return "USDT"; + }; const { addresses, loading: addressesLoading } = useWalletAddresses(); - const { rates, isLoading: ratesLoading, lastUpdated } = useExchangeRates(); - const { generateQRCode, getQRPaymentStatus } = useAuth(); - - const pollingRef = useRef(null); - - useEffect(() => { - if (addresses && addresses.length > 0) { - setLoading(false); - } else if (!addressesLoading) { - setLoading(false); + const { rates, isLoading: ratesLoading } = useExchangeRates(); + const { createMerchantPayment, user, fetchStatus } = useAuth(); + + const handleCheckStatus = async () => { + try { + console.log("checking status..."); + const result = await fetchStatus(); + console.log(result); + } catch (error) { + console.error("Failed to check deposits:", error); } - }, [addresses, addressesLoading]); + }; - const tokens = useMemo(() => ["ETH", "BTC", "SOL", "STRK"], []); + setInterval(() => { + handleCheckStatus() + }, 60000); + + const tokens = useMemo( + () => [ + "ETHEREUM", + "BITCOIN", + "SOLANA", + "STARKNET", + "USDT_TRC20", + "USDT_ERC20", + ], + [] + ); const getTokenChain = useCallback((): string => { const chainMap: { [key: string]: string } = { - ETH: "ethereum", - BTC: "bitcoin", - SOL: "solana", - STRK: "starknet", + ETHEREUM: "ethereum", + BITCOIN: "bitcoin", + SOLANA: "solana", + STARKNET: "starknet", + USDT_ERC20: "usdt_erc20", + USDT_TRC20: "usdt_trc20", }; return chainMap[token] || "ethereum"; }, [token]); @@ -62,7 +225,7 @@ export default function QrPayment() { if (!addresses || addresses.length === 0) return ""; const chain = getTokenChain(); - const addr = addresses.find(a => a.chain === chain); + const addr = addresses.find((a) => a.chain === chain); if (!addr) return ""; return normalizeStarknetAddress(addr.address, chain); @@ -70,96 +233,80 @@ export default function QrPayment() { const calculateTokenAmount = useCallback((): string => { const ngnAmount = parseFloat(amount) || 0; - const rate = rates[token] || 1; - const tokenAmount = ngnAmount / rate; + const rateKey = tokenRate(token); + const rate = rateKey ? rates[rateKey] : 1; + const tokenAmount = ngnAmount / (rate || 1); return tokenAmount.toFixed(6); }, [amount, rates, token]); - const handleGenerateQR = async () => { - if (!currentReceiverAddress) return; - - setIsProcessing(true); - try { - const cryptoAmount = calculateTokenAmount(); - - const request = { - chain: getTokenChain(), - network: "testnet", - amount: cryptoAmount, - description: "Payment request", - expiresInMinutes: 30, - }; - - const response = await generateQRCode(request); - - const qrImage = await QRCodeLib.toDataURL(response.qrCodeString, { - width: 300, - margin: 1, - }); - - setQrData(qrImage); - setPaymentId(response.qrData.paymentId); - setShowQR(true); - setLocalPaymentStatus("pending"); - } catch (error) { - console.error("Error generating QR:", error); - // Handle error, perhaps show toast - } finally { - setIsProcessing(false); - } + const handleTokenSelect = (tkn: string) => { + setToken(tkn); + setShowTokenDropdown(false); }; - useEffect(() => { - if (!showQR || !paymentId) { - if (pollingRef.current) { - clearInterval(pollingRef.current); - pollingRef.current = null; - } + const handleCreatePaymentRequest = async () => { + if (!amount || !currentReceiverAddress) { + setLocalError( + "Please enter an amount and ensure wallet address is available" + ); return; } - const pollStatus = async () => { - try { - const statusResponse = await getQRPaymentStatus(paymentId); - const { paymentStatus } = statusResponse; - - if (paymentStatus.status === "confirmed" || paymentStatus.status === "completed") { - setLocalPaymentStatus("success"); - if (pollingRef.current) clearInterval(pollingRef.current); - } else if (paymentStatus.isExpired) { - setLocalPaymentStatus("error"); - setLocalError("Payment request has expired"); - if (pollingRef.current) clearInterval(pollingRef.current); - } else { - setLocalPaymentStatus("pending"); + setIsProcessing(true); + setLocalError(null); + + try { + const tokenAmount = calculateTokenAmount(); + const chain = getTokenChain(); + const network = addresses.find((a) => a.chain === chain); + if (!network) return ""; + + const qrResult = await generateCompatibleQRCode( + chain, + currentReceiverAddress, + { + amount: tokenAmount, + // label: description || customerEmail || "Payment Request", + width: 200, + margin: 2, + errorCorrectionLevel: 'M', } - } catch (error) { - console.error("Error checking payment status:", error); - } - }; + ); - pollStatus(); // Initial check - pollingRef.current = setInterval(pollStatus, 5000); + const requestBody = { + amount: tokenAmount, + btcAddress: currentReceiverAddress, + chain: chain, + network: network.network, + }; - return () => { - if (pollingRef.current) { - clearInterval(pollingRef.current); - pollingRef.current = null; + const response = await createMerchantPayment(requestBody); + + if (response && response.payment) { + const payment = response.payment; + setPaymentId(response.payment.paymentId || ""); + setMerchantId(response.payment.merchantId || user?.id || ""); + // setPaymentStatus(response.payment.status) + setQrData(qrResult.dataUrl); + setShowQR(true); + } else { + throw new Error("Invalid response from server"); } - }; - }, [showQR, paymentId, getQRPaymentStatus]); + } catch (error: any) { + console.error("Error creating payment request:", error); + setLocalError(error.message || "Failed to create payment request"); + } finally { + setIsProcessing(false); + } + }; const handleCloseQR = () => { setShowQR(false); setQrData(""); setPaymentId(""); - setLocalPaymentStatus("idle"); + setMerchantId(""); setLocalError(null); - }; - - const handleTokenSelect = (tkn: string) => { - setToken(tkn); - setShowTokenDropdown(false); + setAmount(""); }; const steps = [ @@ -182,7 +329,7 @@ export default function QrPayment() { ]; return ( -
+

@@ -190,102 +337,149 @@ export default function QrPayment() {

- {/* Token Selection */} -
- -
- - - - {showTokenDropdown && ( - - {tokens.map((tkn) => ( - - ))} - - )} -
+ + + + {showTokenDropdown && ( + + {tokens.map((tkn) => ( + + ))} + + )} +
+ {/* Amount Input */} +
+ + setAmount(e.target.value)} + placeholder="Enter amount in NGN" + className="w-full p-3 rounded-lg bg-background border border-border placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" + min="0" + step="any" + disabled={loading || ratesLoading || isProcessing} + /> +

+ ≈ {calculateTokenAmount()} {token} +

+
+ +
+ + setDescription(e.target.value)} + placeholder="Describe the payment purpose" + className="w-full p-3 rounded-lg bg-background border border-border placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" + min="0" + step="any" + disabled={loading || ratesLoading || isProcessing} + /> +
- {/* Amount Input */} -
- - setAmount(e.target.value)} - placeholder="Enter amount in NGN" - className="w-full p-3 rounded-lg bg-background border border-border placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" - min="0" - step="any" - disabled={loading || ratesLoading} - /> -

- ≈ {calculateTokenAmount()} {token} -

+
+ + setCustomerEmail(e.target.value)} + placeholder="Enter customer email" + className="w-full p-3 rounded-lg bg-background border border-border placeholder:text-muted-foreground focus:outline-none focus:border-primary transition-colors" + min="0" + step="any" + disabled={loading || ratesLoading || isProcessing} + /> +
+ {/* Error Message */} + {localError && !showQR && ( +
+

{localError}

+
+ )} + {/* Generate Button */}
@@ -299,11 +493,15 @@ export default function QrPayment() { {steps.map((step, id) => (
- {id + 1} + + {id + 1} +

{step.step}

-

{step.description}

+

+ {step.description} +

))} @@ -315,13 +513,15 @@ export default function QrPayment() { {showQR && ( )}
diff --git a/components/dashboard/tabs/send-funds.tsx b/components/dashboard/tabs/send-funds.tsx index b607b38..0168e0a 100644 --- a/components/dashboard/tabs/send-funds.tsx +++ b/components/dashboard/tabs/send-funds.tsx @@ -450,7 +450,7 @@ export default function SendFunds() {
{showTokenDropdown && ( - + {tokenOptions.map((token, id) => ( + +
)} @@ -120,12 +179,14 @@ export function WalletOverview({ addresses }: WalletOverviewProps) {
Total Value: - {formatNGN(breakdown.reduce((sum, item) => sum + item.ngnValue, 0))} + {formatNGN( + breakdown.reduce((sum, item) => sum + item.ngnValue, 0) + )}
)}
- ) -} \ No newline at end of file + ); +} diff --git a/components/hooks/useNotifications.ts b/components/hooks/useNotifications.ts index 4716445..9cb0d87 100644 --- a/components/hooks/useNotifications.ts +++ b/components/hooks/useNotifications.ts @@ -111,7 +111,7 @@ export const useNotifications = () => { const fetchNotifications = useCallback( async ( page: number = 1, - limit: number = 50, + limit: number = 1000, unreadOnly: boolean = false ) => { if (!token) { @@ -200,7 +200,7 @@ export const useNotifications = () => { } pollingIntervalRef.current = setInterval(() => { - fetchNotifications(1, 50); + fetchNotifications(1, 1000); }, 10000); // Poll every 10 seconds // Return cleanup function @@ -255,7 +255,7 @@ export const useNotifications = () => { stopPolling(); } else { // Refresh immediately when tab becomes visible - fetchNotifications(1, 50); + fetchNotifications(1, 1000); startPolling(); } }; @@ -318,7 +318,7 @@ export const useNotifications = () => { // Manual refresh function const refreshNotifications = useCallback(() => { - return fetchNotifications(1, 50); + return fetchNotifications(1, 1000); }, [fetchNotifications]); return { diff --git a/components/landingpage/landing/hero.tsx b/components/landingpage/landing/hero.tsx index 1a16c5f..fff962a 100644 --- a/components/landingpage/landing/hero.tsx +++ b/components/landingpage/landing/hero.tsx @@ -5,16 +5,9 @@ import { ArrowRight, ChevronDown, Play } from "lucide-react" import { Paprika } from "next/font/google" import Image from "next/image" import Link from "next/link" -import { useState , useRef} from "react" - -const paprika = Paprika({ - subsets: ["latin"], - weight: ["400"], - variable: "--font-paprika", -}); - +import { useState } from "react" export function Hero() { -const sectionRef = useRef(null) + const scrollToFeatures = () => { const featuresSection = document.getElementById("features") @@ -23,29 +16,8 @@ const sectionRef = useRef(null) } } - useEffect(() => { - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting) { - setIsVisible(true) - } - }, - { threshold: 0.1 }, - ) - - if (sectionRef.current) { - observer.observe(sectionRef.current) - } - - return () => { - if (sectionRef.current) { - observer.unobserve(sectionRef.current) - } - } - }, []) - return ( -
+
diff --git a/components/modals/qr-code-display.tsx b/components/modals/qr-code-display.tsx index f63426d..3a0f203 100644 --- a/components/modals/qr-code-display.tsx +++ b/components/modals/qr-code-display.tsx @@ -1,6 +1,6 @@ "use client"; -import { CheckCheck, TriangleAlert, Copy, Check } from "lucide-react"; +import { Check, Copy, TriangleAlert, X } from "lucide-react"; import Image from "next/image"; import { useState } from "react"; @@ -13,17 +13,21 @@ interface QRCodeDisplayProps { calculatedAmount: string; receiverAddress: string; onClose: () => void; + paymentId: string; + merchantId: string; } -export function QRCodeDisplay({ - qrData, - paymentStatus, - error, - amount, - token, - calculatedAmount, +export function QRCodeDisplay({ + qrData, + paymentStatus, + error, + amount, + token, + calculatedAmount, receiverAddress, - onClose + onClose, + paymentId, + merchantId, }: QRCodeDisplayProps) { const [copied, setCopied] = useState(false); @@ -41,7 +45,16 @@ export function QRCodeDisplay({ return (
-
+
+ {/* Close button */} + +
{/* Header */}
@@ -53,9 +66,9 @@ export function QRCodeDisplay({ {/* QR Code */}
- QR Code @@ -73,6 +86,15 @@ export function QRCodeDisplay({
+ {/* Payment ID */} + {paymentId && ( +
+

+ Payment ID: {paymentId} +

+
+ )} + {/* Wallet Address */}
@@ -100,35 +122,43 @@ export function QRCodeDisplay({ {/* Status */} {paymentStatus === "pending" && ( -
-
-

Waiting for payment...

+
+
+
+

Waiting for payment...

+
)} {paymentStatus === "success" && (
- +

Payment Successful!

)} {paymentStatus === "error" && ( -
- -
-

Payment Failed

- {error &&

{error}

} +
+
+ +
+

Payment Failed

+
+ {error && ( +

+ {error} +

+ )}
)} {/* Close Button */}
diff --git a/components/ui/notification.tsx b/components/ui/notification.tsx index 4887c5d..44acb90 100644 --- a/components/ui/notification.tsx +++ b/components/ui/notification.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useState, useCallback, useRef } from "react"; import { Card } from "./Card"; import { Bell } from "lucide-react"; import { useAuth } from "../context/AuthContext"; +import { useNotifications } from "../hooks/useNotifications"; interface NotificationProps { onclick: React.Dispatch>; @@ -12,10 +13,11 @@ export default function Notification({ onclick }: NotificationProps) { const [isLoading, setIsLoading] = useState(false); const { getUnreadCount } = useAuth(); const intervalRef = useRef(null); + const { markAllAsRead } = useNotifications(); const handleFetchNotification = useCallback(async () => { if (isLoading) return; // Prevent concurrent requests - + try { setIsLoading(true); const notificationCount = await getUnreadCount(); @@ -41,7 +43,7 @@ export default function Notification({ onclick }: NotificationProps) { clearInterval(intervalRef.current); } }; - }, [handleFetchNotification]); + }, []); // Empty dependency array - only run once on mount // Optional: Stop polling when tab is not visible useEffect(() => { @@ -54,25 +56,30 @@ export default function Notification({ onclick }: NotificationProps) { } }; - document.addEventListener('visibilitychange', handleVisibilityChange); + document.addEventListener("visibilitychange", handleVisibilityChange); return () => { - document.removeEventListener('visibilitychange', handleVisibilityChange); + document.removeEventListener("visibilitychange", handleVisibilityChange); }; - }, [handleFetchNotification]); + }, []); // Empty dependency array - only run once on mount + + const handleview = () => { + markAllAsRead(); + onclick("Notification"); + }; return ( - + ); } \ No newline at end of file diff --git a/types/authContext.ts b/types/authContext.ts index 4a9f4af..bb12caa 100644 --- a/types/authContext.ts +++ b/types/authContext.ts @@ -283,76 +283,74 @@ export type ToggleSplitPaymentResponse = { } -export type GenerateQRRequest = { - chain: string; - network: string; +export type CreateMerchantPaymentRequest = { amount: string; - description?: string; - expiresInMinutes?: number; -} + network: string; + btcAddress: string; + chain: string; +}; -export type GenerateQRResponse = { +export type CreateMerchantPaymentResponse = { message: string; - qrData: { + payment: { paymentId: string; - recipientAddress: string; - chain: string; - network: string; + merchantId: string; amount: string; - description: string; - createdAt: string; - expiresAt: string; + currency: string; + status: string; + description?: string; + orderId?: string; + paymentAddress?: string; + qrCode?: string; + expiresAt?: string; + createdAt?: string; }; - qrCodeString: string; -} +}; -export type ParseQRRequest = { - qrCodeString: string; -} - -export type ParseQRResponse = { - message: string; - paymentDetails: { +export type GetMerchantPaymentStatusResponse = { + payment: { paymentId: string; - recipientAddress: string; - chain: string; - network: string; + merchantId: string; amount: string; - description: string; - isValid: boolean; - isExpired: boolean; - expiresAt: string; + currency: string; + status: string; + description?: string; + orderId?: string; + txHash?: string; + paidAt?: string; + customerEmail?: string; + paymentAddress?: string; }; -} +}; -export type ExecuteQRRequest = { +export type PayMerchantInvoiceRequest = { paymentId: string; fromAddress: string; -} +}; -export type ExecuteQRResponse = { +export type PayMerchantInvoiceResponse = { message: string; transaction: { txHash: string; - fromAddress: string; - toAddress: string; - amount: string; - chain: string; - network: string; paymentId: string; + amount: string; + currency: string; + merchantId: string; status: string; + paidAt?: string; }; -} +}; -export type GetQRStatusResponse = { - paymentStatus: { +export type GetMerchantPaymentHistoryResponse = { + payments: Array<{ paymentId: string; - status: string; + merchantId: string; amount: string; - chain: string; - network: string; - txHash?: string; + currency: string; + status: string; + description?: string; + orderId?: string; paidAt?: string; - isExpired: boolean; - }; -} \ No newline at end of file + }>; + pagination: PaginationInfo; +}; \ No newline at end of file