veza/apps/web/src/components/views/CheckoutView.tsx

282 lines
10 KiB
TypeScript

import React, { useState } from 'react';
import { Card } from '../ui/card';
import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { useCart } from '../../context/CartContext';
import { CreditCard, Lock, ArrowLeft, Loader2 } from 'lucide-react';
import { useToast } from '../../context/ToastContext';
import { marketplaceService } from '../../services/marketplaceService';
import { logger } from '@/utils/logger';
interface CheckoutViewProps {
onBack: () => void;
onComplete: () => void;
}
export const CheckoutView: React.FC<CheckoutViewProps> = ({
onBack,
onComplete,
}) => {
const { cart, cartTotal, clearCart } = useCart();
const { addToast } = useToast();
const [loading, setLoading] = useState(false);
const tax = cartTotal * 0.08;
// const total = cartTotal + tax; // Calculated but not strictly needed for the API call in this mock
const [form, setForm] = useState({
fullName: '',
email: '',
address: '',
city: '',
zip: '',
country: 'United States',
saveInfo: true,
acceptTerms: false,
});
const handlePurchase = async () => {
if (!form.fullName || !form.email || !form.address) {
addToast('Please fill in all billing fields', 'error');
return;
}
if (!form.acceptTerms) {
addToast('Please accept the terms and conditions', 'error');
return;
}
setLoading(true);
try {
// Map cart items to API format
const orderItems = cart.map((item) => ({ product_id: item.id }));
await marketplaceService.createOrder(orderItems);
addToast('Purchase successful!', 'success');
clearCart();
onComplete();
} catch (error) {
addToast('Payment failed. Please try again.', 'error');
logger.error('Payment failed', {
error: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
});
} finally {
setLoading(false);
}
};
return (
<div className="animate-fadeIn max-w-5xl mx-auto pb-20">
<Button
variant="ghost"
onClick={onBack}
className="mb-6 pl-0 text-gray-400 hover:text-white"
>
<ArrowLeft className="w-4 h-4 mr-2" /> Back to Cart
</Button>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Left: Billing & Payment */}
<div className="lg:col-span-2 space-y-8">
{/* Billing Info */}
<Card variant="default">
<h3 className="font-bold text-white mb-6 border-b border-gray-700 pb-2">
Billing Information
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<Input
label="Full Name"
placeholder="John Doe"
value={form.fullName}
onChange={(e) => setForm({ ...form, fullName: e.target.value })}
/>
<Input
label="Email Address"
placeholder="john@example.com"
type="email"
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
/>
</div>
<Input
label="Address"
placeholder="123 Studio Blvd"
value={form.address}
onChange={(e) => setForm({ ...form, address: e.target.value })}
className="mb-4"
/>
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<Input
label="City"
placeholder="New York"
value={form.city}
onChange={(e) => setForm({ ...form, city: e.target.value })}
/>
<Input
label="Zip / Postal"
placeholder="10001"
value={form.zip}
onChange={(e) => setForm({ ...form, zip: e.target.value })}
/>
<div>
<label className="block text-sm font-medium text-gray-400 mb-2">
Country
</label>
<select
className="w-full bg-kodo-graphite border border-kodo-steel rounded-lg py-3 px-4 text-white focus:border-kodo-cyan outline-none appearance-none"
value={form.country}
onChange={(e) =>
setForm({ ...form, country: e.target.value })
}
>
<option>United States</option>
<option>Canada</option>
<option>United Kingdom</option>
<option>Japan</option>
<option>Germany</option>
</select>
</div>
</div>
</Card>
{/* Payment Method (Visual Mock) */}
<Card variant="default">
<h3 className="font-bold text-white mb-6 border-b border-gray-700 pb-2 flex items-center gap-2">
<Lock className="w-4 h-4 text-kodo-lime" /> Payment Method
</h3>
<div className="bg-kodo-ink p-4 rounded-xl border border-kodo-steel mb-4">
<div className="flex items-center gap-3 mb-4">
<CreditCard className="w-5 h-5 text-kodo-cyan" />
<span className="font-bold text-white">Credit Card</span>
<div className="flex gap-1 ml-auto">
<div className="w-8 h-5 bg-gray-700 rounded"></div>
<div className="w-8 h-5 bg-gray-700 rounded"></div>
<div className="w-8 h-5 bg-gray-700 rounded"></div>
</div>
</div>
{/* Stripe Element Simulation */}
<div className="space-y-3">
<div className="bg-kodo-graphite border border-kodo-steel rounded px-3 py-3 text-sm text-gray-400 flex items-center justify-between">
<span>4242 4242 4242 4242</span>
<CreditCard className="w-4 h-4" />
</div>
<div className="grid grid-cols-2 gap-3">
<div className="bg-kodo-graphite border border-kodo-steel rounded px-3 py-3 text-sm text-gray-400">
MM / YY
</div>
<div className="bg-kodo-graphite border border-kodo-steel rounded px-3 py-3 text-sm text-gray-400">
CVC
</div>
</div>
</div>
</div>
<div className="space-y-2">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
className="rounded border-gray-600 bg-transparent text-kodo-cyan focus:ring-0"
checked={form.saveInfo}
onChange={(e) =>
setForm({ ...form, saveInfo: e.target.checked })
}
/>
<span className="text-sm text-gray-400">
Save payment information for future purchases
</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
className="rounded border-gray-600 bg-transparent text-kodo-cyan focus:ring-0"
checked={form.acceptTerms}
onChange={(e) =>
setForm({ ...form, acceptTerms: e.target.checked })
}
/>
<span className="text-sm text-gray-400">
I accept the{' '}
<span className="text-white hover:underline">
Terms of Service
</span>{' '}
and{' '}
<span className="text-white hover:underline">
Licensing Agreement
</span>
</span>
</label>
</div>
</Card>
</div>
{/* Right: Order Summary */}
<div className="space-y-6">
<div className="bg-kodo-ink border border-kodo-steel rounded-xl p-6 sticky top-24 shadow-2xl">
<h3 className="font-bold text-white mb-4 uppercase tracking-wider text-sm">
Order Summary
</h3>
<div className="space-y-3 max-h-60 overflow-y-auto custom-scrollbar mb-6 pr-2">
{cart.map((item) => (
<div
key={item.cartId}
className="flex justify-between items-start text-sm"
>
<div className="flex-1 pr-4">
<div className="text-gray-300 font-medium truncate">
{item.title}
</div>
<div className="text-xs text-gray-500">
{item.selectedLicense?.name || 'Standard'} License
</div>
</div>
<div className="text-white font-mono">
${(item.selectedLicense?.price || item.price).toFixed(2)}
</div>
</div>
))}
</div>
<div className="border-t border-gray-700 pt-4 space-y-2 mb-6">
<div className="flex justify-between text-sm text-gray-400">
<span>Subtotal</span>
<span>${cartTotal.toFixed(2)}</span>
</div>
<div className="flex justify-between text-sm text-gray-400">
<span>Taxes</span>
<span>${tax.toFixed(2)}</span>
</div>
<div className="flex justify-between items-end pt-2">
<span className="font-bold text-white">Total</span>
<span className="text-2xl font-mono font-bold text-kodo-cyan">
${(cartTotal + tax).toFixed(2)}
</span>
</div>
</div>
<Button
variant="primary"
className="w-full h-12 text-base shadow-neon-cyan/20"
onClick={handlePurchase}
disabled={loading}
>
{loading ? (
<span className="flex items-center gap-2">
<Loader2 className="w-4 h-4 animate-spin" /> Processing...
</span>
) : (
'COMPLETE PURCHASE'
)}
</Button>
<div className="text-center mt-4 flex items-center justify-center gap-2 text-xs text-gray-500">
<Lock className="w-3 h-3" /> 256-bit SSL Encrypted
</div>
</div>
</div>
</div>
</div>
);
};