741 lines
26 KiB
TypeScript
741 lines
26 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useEffect, useCallback } from 'react'
|
|
import { useSession } from 'next-auth/react'
|
|
import { useRouter } from 'next/navigation'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
import { Separator } from '@/components/ui/separator'
|
|
import { Badge } from '@/components/ui/badge'
|
|
import { ArrowLeft, CreditCard, Truck, Plus, MapPin } from 'lucide-react'
|
|
import { cartManager } from '@/lib/cart'
|
|
import { toast } from 'sonner'
|
|
import Link from 'next/link'
|
|
import Image from 'next/image'
|
|
|
|
interface CartItem {
|
|
id: string
|
|
name: string
|
|
price: number
|
|
quantity: number
|
|
image: string | null
|
|
}
|
|
|
|
interface Address {
|
|
id: string
|
|
firstName: string
|
|
lastName: string
|
|
company?: string
|
|
address1: string
|
|
address2?: string
|
|
city: string
|
|
state: string
|
|
zipCode: string
|
|
country: string
|
|
phone?: string
|
|
isDefault: boolean
|
|
type: 'HOME' | 'WORK' | 'OTHER'
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
Razorpay: any;
|
|
}
|
|
}
|
|
|
|
export default function CheckoutPage() {
|
|
const router = useRouter()
|
|
const { data: session, status } = useSession()
|
|
const [cart, setCart] = useState<CartItem[]>([])
|
|
const [addresses, setAddresses] = useState<Address[]>([])
|
|
const [selectedAddressId, setSelectedAddressId] = useState<string>('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [useNewAddress, setUseNewAddress] = useState(false)
|
|
const [formData, setFormData] = useState({
|
|
email: '',
|
|
firstName: '',
|
|
lastName: '',
|
|
address: '',
|
|
city: '',
|
|
state: '',
|
|
zipCode: '',
|
|
phone: ''
|
|
})
|
|
const [saveAddress, setSaveAddress] = useState(false)
|
|
const [processingPayment, setProcessingPayment] = useState(false)
|
|
|
|
const loadCart = useCallback(() => {
|
|
const cartItems = cartManager.getCart()
|
|
setCart(cartItems || [])
|
|
|
|
if (cartItems.length === 0) {
|
|
router.push('/products')
|
|
}
|
|
}, [router])
|
|
|
|
const fetchAddresses = useCallback(async () => {
|
|
if (status !== 'authenticated') return
|
|
|
|
try {
|
|
const response = await fetch('/api/user/addresses')
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
const userAddresses = data.addresses || []
|
|
setAddresses(userAddresses)
|
|
|
|
// Auto-select default address
|
|
const defaultAddress = userAddresses.find((addr: Address) => addr.isDefault)
|
|
if (defaultAddress) {
|
|
setSelectedAddressId(defaultAddress.id)
|
|
} else if (userAddresses.length > 0) {
|
|
setSelectedAddressId(userAddresses[0].id)
|
|
} else {
|
|
setUseNewAddress(true)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching addresses:', error)
|
|
setUseNewAddress(true)
|
|
}
|
|
}, [status])
|
|
|
|
const fetchUserProfile = useCallback(async () => {
|
|
if (status !== 'authenticated') return
|
|
|
|
try {
|
|
const response = await fetch('/api/user/profile')
|
|
if (response.ok) {
|
|
const userData = await response.json()
|
|
|
|
// Prefill form with user data
|
|
setFormData(prev => ({
|
|
...prev,
|
|
email: userData.email || session?.user?.email || '',
|
|
firstName: userData.name?.split(' ')[0] || session?.user?.name?.split(' ')[0] || '',
|
|
lastName: userData.name?.split(' ').slice(1).join(' ') || session?.user?.name?.split(' ').slice(1).join(' ') || '',
|
|
phone: userData.phone || ''
|
|
}))
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching user profile:', error)
|
|
// Fallback to session data
|
|
if (session?.user) {
|
|
setFormData(prev => ({
|
|
...prev,
|
|
email: session.user.email || '',
|
|
firstName: session.user.name?.split(' ')[0] || '',
|
|
lastName: session.user.name?.split(' ').slice(1).join(' ') || ''
|
|
}))
|
|
}
|
|
}
|
|
}, [status, session?.user])
|
|
|
|
useEffect(() => {
|
|
loadCart()
|
|
}, [loadCart])
|
|
|
|
useEffect(() => {
|
|
if (status === 'authenticated') {
|
|
fetchAddresses()
|
|
fetchUserProfile()
|
|
} else if (status === 'unauthenticated') {
|
|
// For guest checkout, keep form empty
|
|
setUseNewAddress(true)
|
|
}
|
|
}, [status, fetchAddresses, fetchUserProfile])
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { name, value } = e.target
|
|
setFormData(prev => ({ ...prev, [name]: value }))
|
|
}
|
|
|
|
const getSubtotal = () => {
|
|
return cart.reduce((sum, item) => sum + (item.price * item.quantity), 0)
|
|
}
|
|
|
|
const getTotal = () => {
|
|
return getSubtotal() // Add tax, shipping, etc. here if needed
|
|
}
|
|
|
|
const getSelectedAddress = () => {
|
|
return addresses.find(addr => addr.id === selectedAddressId)
|
|
}
|
|
|
|
const handlePlaceOrder = async () => {
|
|
// Get shipping address data
|
|
let shippingAddress
|
|
let savedAddressId = null
|
|
|
|
if (useNewAddress) {
|
|
// Validate new address form
|
|
const requiredFields = ['email', 'firstName', 'lastName', 'address', 'city', 'state', 'zipCode', 'phone']
|
|
const missingFields = requiredFields.filter(field => !formData[field as keyof typeof formData])
|
|
|
|
if (missingFields.length > 0) {
|
|
toast.error('Please fill in all required fields')
|
|
return
|
|
}
|
|
|
|
shippingAddress = formData
|
|
|
|
// Save address if user is authenticated and opted to save
|
|
if (status === 'authenticated' && saveAddress) {
|
|
try {
|
|
const addressData = {
|
|
firstName: formData.firstName,
|
|
lastName: formData.lastName,
|
|
address1: formData.address,
|
|
city: formData.city,
|
|
state: formData.state,
|
|
zipCode: formData.zipCode,
|
|
phone: formData.phone,
|
|
isDefault: addresses.length === 0, // Set as default if it's the first address
|
|
type: 'HOME'
|
|
}
|
|
|
|
const response = await fetch('/api/user/addresses', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(addressData)
|
|
})
|
|
|
|
if (response.ok) {
|
|
const newAddress = await response.json()
|
|
savedAddressId = newAddress.address.id
|
|
toast.success('Address saved for future use!')
|
|
}
|
|
} catch (error) {
|
|
console.error('Error saving address:', error)
|
|
// Don't fail the order if address saving fails
|
|
}
|
|
}
|
|
} else {
|
|
const selectedAddr = getSelectedAddress()
|
|
if (!selectedAddr) {
|
|
toast.error('Please select a shipping address')
|
|
return
|
|
}
|
|
|
|
// Check if email is provided
|
|
if (!formData.email) {
|
|
toast.error('Please provide your email address')
|
|
return
|
|
}
|
|
|
|
shippingAddress = {
|
|
email: formData.email,
|
|
firstName: selectedAddr.firstName,
|
|
lastName: selectedAddr.lastName,
|
|
address: `${selectedAddr.address1}${selectedAddr.address2 ? ', ' + selectedAddr.address2 : ''}`,
|
|
city: selectedAddr.city,
|
|
state: selectedAddr.state,
|
|
zipCode: selectedAddr.zipCode,
|
|
phone: selectedAddr.phone || formData.phone || ''
|
|
}
|
|
savedAddressId = selectedAddressId
|
|
}
|
|
|
|
setLoading(true)
|
|
try {
|
|
const orderData = {
|
|
items: cart.map(item => ({
|
|
productId: item.id,
|
|
quantity: item.quantity,
|
|
price: item.price
|
|
})),
|
|
total: getTotal(),
|
|
shippingAddress,
|
|
shippingAddressId: savedAddressId
|
|
}
|
|
|
|
const response = await fetch('/api/orders', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
},
|
|
body: JSON.stringify(orderData)
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const errorData = await response.json()
|
|
if (response.status === 401) {
|
|
toast.error('Please sign in to place an order')
|
|
router.push('/auth/signin?callbackUrl=/checkout')
|
|
return
|
|
}
|
|
throw new Error(errorData.error || 'Failed to create order')
|
|
}
|
|
|
|
const order = await response.json()
|
|
|
|
// Initialize Razorpay payment
|
|
await initializePayment(order)
|
|
|
|
} catch (error) {
|
|
console.error('Error placing order:', error)
|
|
toast.error('Failed to place order. Please try again.')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const initializePayment = async (order: any) => {
|
|
setProcessingPayment(true)
|
|
|
|
// Add debugging logs
|
|
console.log('Order data received:', order)
|
|
console.log('Razorpay Key ID:', order.razorpayKeyId)
|
|
console.log('Razorpay Order ID:', order.razorpayOrderId)
|
|
console.log('Total amount:', order.total)
|
|
console.log('Environment:', order.isProduction ? 'Production' : 'Test')
|
|
|
|
// Check if Razorpay is loaded
|
|
if (!window.Razorpay) {
|
|
toast.error('Payment gateway not loaded. Please refresh the page.')
|
|
setProcessingPayment(false)
|
|
return
|
|
}
|
|
|
|
// Validate required data
|
|
if (!order.razorpayKeyId) {
|
|
toast.error('Payment configuration error. Please try again.')
|
|
setProcessingPayment(false)
|
|
return
|
|
}
|
|
|
|
// Show environment warning for test mode
|
|
if (!order.isProduction) {
|
|
toast.info('Running in test mode - No actual payment will be charged')
|
|
}
|
|
|
|
const options = {
|
|
key: order.razorpayKeyId,
|
|
amount: Math.round(order.total * 100), // Convert to paise
|
|
currency: 'INR',
|
|
name: 'Padmaaja Rasooi',
|
|
description: `Order Payment ${order.isProduction ? '' : '(Test Mode)'}`,
|
|
order_id: order.razorpayOrderId,
|
|
handler: async function (response: any) {
|
|
try {
|
|
console.log('Payment response:', response)
|
|
|
|
const verifyResponse = await fetch('/api/orders/verify-payment', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
razorpay_order_id: response.razorpay_order_id,
|
|
razorpay_payment_id: response.razorpay_payment_id,
|
|
razorpay_signature: response.razorpay_signature,
|
|
orderId: order.id
|
|
})
|
|
})
|
|
|
|
if (verifyResponse.ok) {
|
|
cartManager.clearCart()
|
|
toast.success('Payment successful! Order confirmed.')
|
|
router.push(`/order-confirmation?orderId=${order.id}`)
|
|
} else {
|
|
const errorData = await verifyResponse.json()
|
|
console.error('Verification failed:', errorData)
|
|
toast.error('Payment verification failed')
|
|
}
|
|
} catch (error) {
|
|
console.error('Payment verification error:', error)
|
|
toast.error('Payment verification failed')
|
|
} finally {
|
|
setProcessingPayment(false)
|
|
}
|
|
},
|
|
prefill: {
|
|
name: formData.firstName + ' ' + formData.lastName,
|
|
email: formData.email,
|
|
contact: formData.phone
|
|
},
|
|
modal: {
|
|
ondismiss: function() {
|
|
setProcessingPayment(false)
|
|
toast.error('Payment cancelled')
|
|
}
|
|
},
|
|
theme: {
|
|
color: order.isProduction ? '#10B981' : '#F59E0B' // Green for production, amber for test
|
|
}
|
|
}
|
|
|
|
console.log('Razorpay options:', options)
|
|
|
|
try {
|
|
const rzp = new window.Razorpay(options)
|
|
rzp.open()
|
|
} catch (error) {
|
|
console.error('Error opening Razorpay:', error)
|
|
toast.error('Failed to open payment gateway')
|
|
setProcessingPayment(false)
|
|
}
|
|
}
|
|
|
|
// Load Razorpay script
|
|
useEffect(() => {
|
|
const script = document.createElement('script')
|
|
script.src = 'https://checkout.razorpay.com/v1/checkout.js'
|
|
script.async = true
|
|
script.onload = () => {
|
|
console.log('Razorpay script loaded successfully')
|
|
}
|
|
script.onerror = () => {
|
|
console.error('Failed to load Razorpay script')
|
|
toast.error('Failed to load payment gateway')
|
|
}
|
|
document.body.appendChild(script)
|
|
|
|
return () => {
|
|
if (document.body.contains(script)) {
|
|
document.body.removeChild(script)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
if (cart.length === 0) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
|
<div className="text-center">
|
|
<h1 className="text-2xl font-bold text-gray-900 mb-4">Your cart is empty</h1>
|
|
<Button asChild>
|
|
<Link href="/products">Continue Shopping</Link>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50">
|
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<div className="mb-8">
|
|
<Button variant="ghost" asChild className="mb-4">
|
|
<Link href="/products">
|
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
Back to Products
|
|
</Link>
|
|
</Button>
|
|
<h1 className="text-3xl font-bold text-gray-900">Checkout</h1>
|
|
{status === 'unauthenticated' && (
|
|
<p className="text-gray-600 mt-2">
|
|
<Link href="/auth/signin?callbackUrl=/checkout" className="text-blue-600 hover:underline">
|
|
Sign in
|
|
</Link> for a faster checkout experience
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
|
{/* Checkout Form */}
|
|
<div className="space-y-6">
|
|
{/* Contact Information */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Contact Information</CardTitle>
|
|
{status === 'authenticated' && (
|
|
<CardDescription>
|
|
Welcome back, {session?.user?.name || session?.user?.email}!
|
|
</CardDescription>
|
|
)}
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div>
|
|
<Label htmlFor="email">Email Address</Label>
|
|
<Input
|
|
id="email"
|
|
name="email"
|
|
type="email"
|
|
value={formData.email}
|
|
onChange={handleInputChange}
|
|
placeholder="your@email.com"
|
|
disabled={status === 'authenticated' && !!session?.user?.email}
|
|
required
|
|
/>
|
|
{status === 'authenticated' && session?.user?.email && (
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
Using your account email
|
|
</p>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Shipping Address */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center">
|
|
<Truck className="h-5 w-5 mr-2" />
|
|
Shipping Address
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{status === 'authenticated' && addresses.length > 0 && (
|
|
<div className="space-y-4">
|
|
<div className="flex items-center justify-between">
|
|
<Label>Choose saved address</Label>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => setUseNewAddress(!useNewAddress)}
|
|
>
|
|
{useNewAddress ? 'Use Saved Address' : 'Use New Address'}
|
|
</Button>
|
|
</div>
|
|
|
|
{!useNewAddress && (
|
|
<div className="space-y-3">
|
|
{addresses.map((address) => (
|
|
<div
|
|
key={address.id}
|
|
className={`border rounded-lg p-4 cursor-pointer transition-colors ${
|
|
selectedAddressId === address.id
|
|
? 'border-blue-500 bg-blue-50'
|
|
: 'border-gray-200 hover:border-gray-300'
|
|
}`}
|
|
onClick={() => setSelectedAddressId(address.id)}
|
|
>
|
|
<div className="flex items-start justify-between">
|
|
<div>
|
|
<div className="flex items-center space-x-2 mb-1">
|
|
<span className="font-medium">
|
|
{address.firstName} {address.lastName}
|
|
</span>
|
|
{address.isDefault && (
|
|
<Badge variant="secondary" className="text-xs">Default</Badge>
|
|
)}
|
|
<Badge variant="outline" className="text-xs">
|
|
{address.type}
|
|
</Badge>
|
|
</div>
|
|
<div className="text-sm text-gray-600">
|
|
{address.company && <div>{address.company}</div>}
|
|
<div>{address.address1}</div>
|
|
{address.address2 && <div>{address.address2}</div>}
|
|
<div>{address.city}, {address.state} {address.zipCode}</div>
|
|
{address.phone && <div>{address.phone}</div>}
|
|
</div>
|
|
</div>
|
|
<input
|
|
type="radio"
|
|
checked={selectedAddressId === address.id}
|
|
onChange={() => setSelectedAddressId(address.id)}
|
|
className="mt-1"
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<Button
|
|
variant="outline"
|
|
className="w-full"
|
|
asChild
|
|
>
|
|
<Link href="/profile/addresses">
|
|
<MapPin className="h-4 w-4 mr-2" />
|
|
Manage Addresses
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{(useNewAddress || addresses.length === 0 || status === 'unauthenticated') && (
|
|
<div className="space-y-4">
|
|
{status === 'unauthenticated' && (
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-3 mb-4">
|
|
<p className="text-sm text-blue-800">
|
|
💡 <Link href="/auth/signin?callbackUrl=/checkout" className="font-medium underline">Sign in</Link> to use saved addresses and speed up checkout
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="firstName">First Name</Label>
|
|
<Input
|
|
id="firstName"
|
|
name="firstName"
|
|
value={formData.firstName}
|
|
onChange={handleInputChange}
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="lastName">Last Name</Label>
|
|
<Input
|
|
id="lastName"
|
|
name="lastName"
|
|
value={formData.lastName}
|
|
onChange={handleInputChange}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="phone">Phone</Label>
|
|
<Input
|
|
id="phone"
|
|
name="phone"
|
|
value={formData.phone}
|
|
onChange={handleInputChange}
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="address">Address</Label>
|
|
<Input
|
|
id="address"
|
|
name="address"
|
|
value={formData.address}
|
|
onChange={handleInputChange}
|
|
placeholder="Street address"
|
|
required
|
|
/>
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-4">
|
|
<div>
|
|
<Label htmlFor="city">City</Label>
|
|
<Input
|
|
id="city"
|
|
name="city"
|
|
value={formData.city}
|
|
onChange={handleInputChange}
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="state">State</Label>
|
|
<Input
|
|
id="state"
|
|
name="state"
|
|
value={formData.state}
|
|
onChange={handleInputChange}
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="zipCode">ZIP Code</Label>
|
|
<Input
|
|
id="zipCode"
|
|
name="zipCode"
|
|
value={formData.zipCode}
|
|
onChange={handleInputChange}
|
|
required
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Save Address Option for Authenticated Users */}
|
|
{status === 'authenticated' && (
|
|
<div className="flex items-center space-x-2 pt-4 border-t border-gray-200">
|
|
<input
|
|
type="checkbox"
|
|
id="saveAddress"
|
|
checked={saveAddress}
|
|
onChange={(e) => setSaveAddress(e.target.checked)}
|
|
className="rounded"
|
|
/>
|
|
<Label htmlFor="saveAddress" className="text-sm">
|
|
Save this address for future orders
|
|
</Label>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Payment Method */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center">
|
|
<CreditCard className="h-5 w-5 mr-2" />
|
|
Payment Method
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-3">
|
|
<p className="text-gray-600">Payment will be processed securely via Razorpay</p>
|
|
|
|
{process.env.NODE_ENV === 'development' && (
|
|
<div className="bg-amber-50 border border-amber-200 rounded-lg p-3">
|
|
<p className="text-sm text-amber-800">
|
|
🧪 <strong>Development Mode:</strong> Using Razorpay test environment - No real payments will be processed
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Order Summary */}
|
|
<div>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Order Summary</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{cart.map((item) => (
|
|
<div key={item.id} className="flex items-center space-x-4">
|
|
<Image
|
|
src={item.image || 'https://images.pexels.com/photos/3683107/pexels-photo-3683107.jpeg'}
|
|
alt={item.name}
|
|
width={60}
|
|
height={60}
|
|
className="rounded-lg object-cover"
|
|
/>
|
|
<div className="flex-1">
|
|
<h4 className="font-medium">{item.name}</h4>
|
|
<p className="text-sm text-gray-500">Quantity: {item.quantity}</p>
|
|
</div>
|
|
<div className="text-right">
|
|
<p className="font-medium">
|
|
₹{(item.price * item.quantity).toFixed(2)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<Separator />
|
|
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between">
|
|
<span>Subtotal</span>
|
|
<span>₹{getSubtotal().toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span>Shipping</span>
|
|
<span className="text-green-600">Free</span>
|
|
</div>
|
|
<Separator />
|
|
<div className="flex justify-between font-bold text-lg">
|
|
<span>Total</span>
|
|
<span>₹{getTotal().toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<Button
|
|
className="w-full"
|
|
size="lg"
|
|
onClick={handlePlaceOrder}
|
|
disabled={loading || processingPayment}
|
|
>
|
|
{loading ? 'Creating Order...' : processingPayment ? 'Processing Payment...' : 'Place Order & Pay'}
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|