added new ui

This commit is contained in:
shialoth 2025-05-04 07:28:42 +05:30
parent df220a533f
commit 42350c9acc
3 changed files with 535 additions and 113 deletions

View File

@ -156,7 +156,9 @@ export default function Boxes() {
['left', 'right'].map((side) => (
<span
key={`${pos}-${side}`}
className={`absolute -${pos}-px -${side}-px opacity-25 group-hover:opacity-100 duration-200`}
className={`absolute -${pos}-px -${side}-px opacity-25 group-hover:opacity-100 duration-200 ${
side === 'right' ? 'scale-x-[-1]' : ''
}`}
>
<svg width="6" height="5" viewBox="0 0 6 5" fill="none">
<path d={pos === 'top' ? 'M1 0V4.5' : 'M1 4.5V0'} stroke="#0CE77E" />

View File

@ -11,3 +11,26 @@ textarea {
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.token-dropdown {
width: 100%;
padding: 10px 12px;
background: #0E1618;
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 3px;
color: #FFFFFF;
font-family: 'Geist Mono', monospace;
font-size: 14px;
line-height: 18px;
letter-spacing: -0.01em;
appearance: none;
-webkit-appearance: none;
background-image: url("data:image/svg+xml,%3Csvg width='10' height='6' viewBox='0 0 10 6' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%230CE77E' stroke-width='1.5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 12px;
}
.token-dropdown:focus {
outline: none;
border-color: #0CE77E;
}

View File

@ -1,18 +1,23 @@
'use client';
import { useEffect, useState } from 'react';
import React from "react";
import {
Connection,
PublicKey,
Transaction,
sendAndConfirmTransaction,
Connection,
SystemProgram,
} from '@solana/web3.js';
} from "@solana/web3.js";
import axios from 'axios';
import { usePrivy } from '@privy-io/react-auth';
import {
useSolanaWallets,
} from '@privy-io/react-auth/solana';
import { useSolanaWallets } from '@privy-io/react-auth/solana';
import { createClient } from '@supabase/supabase-js';
import {
getAssociatedTokenAddress,
createTransferInstruction,
TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
interface GpuPaymentModalProps {
isOpen: boolean;
@ -21,15 +26,197 @@ interface GpuPaymentModalProps {
id: string;
title: string;
price_usd: number;
price_per_hour: string; // new field
price_per_hour: string;
};
}
const SOLANA_RPC = process.env.NEXT_PUBLIC_SOLANA_RPC!;
const BUSINESS_WALLET = process.env.NEXT_PUBLIC_BUSINESS_WALLET!;
const EMAIL_API_URL = process.env.NEXT_PUBLIC_EMAIL_API_URL!;
const USDC_MINT = new PublicKey("Es9vMFrzaCERJ8gLhEvX5yQceQ2uKcXfUrx2Wcikgqay");
const JUP_MINT = new PublicKey("JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN");
const CornerEdge = ({ position }: { position: 'left-top' | 'right-top' | 'left-bottom' | 'right-bottom' }) => {
const paths = {
'left-top': (
<>
<path d="M1 0V4.5" stroke="#0CE77E" strokeWidth="1" />
<path d="M5.5 0.5L1 0.5" stroke="#0CE77E" strokeWidth="1" />
</>
),
'right-top': (
<>
<path d="M5 0V4.5" stroke="#0CE77E" strokeWidth="1" />
<path d="M0.5 0.5L5 0.5" stroke="#0CE77E" strokeWidth="1" />
</>
),
'left-bottom': (
<>
<path d="M1 4.5V0" stroke="#0CE77E" strokeWidth="1" />
<path d="M5.5 4L1 4" stroke="#0CE77E" strokeWidth="1" />
</>
),
'right-bottom': (
<>
<path d="M5 4.5V0" stroke="#0CE77E" strokeWidth="1" />
<path d="M0.5 4L5 4" stroke="#0CE77E" strokeWidth="1" />
</>
)
};
return (
<svg
width="6"
height="5"
viewBox="0 0 6 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`absolute ${position.includes('left') ? 'left-0' : 'right-0'} ${position.includes('top') ? 'top-0' : 'bottom-0'}`}
>
{paths[position]}
</svg>
);
};
// Red corner edges for error UI
const RedCornerEdge = ({ position }: { position: 'left-top' | 'right-top' | 'left-bottom' | 'right-bottom' }) => {
const paths = {
'left-top': (
<>
<path d="M1 0V4.5" stroke="#F62727" strokeWidth="1" />
<path d="M5.5 0.5L1 0.5" stroke="#F62727" strokeWidth="1" />
</>
),
'right-top': (
<>
<path d="M5 0V4.5" stroke="#F62727" strokeWidth="1" />
<path d="M0.5 0.5L5 0.5" stroke="#F62727" strokeWidth="1" />
</>
),
'left-bottom': (
<>
<path d="M1 4.5V0" stroke="#F62727" strokeWidth="1" />
<path d="M5.5 4L1 4" stroke="#F62727" strokeWidth="1" />
</>
),
'right-bottom': (
<>
<path d="M5 4.5V0" stroke="#F62727" strokeWidth="1" />
<path d="M0.5 4L5 4" stroke="#F62727" strokeWidth="1" />
</>
)
};
return (
<svg
width="6"
height="5"
viewBox="0 0 6 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={`absolute ${position.includes('left') ? 'left-0' : 'right-0'} ${position.includes('top') ? 'top-0' : 'bottom-0'}`}
>
{paths[position]}
</svg>
);
};
const SuccessIcon = () => {
return (
<div className="relative h-[54px] w-[54px]">
<div className="absolute top-0 left-0 bg-emerald-500 rounded-xl h-[54px] w-[54px]" />
<svg
width="39"
height="38"
viewBox="0 0 39 38"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute top-2 left-2"
aria-hidden="true"
>
<path
d="M32.1663 9.5L14.7497 26.9167L6.83301 19"
stroke="black"
strokeWidth="3.16667"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
);
};
const ErrorIcon = () => {
return (
<div className="relative h-[54px] w-[54px]">
<div className="absolute bg-red-600 rounded-xl h-[54px] w-[54px]" />
<svg
width="39"
height="38"
viewBox="0 0 39 38"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute w-[38px] h-[38px] left-[9px] top-[8px]"
>
<path
d="M29 9.5L10 28.5M10 9.5L29 28.5"
stroke="black"
strokeWidth="3.16667"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
);
};
const ErrorBox = () => {
return (
<div className="relative w-full max-w-[357px] h-[60px]">
<div className="absolute inset-0 bg-[#F62727] bg-opacity-10" />
<div className="absolute inset-[1px] border border-white border-opacity-5" />
<div className="absolute flex justify-between items-center px-5 w-full h-full">
<span className="font-mono text-[18px] font-medium text-white">
Error
</span>
<span className="font-mono text-[18px] font-medium text-[#F62727]">
404 (Payment Error)
</span>
</div>
<RedCornerEdge position="left-top" />
<RedCornerEdge position="right-top" />
<RedCornerEdge position="left-bottom" />
<RedCornerEdge position="right-bottom" />
</div>
);
};
interface OrderIdBoxProps {
orderId: string;
}
const OrderIdBox = ({ orderId }: OrderIdBoxProps) => {
return (
<div className="relative w-full max-w-[357px] h-[60px]">
<div className="absolute inset-0 bg-[#0CE77E] bg-opacity-10" />
<div className="absolute inset-[1px] border border-white border-opacity-5" />
<div className="absolute flex justify-between items-center px-5 w-full h-full">
<span className="font-mono text-[18px] font-medium text-white">
Order ID
</span>
<span className="font-mono text-[18px] font-medium text-[#0CE77E]">
{orderId}
</span>
</div>
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
</div>
);
};
export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps) => {
const { user, connectWallet } = usePrivy();
const { wallets: privyWallets } = useSolanaWallets();
@ -39,12 +226,15 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
const [loading, setLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [successMsg, setSuccessMsg] = useState<string | null>(null);
const [paymentSuccess, setPaymentSuccess] = useState(false);
const [paymentFailure, setPaymentFailure] = useState(false);
const [orderId, setOrderId] = useState<string>('');
const connection = new Connection(SOLANA_RPC);
const userEmail = user?.email?.address || emailInput || null;
const isValidEmail = (email: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
const [emailTouched, setEmailTouched] = useState(false);
const [selectedToken, setSelectedToken] = useState<'SOL' | 'USDC' | 'JUP'>('SOL');
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
@ -67,6 +257,8 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
fetchSolPrice();
setErrorMsg(null);
setSuccessMsg(null);
setPaymentSuccess(false);
setPaymentFailure(false);
}
}, [isOpen]);
@ -77,155 +269,360 @@ export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps)
try {
if (!userEmail || !isValidEmail(userEmail)) {
throw new Error('Please enter a valid email address for your receipt.');
throw new Error("Please enter a valid email address for your receipt.");
}
const solWallet = privyWallets[0];
if (!solWallet || !solWallet.address) {
throw new Error('No connected Solana wallet found. Please connect a wallet via Privy.');
}
if (!solWallet.signTransaction) {
throw new Error('Connected wallet does not support transaction signing.');
if (!solWallet?.address || !solWallet.signTransaction) {
throw new Error("No connected Solana wallet found or wallet can't sign.");
}
const fromPubKey = new PublicKey(solWallet.address);
const toPubKey = new PublicKey(BUSINESS_WALLET);
const transaction = new Transaction();
let txId = "";
let tokenUsed = selectedToken;
let amountDisplay = "";
const solAmount = (gpu.price_usd / (solPrice || 1)).toFixed(6);
if (selectedToken === "SOL") {
const { data } = await axios.get(
"https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd"
);
const solPrice = data.solana.usd;
const solAmount = (gpu.price_usd / solPrice).toFixed(6);
amountDisplay = `${solAmount} SOL`;
const transaction = new Transaction().add(
transaction.add(
SystemProgram.transfer({
fromPubkey: fromPubKey,
toPubkey: toPubKey,
lamports: Math.floor(parseFloat(solAmount) * 1e9),
})
);
} else {
const selectedMint = selectedToken === "USDC" ? USDC_MINT : JUP_MINT;
const tokenDecimals = 6;
let tokenPrice = 1;
if (selectedToken === "JUP") {
const { data } = await axios.get(
"https://api.coingecko.com/api/v3/simple/price?ids=jupiter-exchange&vs_currencies=usd"
);
tokenPrice = data["jupiter-exchange"]?.usd ?? 0.5;
}
const tokenAmount = (gpu.price_usd / tokenPrice).toFixed(tokenDecimals);
amountDisplay = `${tokenAmount} ${selectedToken}`;
const fromTokenAccount = await getAssociatedTokenAddress(selectedMint, fromPubKey);
const toTokenAccount = await getAssociatedTokenAddress(selectedMint, toPubKey);
transaction.add(
createTransferInstruction(
fromTokenAccount,
toTokenAccount,
fromPubKey,
BigInt(Math.floor(parseFloat(tokenAmount) * Math.pow(10, tokenDecimals))),
[],
TOKEN_PROGRAM_ID
)
);
}
transaction.feePayer = fromPubKey;
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
const signedTx = await solWallet.signTransaction(transaction);
txId = await connection.sendRawTransaction(signedTx.serialize());
await connection.confirmTransaction(txId, "confirmed");
const txId = await connection.sendRawTransaction(signedTx.serialize());
await connection.confirmTransaction(txId, 'confirmed');
// Send confirmation email
await axios.post(EMAIL_API_URL, {
email: userEmail,
product: gpu.title,
price_hour: gpu.price_per_hour,
price: gpu.price_usd.toFixed(2),
token: selectedToken,
});
// Store order in Supabase
const { error } = await supabase.from('orders').insert([
// Generate a random order ID (in production this would come from the backend)
const generatedOrderId = `${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}`;
setOrderId(generatedOrderId);
const { error } = await supabase.from("orders").insert([
{
gpu_id: gpu.id,
gpu: `${gpu.id} ${gpu.title}`,
user_email: userEmail,
amount_sol: parseFloat(solAmount),
sol_tx_signature: txId,
status: 'success',
amount_usd: gpu.price_usd,
token_used: tokenUsed,
token_amount: amountDisplay,
token_tx_signature: txId,
status: "success",
order_id: generatedOrderId,
},
]);
if (error) {
throw new Error(`Failed to save order: ${error.message}`);
}
if (error) throw new Error(`Failed to save order: ${error.message}`);
setSuccessMsg(`Payment successful! Transaction ID: ${txId}`);
setPaymentSuccess(true);
} catch (err: any) {
setErrorMsg(err.message || 'Payment failed.');
console.error("Payment error:", err);
setErrorMsg(err.message || "Payment failed.");
setPaymentFailure(true);
// Log the failed attempt in Supabase
const failedOrderId = `FAIL-${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 900) + 100}`;
await supabase.from("orders").insert([
{
gpu: `${gpu.id} ${gpu.title}`,
user_email: userEmail,
amount_usd: gpu.price_usd,
token_used: selectedToken,
status: "failed",
error_message: err.message || "Unknown error",
order_id: failedOrderId,
},
]);
} finally {
setLoading(false);
}
};
const handleRetry = () => {
// Reset failure state and error message
setPaymentFailure(false);
setErrorMsg(null);
// Don't reset other form values so user doesn't have to start over
};
if (!isOpen) return null;
const solAmount = solPrice ? (gpu.price_usd / solPrice).toFixed(6) : '...';
// Show failure screen if payment failed
if (paymentFailure) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div className="bg-white w-full max-w-md rounded-xl p-6 shadow-xl">
<h2 className="text-xl font-semibold text-gray-800">{gpu.title}</h2>
<p className="text-gray-600 mt-1">
Price: <span className="font-medium">${gpu.price_usd} USD</span> (~{solAmount} SOL)
</p>
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div className="relative flex flex-col gap-8 items-center p-6 bg-[#0E1618] border border-white border-opacity-10 w-full max-w-lg mx-4">
<RedCornerEdge position="left-top" />
<RedCornerEdge position="right-top" />
<RedCornerEdge position="left-bottom" />
<RedCornerEdge position="right-bottom" />
<ErrorIcon />
<div className="flex flex-col gap-1.5 items-center">
<h1 className="text-2xl text-center text-white">
PURCHASE FAILED
</h1>
<p className="text-lg text-center text-white opacity-50">
Your purchase didn't go through
</p>
</div>
<ErrorBox />
<div className="flex gap-4">
<button
onClick={handleRetry}
className="relative w-[157px] h-[51px]"
>
<div className="absolute inset-0 bg-[#F62727] bg-opacity-25" />
<div className="absolute inset-[1px] border border-white border-opacity-5" />
<span className="absolute inset-0 flex items-center justify-center font-mono text-[17.85px] font-medium text-[#F62727]">
RETRY
</span>
<RedCornerEdge position="left-top" />
<RedCornerEdge position="right-top" />
<RedCornerEdge position="left-bottom" />
<RedCornerEdge position="right-bottom" />
</button>
<button
onClick={onClose}
className="relative w-[157px] h-[51px]"
>
<div className="absolute inset-0 bg-[#333333]" />
<div className="absolute inset-[1px] border border-white border-opacity-5" />
<span className="absolute inset-0 flex items-center justify-center font-mono text-[17.85px] font-medium text-white">
CLOSE
</span>
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
</button>
</div>
</div>
</div>
);
}
// Show success screen if payment was successful
if (paymentSuccess) {
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div className="relative flex flex-col gap-8 items-center p-6 bg-[#0E1618] border border-white border-opacity-10 w-full max-w-lg mx-4">
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
<SuccessIcon />
<div className="flex flex-col gap-1.5 items-center">
<h1 className="text-2xl text-center text-white">
PURCHASE SUCCESSFUL
</h1>
<p className="text-lg text-center text-white opacity-50">
Thank you for shopping with Vertex
</p>
</div>
<OrderIdBox orderId={orderId} />
<button
onClick={onClose}
className="relative w-[157px] h-[51px]"
>
<div className="absolute inset-0 bg-[#0CE77E] bg-opacity-25" />
<div className="absolute inset-[1px] border border-white border-opacity-5" />
<span className="absolute inset-0 flex items-center justify-center font-mono text-[17.85px] font-medium text-[#0CE77E]">
CLOSE
</span>
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
</button>
</div>
</div>
);
}
// Show payment form
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80">
<div className="relative flex flex-col gap-5 p-6 bg-[#0E1618] border border-white border-opacity-10 w-full max-w-lg mx-4">
{/* Corner decoration elements */}
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
{/* Header */}
<div className="flex flex-col gap-1">
<h2 className="text-sm font-medium text-white text-opacity-50">
Selected configuration
</h2>
<h1 className="text-xl font-medium text-[#0CE77E]">
{gpu.title}
</h1>
</div>
{/* Price Display */}
<div className="relative border border-white border-opacity-10 bg-[#0CE77E]/10">
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
<div className="flex justify-between items-center p-3 w-full">
<span className="text-base font-medium text-white">Total Price</span>
<div className="flex gap-3 items-center">
<span className="text-base font-bold text-[#0CE77E]">${gpu.price_usd}</span>
<span className="text-base text-[#0CE77E]">(~{solAmount} SOL)</span>
</div>
</div>
</div>
{/* Email Input */}
{!user?.email?.address && (
<div className="relative">
<span className="block mb-1 text-sm font-medium text-white">YOUR EMAIL ADDRESS</span>
<div className="relative border border-white border-opacity-10 bg-[#0CE77E]/10">
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
<input
type="email"
placeholder="ENTER YOUR EMAIL"
value={emailInput}
onChange={(e) => setEmailInput(e.target.value)}
onBlur={() => setEmailTouched(true)}
className="w-full p-3 bg-transparent text-[#0CE77E] placeholder-[#0CE77E]/50 focus:outline-none text-base"
/>
</div>
{emailTouched && emailInput && !isValidEmail(emailInput) && (
<p className="mt-1 text-xs text-red-400">Please enter a valid email address.</p>
)}
</div>
)}
{/* Token Selection */}
<div className="relative">
<span className="block mb-1 text-sm font-medium text-white">SELECT PAYMENT TOKEN</span>
<div className="relative border border-white border-opacity-10 bg-[#0CE77E]/10">
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
<select
value={selectedToken}
onChange={(e) => setSelectedToken(e.target.value as 'SOL' | 'USDC' | 'JUP')}
className="w-full p-3 bg-transparent text-[#0CE77E] focus:outline-none text-base cursor-pointer"
>
<option value="SOL" className="bg-[#0E1618] text-[#0CE77E]">SOL</option>
<option value="USDC" className="bg-[#0E1618] text-[#0CE77E]">USDC</option>
<option value="JUP" className="bg-[#0E1618] text-[#0CE77E]">JUP</option>
</select>
</div>
</div>
{/* Error and Success Messages */}
{errorMsg && (
<div className="mt-4 rounded-md bg-red-100 px-4 py-2 text-sm text-red-700">
<div className="p-2 text-sm text-red-400 border border-red-400 bg-red-400/10">
{errorMsg}
</div>
)}
{successMsg && (
<div className="mt-4 rounded-md bg-green-100 px-4 py-2 text-sm text-green-700">
<div className="p-2 text-sm text-[#0CE77E] border border-[#0CE77E] bg-[#0CE77E]/10">
{successMsg}
</div>
)}
{/* Wallet not connected */}
{privyWallets.length === 0 ? (
<div className="mt-6 text-center">
<p className="text-sm text-gray-700 mb-4">
To proceed with payment, please connect a Solana wallet.
</p>
{/* Action Buttons */}
<div className="flex justify-between gap-3 mt-2">
{/* Confirm Button */}
<button
onClick={connectWallet}
className="px-4 py-2 rounded-lg bg-indigo-600 text-white hover:bg-indigo-700 transition"
onClick={handlePayment}
disabled={loading || (!user?.email?.address && (!emailInput || !isValidEmail(emailInput)))}
className="relative flex-1 py-3 bg-[#004d33] border border-[#0CE77E]/30 text-[#0CE77E] text-sm font-medium disabled:opacity-50 transition-all hover:bg-[#006644] hover:border-[#0CE77E]/50 hover:shadow-[0_0_12px_rgba(12,231,126,0.3)]"
>
Connect Wallet
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
{loading ? 'PROCESSING...' : 'CONFIRM PAYMENT'}
</button>
</div>
) : (
<>
{/* Show email prompt if not logged in with email */}
{!user?.email?.address && (
<div className="mt-4">
<label className="block text-sm text-gray-700 mb-1">
Email for invoice & access:
</label>
<input
type="email"
placeholder="you@example.com"
value={emailInput}
onChange={(e) => setEmailInput(e.target.value)}
onBlur={() => setEmailTouched(true)}
className="w-full px-3 py-2 border rounded-md text-sm"
/>
{emailTouched && emailInput && !isValidEmail(emailInput) && (
<p className="mt-1 text-xs text-red-600">Please enter a valid email address.</p>
)}
</div>
)}
<div className="mt-6 flex justify-end gap-3">
{/* Cancel Button */}
<button
onClick={onClose}
disabled={loading}
className="px-4 py-2 rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition"
className="relative flex-1 py-3 bg-[#333333] border border-white/30 text-white text-sm font-medium disabled:opacity-50 transition-all hover:bg-[#444444] hover:border-white/50 hover:shadow-[0_0_12px_rgba(255,255,255,0.2)]"
>
Cancel
</button>
<button
onClick={handlePayment}
disabled={
loading ||
(!user?.email?.address && (!emailInput || !isValidEmail(emailInput)))
}
className="px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 transition disabled:opacity-60"
>
{loading ? 'Processing...' : 'Confirm Payment'}
<CornerEdge position="left-top" />
<CornerEdge position="right-top" />
<CornerEdge position="left-bottom" />
<CornerEdge position="right-bottom" />
CANCEL
</button>
</div>
</>
)}
</div>
</div>
);