153 lines
4.7 KiB
TypeScript
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>
|
|
);
|
|
}
|