new payment system

This commit is contained in:
shialoth 2025-04-30 00:34:19 +05:30
parent e56a469cfa
commit a2af10606e
5 changed files with 388 additions and 193 deletions

View File

@ -11,10 +11,20 @@
"dependencies": {
"@geist-ui/core": "^2.3.8",
"@privy-io/react-auth": "^2.0.4",
"@solana/spl-token": "^0.4.13",
"@solana/wallet-adapter-base": "^0.9.26",
"@solana/wallet-adapter-phantom": "^0.9.27",
"@solana/wallet-adapter-react": "^0.15.38",
"@solana/wallet-adapter-react-ui": "^0.9.38",
"@solana/wallet-adapter-wallets": "^0.19.36",
"@solana/web3.js": "^1.98.2",
"@supabase/supabase-js": "^2.49.4",
"axios": "^1.9.0",
"chart.js": "^4.4.7",
"framer-motion": "^11.18.1",
"geist": "^1.3.1",
"next": "^14.2.23",
"nodemailer": "^6.10.1",
"react": "^18.2.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^18.2.0",
@ -24,6 +34,7 @@
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/nodemailer": "^6.4.17",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8.0.0",

View File

@ -0,0 +1,70 @@
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@supabase/supabase-js';
import nodemailer from 'nodemailer';
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
const emailUser = process.env.EMAIL_USER!;
const emailPass = process.env.EMAIL_PASS!;
const businessEmail = process.env.BUSINESS_EMAIL!;
export async function POST(req: NextRequest) {
try {
const { gpuId, userEmail, txSignature, amountSol, status } = await req.json();
if (!gpuId || !userEmail || !amountSol || !status) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
}
if (!['pending', 'success', 'failed'].includes(status)) {
return NextResponse.json({ error: 'Invalid status value' }, { status: 400 });
}
const { error } = await supabase.from('orders').insert({
gpu_id: gpuId,
user_email: userEmail,
amount_sol: amountSol,
sol_tx_signature: txSignature || null,
status,
});
if (error) {
console.error('[Supabase error]', error);
return NextResponse.json({ error: 'Database error' }, { status: 500 });
}
// Send confirmation email
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: emailUser,
pass: emailPass,
},
});
await transporter.sendMail({
from: `"GPU Store" <${emailUser}>`,
to: userEmail,
subject: `GPU Payment ${status === 'success' ? 'Confirmed' : 'Attempted'}`,
html: `
<p>Hi,</p>
<p>Your order for GPU ID <strong>${gpuId}</strong> has status: <strong>${status}</strong>.</p>
<p>Amount: ${amountSol} SOL</p>
${
txSignature
? `<p>Transaction: <a href="https://solscan.io/tx/${txSignature}" target="_blank">${txSignature}</a></p>`
: '<p>No transaction was recorded.</p>'
}
<p>Thank you for using our platform.</p>
`,
});
return NextResponse.json({ message: 'Order recorded and email sent' });
} catch (err: any) {
console.error('[Order API Error]', err);
return NextResponse.json({ error: 'Server error' }, { status: 500 });
}
}

View File

@ -1,234 +1,185 @@
"use client";
import Link from "next/link";
import { motion } from "framer-motion";
'use client';
import { useState } from 'react';
import { motion } from 'framer-motion';
import GpuPaymentModal from '@/components/GpuPaymentModal'
interface IGPU {
id: string;
title: string;
subTitle: string;
price: string;
price_usd: number;
isRecentlyPurchased?: boolean;
link: string;
}
export default function Boxes() {
const [selectedGpu, setSelectedGpu] = useState<IGPU | null>(null);
const gpus: IGPU[] = [
{
title: "NVIDIA RTX A4000",
subTitle: "16GB GDDR6 - 6144 CUDA Cores",
price: "$0.20/hour ($147/mo)",
id: 'a4000',
title: 'NVIDIA RTX A4000',
subTitle: '16GB GDDR6 - 6144 CUDA Cores',
price: '$0.20/hour ($147/mo)',
price_usd: 147,
isRecentlyPurchased: true,
link: "https://nowpayments.io/payment?iid=5964763281",
},
{
title: "NVIDIA RTX A5000",
subTitle: "24 GB GDDR6 - 8192 CUDA Cores",
price: "$0.26/hr ($190/month)",
link: "https://nowpayments.io/payment?iid=4873013025",
id: 'a5000',
title: 'NVIDIA RTX A5000',
subTitle: '24 GB GDDR6 - 8192 CUDA Cores',
price: '$0.26/hr ($190/month)',
price_usd: 190,
},
{
title: "4x NVIDIA RTX 4090",
subTitle: "24 GB GDDR6X - 16384 GPU CUDA Cores",
price: "$0.37/hr ($268/month)",
link: "https://nowpayments.io/payment?iid=5977605820",
id: '4x4090',
title: '4x NVIDIA RTX 4090',
subTitle: '24 GB GDDR6X - 16384 GPU CUDA Cores',
price: '$0.37/hr ($268/month)',
price_usd: 268,
},
{
title: "NVIDIA V100",
subTitle: "32 GB HBM2 - 5120 CUDA Cores",
price: "$0.44/hr ($317/month)",
link: "https://nowpayments.io/payment?iid=5437270588",
id: 'v100',
title: 'NVIDIA V100',
subTitle: '32 GB HBM2 - 5120 CUDA Cores',
price: '$0.44/hr ($317/month)',
price_usd: 317,
},
{
title: "NVIDIA L4 Ada",
subTitle: "24 GB GDDR6 - 7680 CUDA Cores",
price: "$0.52/hr ($372/month)",
link: "https://nowpayments.io/payment?iid=6283623234",
id: 'l4ada',
title: 'NVIDIA L4 Ada',
subTitle: '24 GB GDDR6 - 7680 CUDA Cores',
price: '$0.52/hr ($372/month)',
price_usd: 372,
},
{
title: "NVIDIA RTX A6000",
subTitle: "48 GB GDDR6 - 10752 CUDA Cores",
price: "$0.53/hr ($380/month)",
link: "https://nowpayments.io/payment?iid=6074730706",
id: 'a6000',
title: 'NVIDIA RTX A6000',
subTitle: '48 GB GDDR6 - 10752 CUDA Cores',
price: '$0.53/hr ($380/month)',
price_usd: 380,
},
{
title: "NVIDIA L40S Ada",
subTitle: "48 GB GDDR6 - 18176 CUDA Cores",
price: "$1.24/hr ($890/month)",
link: "https://nowpayments.io/payment?iid=4570621084",
id: 'l40sada',
title: 'NVIDIA L40S Ada',
subTitle: '48 GB GDDR6 - 18176 CUDA Cores',
price: '$1.24/hr ($890/month)',
price_usd: 890,
},
{
title: "NVIDIA A100 PCIe",
subTitle: "40 GB HBM2 - 6912 CUDA Cores",
price: "$1.62/hr ($1,166/month)",
link: "https://nowpayments.io/payment?iid=6381921922",
id: 'a100',
title: 'NVIDIA A100 PCIe',
subTitle: '40 GB HBM2 - 6912 CUDA Cores',
price: '$1.62/hr ($1,166/month)',
price_usd: 1166,
},
{
title: "NVIDIA H100 NVL",
subTitle: "94 GB HBM3 - 14592 CUDA Cores",
price: "$3.35/hr ($2,410/month)",
link: "https://nowpayments.io/payment?iid=5319698362",
id: 'h100nvl',
title: 'NVIDIA H100 NVL',
subTitle: '94 GB HBM3 - 14592 CUDA Cores',
price: '$3.35/hr ($2,410/month)',
price_usd: 2410,
},
{
title: "NVIDIA H100 SXM",
subTitle: "80 GB HBM3 - 14592 CUDA Cores",
price: "$3.60/hr ($2,592/month)",
link: "https://nowpayments.io/payment?iid=4768823499",
id: 'h100sxm',
title: 'NVIDIA H100 SXM',
subTitle: '80 GB HBM3 - 14592 CUDA Cores',
price: '$3.60/hr ($2,592/month)',
price_usd: 2592,
},
];
return (
<div className="grid grid-cols-2 gap-4">
{gpus.map((gpu, index) => (
<motion.div
key={index}
initial={{
y: 25,
opacity: 0,
}}
whileInView={{
y: 0,
opacity: 1,
}}
transition={{
delay: 0.2 * index,
}}
viewport={{
once: true,
}}
className="group relative p-5 border border-white/5 bg-[#0E1618]/10 hover:bg-[#0CE77E]/25 backdrop-blur-sm duration-200"
>
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
<svg
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="40" height="40" rx="3" fill="#0CE77E" />
<rect
width="40"
height="40"
rx="3"
fill="url(#paint0_linear_26_99714)"
fillOpacity="0.5"
/>
<path
d="M17.0338 16.8167V15.3963C17.1707 15.3877 17.3162 15.3791 17.4531 15.3791C21.3551 15.2593 23.9051 18.7249 23.9051 18.7249C23.9051 18.7249 21.1498 22.5585 18.189 22.5585C17.7612 22.5585 17.3847 22.49 17.0424 22.3702V18.0489C18.557 18.2372 18.865 18.9046 19.7806 20.4192L21.8087 18.7078C21.8087 18.7078 20.3283 16.7654 17.8296 16.7654C17.5558 16.7739 17.2905 16.7911 17.0338 16.8167ZM17.0338 12.1104V14.2325C17.1707 14.2239 17.3162 14.2154 17.4531 14.2068C22.8697 14.0186 26.4038 18.6479 26.4038 18.6479C26.4038 18.6479 22.3478 23.5853 18.1291 23.5853C17.7441 23.5853 17.3847 23.5511 17.0424 23.4912V24.809C17.3333 24.8432 17.6414 24.8689 17.9494 24.8689C21.8857 24.8689 24.7266 22.858 27.482 20.4877C27.9355 20.8557 29.8095 21.7456 30.1946 22.1306C27.5761 24.3213 21.4749 26.0926 18.0179 26.0926C17.6842 26.0926 17.3676 26.0754 17.0509 26.0412V27.8895H32.0001V12.1104H17.0338ZM17.0338 22.3788V23.4998C13.3971 22.8494 12.3874 19.0672 12.3874 19.0672C12.3874 19.0672 14.133 17.1333 17.0338 16.8167V18.0489H17.0253C15.5107 17.8607 14.3127 19.2897 14.3127 19.2897C14.3127 19.2897 14.9887 21.6771 17.0338 22.3788ZM10.5733 18.9046C10.5733 18.9046 12.7296 15.73 17.0338 15.3963V14.2411C12.2676 14.6261 8.14307 18.665 8.14307 18.665C8.14307 18.665 10.4791 25.4251 17.0338 26.0498V24.8176C12.2248 24.21 10.5733 18.9046 10.5733 18.9046Z"
fill="white"
/>
<defs>
<linearGradient
id="paint0_linear_26_99714"
x1="20"
y1="0"
x2="20"
y2="40"
gradientUnits="userSpaceOnUse"
>
<stop stopOpacity="0" />
<stop offset="1" />
</linearGradient>
</defs>
</svg>
<p className="font-medium text-[#0CE77E]">{gpu.title}</p>
<>
<div className="grid grid-cols-2 gap-4">
{gpus.map((gpu, index) => (
<motion.div
key={gpu.id}
initial={{ y: 25, opacity: 0 }}
whileInView={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 * index }}
viewport={{ once: true }}
className="group relative p-5 border border-white/5 bg-[#0E1618]/10 hover:bg-[#0CE77E]/25 backdrop-blur-sm duration-200"
>
<div className="flex justify-between items-center">
<div className="flex items-center gap-3">
{/* GPU SVG ICON */}
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="3" fill="#0CE77E" />
<rect width="40" height="40" rx="3" fill="url(#gpuGrad)" fillOpacity="0.5" />
<path d="M17.0338 16.8167V15.3963C17.1707 15.3877 17.3162 15.3791 17.4531 15.3791C21.3551 15.2593 23.9051 18.7249 23.9051 18.7249C23.9051 18.7249 21.1498 22.5585 18.189 22.5585C17.7612 22.5585 17.3847 22.49 17.0424 22.3702V18.0489C18.557 18.2372 18.865 18.9046 19.7806 20.4192L21.8087 18.7078C21.8087 18.7078 20.3283 16.7654 17.8296 16.7654C17.5558 16.7739 17.2905 16.7911 17.0338 16.8167ZM17.0338 12.1104V14.2325C17.1707 14.2239 17.3162 14.2154 17.4531 14.2068C22.8697 14.0186 26.4038 18.6479 26.4038 18.6479C26.4038 18.6479 22.3478 23.5853 18.1291 23.5853C17.7441 23.5853 17.3847 23.5511 17.0424 23.4912V24.809C17.3333 24.8432 17.6414 24.8689 17.9494 24.8689C21.8857 24.8689 24.7266 22.858 27.482 20.4877C27.9355 20.8557 29.8095 21.7456 30.1946 22.1306C27.5761 24.3213 21.4749 26.0926 18.0179 26.0926C17.6842 26.0926 17.3676 26.0754 17.0509 26.0412V27.8895H32.0001V12.1104H17.0338ZM17.0338 22.3788V23.4998C13.3971 22.8494 12.3874 19.0672 12.3874 19.0672C12.3874 19.0672 14.133 17.1333 17.0338 16.8167V18.0489H17.0253C15.5107 17.8607 14.3127 19.2897 14.3127 19.2897C14.3127 19.2897 14.9887 21.6771 17.0338 22.3788ZM10.5733 18.9046C10.5733 18.9046 12.7296 15.73 17.0338 15.3963V14.2411C12.2676 14.6261 8.14307 18.665 8.14307 18.665C8.14307 18.665 10.4791 25.4251 17.0338 26.0498V24.8176C12.2248 24.21 10.5733 18.9046 10.5733 18.9046Z" fill="white" />
<defs>
<linearGradient id="gpuGrad" x1="20" y1="0" x2="20" y2="40" gradientUnits="userSpaceOnUse">
<stop stopOpacity="0" />
<stop offset="1" />
</linearGradient>
</defs>
</svg>
<p className="font-medium text-[#0CE77E]">{gpu.title}</p>
</div>
<p className="text-sm text-white/75">{gpu.subTitle}</p>
</div>
<p className="text-sm text-white/75">{gpu.subTitle}</p>
</div>
<div className="mt-5 flex justify-between items-center">
<div className="flex items-center gap-3">
<p className="p-2.5 border border-white/10 text-sm font-medium text-[#0CE77E]">
{gpu.price}
</p>
{gpu.isRecentlyPurchased && (
<p className="py-1.5 px-2 border border-[#0CE77E] bg-[#0CE77E]/10 flex items-center gap-1.5 text-xs font-medium text-[#0CE77E]">
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.58334 1.16675L2.38785 7.40136C2.18438 7.64548 2.08265 7.76757 2.08109 7.87071C2.07973 7.96031 2.11968 8.04559 2.1894 8.10194C2.2696 8.16675 2.42852 8.16675 2.74636 8.16675H7.00001L6.41668 12.8334L11.6121 6.59881C11.8156 6.35468 11.9173 6.23259 11.9189 6.12946C11.9203 6.03986 11.8804 5.95457 11.8106 5.89822C11.7304 5.83342 11.5715 5.83342 11.2537 5.83342H7.00001L7.58334 1.16675Z"
fill="#0CE77E"
stroke="#0CE77E"
strokeWidth="1.16667"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Recently purchased
<div className="mt-5 flex justify-between items-center">
<div className="flex items-center gap-3">
<p className="p-2.5 border border-white/10 text-sm font-medium text-[#0CE77E]">
{gpu.price}
</p>
)}
{gpu.isRecentlyPurchased && (
<p className="py-1.5 px-2 border border-[#0CE77E] bg-[#0CE77E]/10 flex items-center gap-1.5 text-xs font-medium text-[#0CE77E]">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
<path
d="M7.58334 1.16675L2.38785 7.40136C2.18438 7.64548 2.08265 7.76757 2.08109 7.87071C2.07973 7.96031 2.11968 8.04559 2.1894 8.10194C2.2696 8.16675 2.42852 8.16675 2.74636 8.16675H7.00001L6.41668 12.8334L11.6121 6.59881C11.8156 6.35468 11.9173 6.23259 11.9189 6.12946C11.9203 6.03986 11.8804 5.95457 11.8106 5.89822C11.7304 5.83342 11.5715 5.83342 11.2537 5.83342H7.00001L7.58334 1.16675Z"
fill="#0CE77E"
stroke="#0CE77E"
strokeWidth="1.16667"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
Recently purchased
</p>
)}
</div>
<button
onClick={() => setSelectedGpu(gpu)}
className="py-2.5 px-5 border border-[#0CE77E] bg-[#0CE77E]/10 hover:bg-[#0CE77E] text-sm font-medium uppercase text-[#0CE77E] hover:text-[#0B1516] duration-200"
>
Rent now
</button>
</div>
<Link
href={gpu.link}
target="_blank"
className="py-2.5 px-5 border border-[#0CE77E] bg-[#0CE77E]/10 hover:bg-[#0CE77E] text-sm font-medium uppercase text-[#0CE77E] hover:text-[#0B1516] duration-200"
>
Rent now
</Link>
</div>
{/* Corner Decorations */}
{['top', 'bottom'].map((pos) =>
['left', 'right'].map((side) => (
<span
key={`${pos}-${side}`}
className={`absolute -${pos}-px -${side}-px opacity-25 group-hover:opacity-100 duration-200`}
>
<svg width="6" height="5" viewBox="0 0 6 5" fill="none">
<path d={pos === 'top' ? 'M1 0V4.5' : 'M1 4.5V0'} stroke="#0CE77E" />
<path d={pos === 'top' ? 'M5.5 0.5L1 0.5' : 'M5.5 4L1 4'} stroke="#0CE77E" />
</svg>
</span>
))
)}
</motion.div>
))}
</div>
<span className="absolute -top-px -left-px opacity-25 group-hover:opacity-100 duration-200">
<svg
width="6"
height="5"
viewBox="0 0 6 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M1 0V4.5" stroke="#0CE77E" />
<path d="M5.5 0.5L1 0.5" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -bottom-px -left-px opacity-25 group-hover:opacity-100 duration-200">
<svg
width="6"
height="5"
viewBox="0 0 6 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M1 4.5V0" stroke="#0CE77E" />
<path d="M5.5 4L1 4" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -top-px -right-px opacity-25 group-hover:opacity-100 duration-200">
<svg
width="6"
height="5"
viewBox="0 0 6 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 0V4.5" stroke="#0CE77E" />
<path d="M0.5 0.5L5 0.5" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -bottom-px -right-px opacity-25 group-hover:opacity-100 duration-200">
<svg
width="6"
height="5"
viewBox="0 0 6 5"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5 4.5V0" stroke="#0CE77E" />
<path d="M0.5 4L5 4" stroke="#0CE77E" />
</svg>
</span>
</motion.div>
))}
</div>
{selectedGpu && (
<GpuPaymentModal
isOpen={true}
onClose={() => setSelectedGpu(null)}
gpu={{
id: selectedGpu.id,
title: selectedGpu.title,
price_usd: selectedGpu.price_usd,
}}
/>
)}
</>
);
}

View File

@ -0,0 +1,163 @@
'use client';
import { useEffect, useState } from 'react';
import {
Connection,
PublicKey,
Transaction,
SystemProgram,
} from '@solana/web3.js';
import axios from 'axios';
import { usePrivy } from '@privy-io/react-auth';
import {
useSolanaWallets,
useSignTransaction,
} from '@privy-io/react-auth/solana';
interface GpuPaymentModalProps {
isOpen: boolean;
onClose: () => void;
gpu: {
id: string;
title: string;
price_usd: number;
};
}
const SOLANA_RPC = 'https://api.mainnet-beta.solana.com';
const BUSINESS_WALLET = 'Y93ednSpre2XRjPTBafHU1BXPXKMPhujcYAEshS5pXm8K'; // <-- Replace this
export const GpuPaymentModal = ({ isOpen, onClose, gpu }: GpuPaymentModalProps) => {
const { user } = usePrivy();
const { wallets } = useSolanaWallets();
const { signTransaction } = useSignTransaction();
const [solPrice, setSolPrice] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [successMsg, setSuccessMsg] = useState<string | null>(null);
const connection = new Connection(SOLANA_RPC);
useEffect(() => {
const fetchSolPrice = async () => {
try {
const { data } = await axios.get(
'https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd'
);
setSolPrice(data.solana.usd);
} catch {
setErrorMsg('Failed to fetch SOL price.');
}
};
if (isOpen) {
fetchSolPrice();
setErrorMsg(null);
setSuccessMsg(null);
}
}, [isOpen]);
const handlePayment = async () => {
setLoading(true);
setErrorMsg(null);
setSuccessMsg(null);
try {
const solWallet = wallets[0];
if (!solWallet || !solWallet.address) {
throw new Error('No connected Solana wallet found.');
}
const fromPubKey = new PublicKey(solWallet.address);
const toPubKey = new PublicKey(BUSINESS_WALLET);
const solAmount = (gpu.price_usd / (solPrice || 1)).toFixed(6);
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: fromPubKey,
toPubkey: toPubKey,
lamports: Math.floor(parseFloat(solAmount) * 1e9),
})
);
transaction.feePayer = fromPubKey;
const { blockhash } = await connection.getRecentBlockhash();
transaction.recentBlockhash = blockhash;
const signedTx = await signTransaction({
transaction,
connection,
address: solWallet.address,
});
const txId = await connection.sendRawTransaction(signedTx.serialize());
await connection.confirmTransaction(txId, 'confirmed');
await axios.post('/api/orders', {
gpuId: gpu.id,
userEmail: user?.email?.address,
txSignature: txId,
status: 'success',
});
setSuccessMsg(`Payment successful! Transaction ID: ${txId}`);
} catch (err: any) {
setErrorMsg(err.message || 'Payment failed.');
await axios.post('/api/orders', {
gpuId: gpu.id,
userEmail: user?.email?.address,
txSignature: null,
status: 'failed',
});
} finally {
setLoading(false);
}
};
if (!isOpen) return null;
const solAmount = solPrice ? (gpu.price_usd / solPrice).toFixed(6) : '...';
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>
{errorMsg && (
<div className="mt-4 rounded-md bg-red-100 px-4 py-2 text-sm text-red-700">
{errorMsg}
</div>
)}
{successMsg && (
<div className="mt-4 rounded-md bg-green-100 px-4 py-2 text-sm text-green-700">
{successMsg}
</div>
)}
<div className="mt-6 flex justify-end gap-3">
<button
onClick={onClose}
disabled={loading}
className="px-4 py-2 rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-100 transition"
>
Cancel
</button>
<button
onClick={handlePayment}
disabled={loading}
className="px-4 py-2 rounded-lg bg-green-600 text-white hover:bg-green-700 transition disabled:opacity-60"
>
{loading ? 'Processing...' : 'Confirm Payment'}
</button>
</div>
</div>
</div>
);
};
export default GpuPaymentModal;

View File

@ -11,7 +11,7 @@ const solanaConnectors = toSolanaWalletConnectors({
export default function Providers({ children }: { children: React.ReactNode }) {
return (
<PrivyProvider
appId="cm5rifywi0397bjqma3ylfo1p"
appId="cm9sh02lb00lblb0mmebf4xle"
config={{
appearance: {
accentColor: "#0ce87e",