diff --git a/package.json b/package.json
index fe49948..2598c77 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/app/dashboard/api/orders/route.ts b/src/app/dashboard/api/orders/route.ts
new file mode 100644
index 0000000..6df271b
--- /dev/null
+++ b/src/app/dashboard/api/orders/route.ts
@@ -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: `
+
Hi,
+ Your order for GPU ID ${gpuId} has status: ${status}.
+ Amount: ${amountSol} SOL
+ ${
+ txSignature
+ ? `Transaction: ${txSignature}
`
+ : 'No transaction was recorded.
'
+ }
+ Thank you for using our platform.
+ `,
+ });
+
+ 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 });
+ }
+}
diff --git a/src/app/dashboard/rent/Boxes.tsx b/src/app/dashboard/rent/Boxes.tsx
index 2c20989..567778e 100644
--- a/src/app/dashboard/rent/Boxes.tsx
+++ b/src/app/dashboard/rent/Boxes.tsx
@@ -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(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 (
-
- {gpus.map((gpu, index) => (
-
-
-
-
-
-
{gpu.title}
+ <>
+
+ {gpus.map((gpu, index) => (
+
+
+
+ {/* GPU SVG ICON */}
+
+
{gpu.title}
+
+
{gpu.subTitle}
- {gpu.subTitle}
-
-
-
-
-
- {gpu.price}
-
-
- {gpu.isRecentlyPurchased && (
-
-
- Recently purchased
+
+
+
+ {gpu.price}
- )}
+ {gpu.isRecentlyPurchased && (
+
+
+ Recently purchased
+
+ )}
+
+
+
-
- Rent now
-
-
+ {/* Corner Decorations */}
+ {['top', 'bottom'].map((pos) =>
+ ['left', 'right'].map((side) => (
+
+
+
+ ))
+ )}
+
+ ))}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ))}
-
+ {selectedGpu && (
+
setSelectedGpu(null)}
+ gpu={{
+ id: selectedGpu.id,
+ title: selectedGpu.title,
+ price_usd: selectedGpu.price_usd,
+ }}
+ />
+ )}
+ >
);
}
diff --git a/src/components/GpuPaymentModal.tsx b/src/components/GpuPaymentModal.tsx
new file mode 100644
index 0000000..f9683c5
--- /dev/null
+++ b/src/components/GpuPaymentModal.tsx
@@ -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(null);
+ const [loading, setLoading] = useState(false);
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [successMsg, setSuccessMsg] = useState(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 (
+
+
+
{gpu.title}
+
+ Price: ${gpu.price_usd} USD (~{solAmount} SOL)
+
+
+ {errorMsg && (
+
+ {errorMsg}
+
+ )}
+
+ {successMsg && (
+
+ {successMsg}
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default GpuPaymentModal;
diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx
index a28ffe3..a9ca767 100644
--- a/src/components/Providers.tsx
+++ b/src/components/Providers.tsx
@@ -11,7 +11,7 @@ const solanaConnectors = toSolanaWalletConnectors({
export default function Providers({ children }: { children: React.ReactNode }) {
return (