boxy/app/components/PaymentModal.tsx
2025-06-25 01:34:47 +05:30

153 lines
4.7 KiB
TypeScript

'use client';
import { useState } from 'react';
import { useAuth } from '../context/AuthContext';
import { getStripe } from '../lib/stripe-client';
interface PaymentModalProps {
isOpen: boolean;
onClose: () => void;
box: {
id: string;
tier: string;
price: number;
description: string;
} | null;
}
export default function PaymentModal({ isOpen, onClose, box }: PaymentModalProps) {
const { user } = useAuth();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handlePayment = async () => {
if (!user || !box) return;
setIsLoading(true);
setError(null);
try {
console.log('Creating checkout session for:', box);
// Create checkout session
const response = await fetch('/api/create-checkout-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
boxId: box.id,
boxName: box.tier,
amount: box.price,
currency: 'usd',
}),
});
console.log('Checkout session response status:', response.status);
if (!response.ok) {
const errorData = await response.json();
console.error('Checkout session error:', errorData);
throw new Error(errorData.error || 'Failed to create checkout session');
}
const { sessionId } = await response.json();
console.log('Checkout session created:', { sessionId });
if (!sessionId) {
throw new Error('No session ID received from server');
}
// Redirect to Stripe Checkout
const stripe = await getStripe();
if (!stripe) {
throw new Error('Stripe failed to load');
}
console.log('Redirecting to Stripe Checkout...');
const { error: stripeError } = await stripe.redirectToCheckout({
sessionId,
});
if (stripeError) {
console.error('Stripe error:', stripeError);
throw new Error(stripeError.message);
}
} catch (err) {
console.error('Payment error:', err);
setError(err instanceof Error ? err.message : 'Payment failed');
} finally {
setIsLoading(false);
}
};
if (!isOpen || !box) return null;
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-background rounded-2xl p-6 max-w-md w-full">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<h2 className="text-2xl font-bold">Open {box.tier} Box</h2>
<button
onClick={onClose}
className="text-foreground/60 hover:text-foreground transition-colors"
>
</button>
</div>
{/* Box Details */}
<div className="mb-6">
<div className="flex items-center gap-4 mb-4">
<div className="w-16 h-16 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg flex items-center justify-center">
<span className="text-2xl">🎁</span>
</div>
<div>
<h3 className="text-xl font-semibold">{box.tier} Box</h3>
<p className="text-foreground/60 text-sm">{box.description}</p>
</div>
</div>
<div className="bg-foreground/5 rounded-lg p-4">
<div className="flex justify-between items-center">
<span className="text-foreground/60">Price:</span>
<span className="text-xl font-bold">${box.price}</span>
</div>
</div>
</div>
{/* Error Message */}
{error && (
<div className="mb-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
<p className="text-red-500 text-sm">{error}</p>
</div>
)}
{/* Action Buttons */}
<div className="flex gap-3">
<button
onClick={onClose}
className="flex-1 px-4 py-3 rounded-lg border border-foreground/20 hover:bg-foreground/5 transition-colors"
disabled={isLoading}
>
Cancel
</button>
<button
onClick={handlePayment}
disabled={isLoading}
className="flex-1 px-4 py-3 rounded-lg bg-gradient-to-r from-purple-600 to-pink-600 text-white font-semibold hover:opacity-90 transition-opacity disabled:opacity-50"
>
{isLoading ? 'Processing...' : 'Pay & Open Box'}
</button>
</div>
{/* Security Notice */}
<p className="text-xs text-foreground/40 text-center mt-4">
Your payment is secured by Stripe. We never store your card details.
</p>
</div>
</div>
);
}