fix: thank you page 401 error
- Add public /checkout/order/{id} endpoint with order_key validation
- Update checkout redirect to include order_key parameter
- Update ThankYou page to use new public endpoint with key
- Support both guest (via key) and logged-in (via customer_id) access
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { useParams, Link, useSearchParams } from 'react-router-dom';
|
||||
import { useThankYouSettings } from '@/hooks/useAppearanceSettings';
|
||||
import Container from '@/components/Layout/Container';
|
||||
import { CheckCircle, ShoppingBag, Package, Truck } from 'lucide-react';
|
||||
@@ -9,17 +9,22 @@ import { apiClient } from '@/lib/api/client';
|
||||
|
||||
export default function ThankYou() {
|
||||
const { orderId } = useParams<{ orderId: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const orderKey = searchParams.get('key');
|
||||
const { template, headerVisibility, footerVisibility, backgroundColor, customMessage, elements, isLoading: settingsLoading } = useThankYouSettings();
|
||||
const [order, setOrder] = useState<any>(null);
|
||||
const [relatedProducts, setRelatedProducts] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchOrderData = async () => {
|
||||
if (!orderId) return;
|
||||
|
||||
|
||||
try {
|
||||
const orderData = await apiClient.get(`/orders/${orderId}`) as any;
|
||||
// Use public order endpoint with key validation
|
||||
const keyParam = orderKey ? `?key=${orderKey}` : '';
|
||||
const orderData = await apiClient.get(`/checkout/order/${orderId}${keyParam}`) as any;
|
||||
setOrder(orderData);
|
||||
|
||||
// Fetch related products from first order item
|
||||
@@ -30,15 +35,16 @@ export default function ThankYou() {
|
||||
setRelatedProducts(productData.related_products.slice(0, 4));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch order data:', error);
|
||||
} catch (err: any) {
|
||||
console.error('Failed to fetch order data:', err);
|
||||
setError(err.message || 'Failed to load order');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchOrderData();
|
||||
}, [orderId]);
|
||||
}, [orderId, orderKey]);
|
||||
|
||||
if (loading || settingsLoading || !order) {
|
||||
return (
|
||||
@@ -68,55 +74,154 @@ export default function ThankYou() {
|
||||
return (
|
||||
<div style={{ backgroundColor }}>
|
||||
<Container>
|
||||
<div className="py-12 max-w-2xl mx-auto">
|
||||
{/* Receipt Container */}
|
||||
<div className="bg-white shadow-lg" style={{ fontFamily: 'monospace' }}>
|
||||
{/* Receipt Header */}
|
||||
<div className="border-b-2 border-dashed border-gray-400 p-8 text-center">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-green-100 rounded-full mb-4">
|
||||
<CheckCircle className="w-10 h-10 text-green-600" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold mb-2">PAYMENT RECEIPT</h1>
|
||||
<p className="text-gray-600">Order #{order.number}</p>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
{new Date().toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Custom Message */}
|
||||
<div className="px-8 py-4 bg-gray-50 border-b border-dashed border-gray-300">
|
||||
<p className="text-sm text-center text-gray-700">{customMessage}</p>
|
||||
</div>
|
||||
|
||||
{/* Order Items */}
|
||||
{elements.order_details && (
|
||||
<div className="p-8">
|
||||
<div className="border-b-2 border-gray-900 pb-2 mb-4">
|
||||
<div className="flex justify-between text-sm font-bold">
|
||||
<span>ITEM</span>
|
||||
<span>AMOUNT</span>
|
||||
</div>
|
||||
<div className="py-12 max-w-2xl mx-auto">
|
||||
{/* Receipt Container */}
|
||||
<div className="bg-white shadow-lg" style={{ fontFamily: 'monospace' }}>
|
||||
{/* Receipt Header */}
|
||||
<div className="border-b-2 border-dashed border-gray-400 p-8 text-center">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-green-100 rounded-full mb-4">
|
||||
<CheckCircle className="w-10 h-10 text-green-600" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold mb-2">PAYMENT RECEIPT</h1>
|
||||
<p className="text-gray-600">Order #{order.number}</p>
|
||||
<p className="text-sm text-gray-500 mt-1">
|
||||
{new Date().toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{order.items.map((item: any) => (
|
||||
<div key={item.id}>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{item.name}</div>
|
||||
<div className="text-sm text-gray-600">Qty: {item.qty}</div>
|
||||
</div>
|
||||
<div className="text-right font-mono">
|
||||
{formatPrice(item.total)}
|
||||
{/* Custom Message */}
|
||||
<div className="px-8 py-4 bg-gray-50 border-b border-dashed border-gray-300">
|
||||
<p className="text-sm text-center text-gray-700">{customMessage}</p>
|
||||
</div>
|
||||
|
||||
{/* Order Items */}
|
||||
{elements.order_details && (
|
||||
<div className="p-8">
|
||||
<div className="border-b-2 border-gray-900 pb-2 mb-4">
|
||||
<div className="flex justify-between text-sm font-bold">
|
||||
<span>ITEM</span>
|
||||
<span>AMOUNT</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
{order.items.map((item: any) => (
|
||||
<div key={item.id}>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{item.name}</div>
|
||||
<div className="text-sm text-gray-600">Qty: {item.qty}</div>
|
||||
</div>
|
||||
<div className="text-right font-mono">
|
||||
{formatPrice(item.total)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Totals */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 mt-6 pt-4 space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>SUBTOTAL:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.subtotal || 0))}</span>
|
||||
</div>
|
||||
{parseFloat(order.shipping_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>SHIPPING:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.shipping_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
{parseFloat(order.tax_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>TAX:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.tax_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between text-lg font-bold border-t-2 border-gray-900 pt-2 mt-2">
|
||||
<span>TOTAL:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.total || 0))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment & Status Info */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 mt-6 pt-4 space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Payment Method:</span>
|
||||
<span className="font-medium uppercase">{order.payment_method || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Status:</span>
|
||||
<span className="font-medium uppercase">{getStatusLabel(order.status)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Customer Info */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 mt-6 pt-4">
|
||||
<div className="text-xs text-gray-600 uppercase mb-2">Bill To:</div>
|
||||
<div className="text-sm">
|
||||
<div className="font-medium">
|
||||
{order.billing?.first_name} {order.billing?.last_name}
|
||||
</div>
|
||||
<div className="text-gray-600">{order.billing?.email}</div>
|
||||
{order.billing?.phone && (
|
||||
<div className="text-gray-600">{order.billing.phone}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Receipt Footer */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 p-8 text-center bg-gray-50">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
{order.status === 'pending'
|
||||
? 'Awaiting payment confirmation'
|
||||
: 'Thank you for your business!'}
|
||||
</p>
|
||||
|
||||
{elements.continue_shopping_button && (
|
||||
<Link to="/shop">
|
||||
<Button size="lg" className="gap-2">
|
||||
<ShoppingBag className="w-5 h-5" />
|
||||
Continue Shopping
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Related Products */}
|
||||
{elements.related_products && relatedProducts.length > 0 && (
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold mb-6">You May Also Like</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{relatedProducts.map((product: any) => (
|
||||
<Link key={product.id} to={`/product/${product.slug}`} className="group no-underline">
|
||||
<div className="bg-white border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
|
||||
<div className="aspect-square bg-gray-100 flex items-center justify-center">
|
||||
{product.image ? (
|
||||
<img src={product.image} alt={product.name} className="w-full !h-full object-cover" />
|
||||
) : (
|
||||
<Package className="w-12 h-12 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<h3 className="font-medium text-sm text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-2">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p className="text-sm font-bold text-gray-900 mt-1">
|
||||
{formatPrice(parseFloat(product.price || 0))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -175,11 +280,11 @@ export default function ThankYou() {
|
||||
{/* Receipt Footer */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 p-8 text-center bg-gray-50">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
{order.status === 'pending'
|
||||
? 'Awaiting payment confirmation'
|
||||
{order.status === 'pending'
|
||||
? 'Awaiting payment confirmation'
|
||||
: 'Thank you for your business!'}
|
||||
</p>
|
||||
|
||||
|
||||
{elements.continue_shopping_button && (
|
||||
<Link to="/shop">
|
||||
<Button size="lg" className="gap-2">
|
||||
@@ -191,105 +296,6 @@ export default function ThankYou() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Related Products */}
|
||||
{elements.related_products && relatedProducts.length > 0 && (
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold mb-6">You May Also Like</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{relatedProducts.map((product: any) => (
|
||||
<Link key={product.id} to={`/product/${product.slug}`} className="group no-underline">
|
||||
<div className="bg-white border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
|
||||
<div className="aspect-square bg-gray-100 flex items-center justify-center">
|
||||
{product.image ? (
|
||||
<img src={product.image} alt={product.name} className="w-full !h-full object-cover" />
|
||||
) : (
|
||||
<Package className="w-12 h-12 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<h3 className="font-medium text-sm text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-2">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p className="text-sm font-bold text-gray-900 mt-1">
|
||||
{formatPrice(parseFloat(product.price || 0))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Totals */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 mt-6 pt-4 space-y-2">
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>SUBTOTAL:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.subtotal || 0))}</span>
|
||||
</div>
|
||||
{parseFloat(order.shipping_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>SHIPPING:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.shipping_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
{parseFloat(order.tax_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-sm">
|
||||
<span>TAX:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.tax_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between text-lg font-bold border-t-2 border-gray-900 pt-2 mt-2">
|
||||
<span>TOTAL:</span>
|
||||
<span className="font-mono">{formatPrice(parseFloat(order.total || 0))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment & Status Info */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 mt-6 pt-4 space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Payment Method:</span>
|
||||
<span className="font-medium uppercase">{order.payment_method || 'N/A'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Status:</span>
|
||||
<span className="font-medium uppercase">{getStatusLabel(order.status)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Customer Info */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 mt-6 pt-4">
|
||||
<div className="text-xs text-gray-600 uppercase mb-2">Bill To:</div>
|
||||
<div className="text-sm">
|
||||
<div className="font-medium">
|
||||
{order.billing?.first_name} {order.billing?.last_name}
|
||||
</div>
|
||||
<div className="text-gray-600">{order.billing?.email}</div>
|
||||
{order.billing?.phone && (
|
||||
<div className="text-gray-600">{order.billing.phone}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Receipt Footer */}
|
||||
<div className="border-t-2 border-dashed border-gray-400 p-8 text-center bg-gray-50">
|
||||
<p className="text-sm text-gray-600 mb-4">
|
||||
{order.status === 'pending'
|
||||
? 'Awaiting payment confirmation'
|
||||
: 'Thank you for your business!'}
|
||||
</p>
|
||||
|
||||
{elements.continue_shopping_button && (
|
||||
<Link to="/shop">
|
||||
<Button size="lg" className="gap-2">
|
||||
<ShoppingBag className="w-5 h-5" />
|
||||
Continue Shopping
|
||||
</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Related Products */}
|
||||
{elements.related_products && relatedProducts.length > 0 && (
|
||||
<div className="mt-12">
|
||||
@@ -328,144 +334,144 @@ export default function ThankYou() {
|
||||
return (
|
||||
<div style={{ backgroundColor }}>
|
||||
<Container>
|
||||
<div className="py-12 max-w-3xl mx-auto">
|
||||
{/* Success Header */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-green-100 rounded-full mb-4">
|
||||
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||
<div className="py-12 max-w-3xl mx-auto">
|
||||
{/* Success Header */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-green-100 rounded-full mb-4">
|
||||
<CheckCircle className="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">Order Confirmed!</h1>
|
||||
<p className="text-gray-600">Order #{order.number}</p>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">Order Confirmed!</h1>
|
||||
<p className="text-gray-600">Order #{order.number}</p>
|
||||
</div>
|
||||
|
||||
{/* Custom Message */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-8">
|
||||
<p className="text-gray-800 text-center">{customMessage}</p>
|
||||
</div>
|
||||
|
||||
{/* Order Details */}
|
||||
{elements.order_details && (
|
||||
<div className="bg-white border rounded-lg p-6 mb-6">
|
||||
<h2 className="text-xl font-bold mb-4">Order Details</h2>
|
||||
|
||||
{/* Order Items */}
|
||||
<div className="space-y-4 mb-6">
|
||||
{order.items.map((item: any) => (
|
||||
<div key={item.id} className="flex items-center gap-4 pb-4 border-b last:border-0">
|
||||
<div className="w-16 h-16 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
{item.image && typeof item.image === 'string' ? (
|
||||
<img src={item.image} alt={item.name} className="w-full !h-full object-cover rounded-lg" />
|
||||
) : (
|
||||
<Package className="w-8 h-8 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-gray-900">{item.name}</h3>
|
||||
<p className="text-sm text-gray-500">Quantity: {item.qty}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-medium text-gray-900">{formatPrice(item.total)}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Order Summary */}
|
||||
<div className="border-t pt-4 space-y-2">
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span>Subtotal</span>
|
||||
<span>{formatPrice(parseFloat(order.subtotal || 0))}</span>
|
||||
</div>
|
||||
{parseFloat(order.shipping_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span>Shipping</span>
|
||||
<span>{formatPrice(parseFloat(order.shipping_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
{parseFloat(order.tax_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span>Tax</span>
|
||||
<span>{formatPrice(parseFloat(order.tax_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between font-bold text-lg text-gray-900 pt-2 border-t">
|
||||
<span>Total</span>
|
||||
<span>{formatPrice(parseFloat(order.total || 0))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Customer Info */}
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<h3 className="font-medium text-gray-900 mb-3">Customer Information</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-gray-500 mb-1">Email</p>
|
||||
<p className="text-gray-900">{order.billing?.email || 'N/A'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500 mb-1">Phone</p>
|
||||
<p className="text-gray-900">{order.billing?.phone || 'N/A'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Order Status */}
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<div className="flex items-center gap-3">
|
||||
<Truck className="w-5 h-5 text-blue-600" />
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">Order Status: {getStatusLabel(order.status)}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{order.status === 'pending' ? 'Awaiting payment confirmation' : "We'll send you shipping updates via email"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Custom Message */}
|
||||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6 mb-8">
|
||||
<p className="text-gray-800 text-center">{customMessage}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Continue Shopping Button */}
|
||||
{elements.continue_shopping_button && (
|
||||
<div className="text-center">
|
||||
<Link to="/shop">
|
||||
<Button size="lg" className="gap-2">
|
||||
<ShoppingBag className="w-5 h-5" />
|
||||
Continue Shopping
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
{/* Order Details */}
|
||||
{elements.order_details && (
|
||||
<div className="bg-white border rounded-lg p-6 mb-6">
|
||||
<h2 className="text-xl font-bold mb-4">Order Details</h2>
|
||||
|
||||
{/* Related Products */}
|
||||
{elements.related_products && relatedProducts.length > 0 && (
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold mb-6">You May Also Like</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{relatedProducts.map((product: any) => (
|
||||
<Link key={product.id} to={`/product/${product.slug}`} className="group no-underline">
|
||||
<div className="bg-white border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
|
||||
<div className="aspect-square bg-gray-100 flex items-center justify-center">
|
||||
{product.image ? (
|
||||
<img src={product.image} alt={product.name} className="w-full !h-full object-cover" />
|
||||
{/* Order Items */}
|
||||
<div className="space-y-4 mb-6">
|
||||
{order.items.map((item: any) => (
|
||||
<div key={item.id} className="flex items-center gap-4 pb-4 border-b last:border-0">
|
||||
<div className="w-16 h-16 bg-gray-100 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
{item.image && typeof item.image === 'string' ? (
|
||||
<img src={item.image} alt={item.name} className="w-full !h-full object-cover rounded-lg" />
|
||||
) : (
|
||||
<Package className="w-12 h-12 text-gray-400" />
|
||||
<Package className="w-8 h-8 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<h3 className="font-medium text-sm text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-2">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p className="text-sm font-bold text-gray-900 mt-1">
|
||||
{formatPrice(parseFloat(product.price || 0))}
|
||||
</p>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-gray-900">{item.name}</h3>
|
||||
<p className="text-sm text-gray-500">Quantity: {item.qty}</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="font-medium text-gray-900">{formatPrice(item.total)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Order Summary */}
|
||||
<div className="border-t pt-4 space-y-2">
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span>Subtotal</span>
|
||||
<span>{formatPrice(parseFloat(order.subtotal || 0))}</span>
|
||||
</div>
|
||||
{parseFloat(order.shipping_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span>Shipping</span>
|
||||
<span>{formatPrice(parseFloat(order.shipping_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
{parseFloat(order.tax_total || 0) > 0 && (
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span>Tax</span>
|
||||
<span>{formatPrice(parseFloat(order.tax_total))}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-between font-bold text-lg text-gray-900 pt-2 border-t">
|
||||
<span>Total</span>
|
||||
<span>{formatPrice(parseFloat(order.total || 0))}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Customer Info */}
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<h3 className="font-medium text-gray-900 mb-3">Customer Information</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<p className="text-gray-500 mb-1">Email</p>
|
||||
<p className="text-gray-900">{order.billing?.email || 'N/A'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-gray-500 mb-1">Phone</p>
|
||||
<p className="text-gray-900">{order.billing?.phone || 'N/A'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Order Status */}
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<div className="flex items-center gap-3">
|
||||
<Truck className="w-5 h-5 text-blue-600" />
|
||||
<div>
|
||||
<p className="font-medium text-gray-900">Order Status: {getStatusLabel(order.status)}</p>
|
||||
<p className="text-sm text-gray-500">
|
||||
{order.status === 'pending' ? 'Awaiting payment confirmation' : "We'll send you shipping updates via email"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Continue Shopping Button */}
|
||||
{elements.continue_shopping_button && (
|
||||
<div className="text-center">
|
||||
<Link to="/shop">
|
||||
<Button size="lg" className="gap-2">
|
||||
<ShoppingBag className="w-5 h-5" />
|
||||
Continue Shopping
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Related Products */}
|
||||
{elements.related_products && relatedProducts.length > 0 && (
|
||||
<div className="mt-12">
|
||||
<h2 className="text-2xl font-bold mb-6">You May Also Like</h2>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{relatedProducts.map((product: any) => (
|
||||
<Link key={product.id} to={`/product/${product.slug}`} className="group no-underline">
|
||||
<div className="bg-white border rounded-lg overflow-hidden hover:shadow-lg transition-shadow">
|
||||
<div className="aspect-square bg-gray-100 flex items-center justify-center">
|
||||
{product.image ? (
|
||||
<img src={product.image} alt={product.name} className="w-full !h-full object-cover" />
|
||||
) : (
|
||||
<Package className="w-12 h-12 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<h3 className="font-medium text-sm text-gray-900 group-hover:text-blue-600 transition-colors line-clamp-2">
|
||||
{product.name}
|
||||
</h3>
|
||||
<p className="text-sm font-bold text-gray-900 mt-1">
|
||||
{formatPrice(parseFloat(product.price || 0))}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user