ajinkya-mhetre-mern
Version:
A MERN starter with frontend and backend folders
754 lines (699 loc) • 34.7 kB
JSX
import React, { useState, useEffect, useCallback, useMemo } from 'react'
import { useAuth } from '../../contexts/AuthContext'
import { Search, Filter, Package, MapPin, Calendar, DollarSign, Phone, Mail, Eye, Edit3, Truck, CheckCircle, XCircle, Clock, AlertCircle, MoreVertical, RefreshCw } from 'lucide-react'
import Stats from '../../components/Framers/Stats'
const baseUrl = import.meta.env.VITE_API_URL;
function FramersMyOrders() {
const { token } = useAuth()
const [orders, setOrders] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState('all')
const [paymentFilter, setPaymentFilter] = useState('all')
const [currentPage, setCurrentPage] = useState(1)
const [selectedOrder, setSelectedOrder] = useState(null)
const [showStatusModal, setShowStatusModal] = useState(false)
const [showDetailsModal, setShowDetailsModal] = useState(false)
const [updatingStatus, setUpdatingStatus] = useState(false)
const [newStatus, setNewStatus] = useState('')
const [statusNote, setStatusNote] = useState('')
const [refreshing, setRefreshing] = useState(false)
const ordersPerPage = 15
const statusOptions = ['placed', 'processing', 'dispatched', 'delivered', 'cancelled']
// Fetch orders from API
const fetchOrders = useCallback(async (showRefreshLoader = false) => {
try {
if (showRefreshLoader) {
setRefreshing(true)
} else {
setLoading(true)
}
setError(null)
const response = await fetch(`${BaseUrl}/farmer/orders`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
if (!response.ok) {
if (response.status === 401) {
throw new Error('Authentication failed. Please login again.')
}
throw new Error(`Failed to fetch orders: ${response.statusText}`)
}
const data = await response.json()
setOrders(data.orders || [])
} catch (err) {
setError(err.message)
console.error('Error fetching orders:', err)
} finally {
setLoading(false)
setRefreshing(false)
}
}, [BaseUrl, token])
// Update order status
const updateOrderStatus = async (orderId, status, note) => {
if (!orderId || !status) {
alert('Invalid order ID or status')
return
}
try {
setUpdatingStatus(true)
const response = await fetch(`${BaseUrl}/farmer/orders/${orderId}/status`, {
method: 'PUT',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ status, note: note.trim() })
})
if (!response.ok) {
if (response.status === 401) {
throw new Error('Authentication failed. Please login again.')
}
const errorData = await response.json().catch(() => ({}))
throw new Error(errorData.message || `Failed to update order status: ${response.statusText}`)
}
// Refresh orders after successful update
await fetchOrders(true)
setShowStatusModal(false)
setNewStatus('')
setStatusNote('')
setSelectedOrder(null)
} catch (err) {
console.error('Error updating order status:', err)
alert('Error updating order status: ' + err.message)
} finally {
setUpdatingStatus(false)
}
}
useEffect(() => {
if (token) {
fetchOrders()
} else {
setError('No authentication token found')
}
}, [token, fetchOrders])
// Filter orders based on search and filters - memoized for performance
const filteredOrders = useMemo(() => {
return orders.filter(order => {
const searchLower = searchTerm.toLowerCase()
const matchesSearch = !searchTerm ||
order.customer?.name?.toLowerCase().includes(searchLower) ||
order.customer?.email?.toLowerCase().includes(searchLower) ||
order._id?.toLowerCase().includes(searchLower) ||
order.trackingNumber?.toLowerCase().includes(searchLower)
const matchesStatus = statusFilter === 'all' || order.status === statusFilter
const matchesPayment = paymentFilter === 'all' || order.paymentStatus === paymentFilter
return matchesSearch && matchesStatus && matchesPayment
})
}, [orders, searchTerm, statusFilter, paymentFilter])
// Pagination - memoized for performance
const paginationData = useMemo(() => {
const totalPages = Math.ceil(filteredOrders.length / ordersPerPage)
const paginatedOrders = filteredOrders.slice(
(currentPage - 1) * ordersPerPage,
currentPage * ordersPerPage
)
return { totalPages, paginatedOrders }
}, [filteredOrders, currentPage, ordersPerPage])
// Reset to first page when filters change
useEffect(() => {
setCurrentPage(1)
}, [searchTerm, statusFilter, paymentFilter])
// Status badge colors
const getStatusColor = (status) => {
const colors = {
placed: 'bg-blue-100 text-blue-800 border-blue-200',
processing: 'bg-yellow-100 text-yellow-800 border-yellow-200',
dispatched: 'bg-purple-100 text-purple-800 border-purple-200',
delivered: 'bg-green-100 text-green-800 border-green-200',
cancelled: 'bg-red-100 text-red-800 border-red-200',
default: 'bg-gray-100 text-gray-800 border-gray-200'
}
return colors[status] || colors.default
}
const getPaymentStatusColor = (status) => {
const colors = {
paid: 'bg-green-100 text-green-800 border-green-200',
pending: 'bg-yellow-100 text-yellow-800 border-yellow-200',
failed: 'bg-red-100 text-red-800 border-red-200',
default: 'bg-gray-100 text-gray-800 border-gray-200'
}
return colors[status] || colors.default
}
// Get status icon
const getStatusIcon = (status) => {
switch (status) {
case 'placed': return <Clock className="w-4 h-4" />
case 'processing': return <AlertCircle className="w-4 h-4" />
case 'dispatched': return <Truck className="w-4 h-4" />
case 'delivered': return <CheckCircle className="w-4 h-4" />
case 'cancelled': return <XCircle className="w-4 h-4" />
default: return <Package className="w-4 h-4" />
}
}
// Handle modal close
const closeModals = () => {
setShowDetailsModal(false)
setShowStatusModal(false)
setSelectedOrder(null)
setNewStatus('')
setStatusNote('')
}
// Handle status modal open
const openStatusModal = (order) => {
setSelectedOrder(order)
setNewStatus(order.status)
setShowStatusModal(true)
}
if (loading) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto"></div>
<p className="mt-4 text-gray-600">Loading orders...</p>
</div>
</div>
)
}
if (error) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center max-w-md">
<XCircle className="w-12 h-12 text-red-500 mx-auto mb-4" />
<h2 className="text-lg font-semibold text-gray-900 mb-2">Error Loading Orders</h2>
<p className="text-red-600 mb-4">{error}</p>
<button
onClick={() => fetchOrders()}
className="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
>
Try Again
</button>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50 p-6">
<div className="max-w-7xl mx-auto">
<Stats />
{/* Header */}
<div className="mb-6 flex flex-col sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">Orders Management</h1>
<p className="text-gray-600">Manage and track all your orders</p>
</div>
<div className="mt-4 sm:mt-0 flex items-center gap-3">
<div className="text-sm text-gray-600">
Total: {filteredOrders.length} orders
</div>
<button
onClick={() => fetchOrders(true)}
disabled={refreshing}
className="flex items-center gap-2 px-4 py-2 text-green-600 hover:text-green-700 hover:bg-green-50 rounded-lg transition-colors disabled:opacity-50"
>
<RefreshCw className={`w-4 h-4 ${refreshing ? 'animate-spin' : ''}`} />
Refresh
</button>
</div>
</div>
{/* Filters */}
<div className="bg-white rounded-lg shadow-sm p-4 mb-6">
<div className="flex flex-col sm:flex-row gap-4">
{/* Search */}
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4" />
<input
type="text"
placeholder="Search orders, customers, tracking..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all"
/>
</div>
{/* Status Filter */}
<select
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value)}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all"
>
<option value="all">All Status</option>
{statusOptions.map(status => (
<option key={status} value={status}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</option>
))}
</select>
{/* Payment Filter */}
<select
value={paymentFilter}
onChange={(e) => setPaymentFilter(e.target.value)}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all"
>
<option value="all">All Payments</option>
<option value="paid">Paid</option>
<option value="pending">Pending</option>
<option value="failed">Failed</option>
</select>
</div>
</div>
{/* Table */}
<div className="bg-white rounded-lg shadow-sm overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="bg-gray-50 border-b border-gray-200">
<tr>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Order Info
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Customer
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Items
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Amount
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Status
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Payment
</th>
<th className="px-6 py-4 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date
</th>
<th className="px-6 py-4 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
Actions
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{paginationData.paginatedOrders.length === 0 ? (
<tr>
<td colSpan="8" className="px-6 py-12 text-center">
<Package className="w-12 h-12 text-gray-300 mx-auto mb-4" />
<p className="text-gray-500 font-medium">No orders found</p>
{searchTerm || statusFilter !== 'all' || paymentFilter !== 'all' ? (
<p className="text-sm text-gray-400 mt-2">
Try adjusting your search or filters
</p>
) : null}
</td>
</tr>
) : (
paginationData.paginatedOrders.map((order) => (
<tr key={order._id} className="hover:bg-gray-50 transition-colors">
{/* Order Info */}
<td className="px-6 py-4 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
#{order._id?.slice(-8) || 'N/A'}
</div>
{order.trackingNumber && (
<div className="text-xs text-gray-500 mt-1">
Track: {order.trackingNumber}
</div>
)}
</div>
</td>
{/* Customer */}
<td className="px-6 py-4 whitespace-nowrap">
<div>
<div className="text-sm font-medium text-gray-900">
{order.customer?.name || 'N/A'}
</div>
<div className="text-sm text-gray-500">
{order.customer?.email || 'N/A'}
</div>
{order.customer?.phone && (
<div className="text-xs text-gray-500 mt-1">
{order.customer.phone}
</div>
)}
</div>
</td>
{/* Items */}
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">
{order.items?.length || 0} item{(order.items?.length || 0) !== 1 ? 's' : ''}
</div>
<div className="text-xs text-gray-500 mt-1">
{order.items?.slice(0, 2).map((item, idx) => (
<div key={idx}>
{item.product?.name || 'Unknown'} (×{item.quantity || 0})
</div>
))}
{(order.items?.length || 0) > 2 && (
<div>+{order.items.length - 2} more</div>
)}
</div>
</td>
{/* Amount */}
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm font-medium text-gray-900">
₹{(order.totalAmount || 0).toLocaleString('en-IN')}
</div>
</td>
{/* Status */}
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center gap-2">
<span className={`inline-flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-full border ${getStatusColor(order.status)}`}>
{getStatusIcon(order.status)}
{order.status?.charAt(0).toUpperCase() + order.status?.slice(1) || 'Unknown'}
</span>
</div>
</td>
{/* Payment */}
<td className="px-6 py-4 whitespace-nowrap">
<span className={`inline-flex px-2 py-1 text-xs font-medium rounded-full border ${getPaymentStatusColor(order.paymentStatus)}`}>
{order.paymentStatus?.charAt(0).toUpperCase() + order.paymentStatus?.slice(1) || 'Unknown'}
</span>
</td>
{/* Date */}
<td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">
{order.createdAt ? new Date(order.createdAt).toLocaleDateString('en-IN') : 'N/A'}
</div>
<div className="text-xs text-gray-500 mt-1">
{order.createdAt ? new Date(order.createdAt).toLocaleTimeString('en-IN', {
hour: '2-digit',
minute: '2-digit'
}) : ''}
</div>
</td>
{/* Actions */}
<td className="px-6 py-4 whitespace-nowrap text-right">
<div className="flex items-center justify-end gap-2">
<button
onClick={() => {
setSelectedOrder(order)
setShowDetailsModal(true)
}}
className="p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
title="View Details"
>
<Eye className="w-4 h-4" />
</button>
{/* {order.status !== 'delivered' && order.status !== 'cancelled' && (
<button
onClick={() => openStatusModal(order)}
className="p-2 text-green-600 hover:text-green-700 hover:bg-green-50 rounded-lg transition-colors"
title="Update Status"
>
<Edit3 className="w-4 h-4" />
</button>
)} */}
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
{/* Pagination */}
{paginationData.totalPages > 1 && (
<div className="bg-white px-4 py-3 border-t border-gray-200 sm:px-6">
<div className="flex items-center justify-between">
<div className="flex-1 flex justify-between sm:hidden">
<button
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage === 1}
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Previous
</button>
<button
onClick={() => setCurrentPage(prev => Math.min(prev + 1, paginationData.totalPages))}
disabled={currentPage === paginationData.totalPages}
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Next
</button>
</div>
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p className="text-sm text-gray-700">
Showing <span className="font-medium">{(currentPage - 1) * ordersPerPage + 1}</span> to{' '}
<span className="font-medium">
{Math.min(currentPage * ordersPerPage, filteredOrders.length)}
</span>{' '}
of <span className="font-medium">{filteredOrders.length}</span> results
</p>
</div>
<div>
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px">
<button
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
disabled={currentPage === 1}
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Previous
</button>
{Array.from({ length: Math.min(5, paginationData.totalPages) }, (_, i) => {
const page = currentPage <= 3 ? i + 1 : currentPage - 2 + i
if (page > paginationData.totalPages) return null
return (
<button
key={page}
onClick={() => setCurrentPage(page)}
className={`relative inline-flex items-center px-4 py-2 border text-sm font-medium transition-colors ${
currentPage === page
? 'z-10 bg-green-50 border-green-500 text-green-600'
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
}`}
>
{page}
</button>
)
})}
<button
onClick={() => setCurrentPage(prev => Math.min(prev + 1, paginationData.totalPages))}
disabled={currentPage === paginationData.totalPages}
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
>
Next
</button>
</nav>
</div>
</div>
</div>
</div>
)}
</div>
</div>
{/* Order Details Modal */}
{showDetailsModal && selectedOrder && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg max-w-4xl w-full max-h-[90vh] overflow-y-auto">
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900">
Order Details - #{selectedOrder._id?.slice(-8) || 'N/A'}
</h3>
<button
onClick={closeModals}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<XCircle className="w-6 h-6" />
</button>
</div>
</div>
<div className="p-6">
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Customer & Shipping */}
<div>
<h4 className="font-medium text-gray-900 mb-4">Customer Information</h4>
<div className="bg-gray-50 rounded-lg p-4 mb-4">
<p className="font-medium">{selectedOrder.customer?.name || 'N/A'}</p>
<p className="text-sm text-gray-600">{selectedOrder.customer?.email || 'N/A'}</p>
{selectedOrder.customer?.phone && (
<p className="text-sm text-gray-600">{selectedOrder.customer.phone}</p>
)}
</div>
<h4 className="font-medium text-gray-900 mb-4">Shipping Address</h4>
<div className="bg-gray-50 rounded-lg p-4">
<p className="text-sm text-gray-600">
{selectedOrder.shippingAddress?.street || 'N/A'}<br />
{selectedOrder.shippingAddress?.city || 'N/A'}, {selectedOrder.shippingAddress?.state || 'N/A'}<br />
{selectedOrder.shippingAddress?.zipCode || 'N/A'}
</p>
</div>
</div>
{/* Order Items */}
<div>
<h4 className="font-medium text-gray-900 mb-4">Order Items</h4>
<div className="space-y-3">
{selectedOrder.items?.map((item, index) => (
<div key={index} className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
<img
src={item.product?.images?.[0]?.url || '/placeholder.jpg'}
alt={item.product?.name || 'Product'}
className="w-12 h-12 object-cover rounded-lg"
onError={(e) => {
e.target.src = '/placeholder.jpg'
}}
/>
<div className="flex-1">
<p className="font-medium text-gray-900">{item.product?.name || 'Unknown Product'}</p>
<p className="text-sm text-gray-600">
Qty: {item.quantity || 0} × ₹{item.price || 0} = ₹{((item.quantity || 0) * (item.price || 0)).toLocaleString('en-IN')}
</p>
</div>
</div>
)) || <p className="text-gray-500">No items found</p>}
</div>
<div className="mt-4 pt-4 border-t border-gray-200">
<div className="flex justify-between items-center">
<span className="font-medium text-gray-900">Total Amount:</span>
<span className="font-bold text-lg text-gray-900">
₹{(selectedOrder.totalAmount || 0).toLocaleString('en-IN')}
</span>
</div>
</div>
</div>
</div>
{/* Status History */}
<div className="mt-6">
<h4 className="font-medium text-gray-900 mb-4">Status History</h4>
<div className="space-y-3">
{selectedOrder.statusHistory?.length > 0 ? (
selectedOrder.statusHistory.map((status, index) => (
<div key={index} className="flex items-start gap-3 p-3 bg-gray-50 rounded-lg">
<div className="flex-shrink-0 mt-1">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="font-medium text-gray-900 capitalize">{status.status}</span>
<span className="text-xs text-gray-500">
{status.timestamp ? new Date(status.timestamp).toLocaleString('en-IN') : 'N/A'}
</span>
</div>
{status.note && <p className="text-sm text-gray-600">{status.note}</p>}
</div>
</div>
))
) : (
<p className="text-gray-500">No status history available</p>
)}
</div>
</div>
{/* Action Buttons */}
<div className="mt-6 flex gap-3">
{selectedOrder.status !== 'delivered' && selectedOrder. status !== 'cancelled' && (
<button
onClick={() => {
setShowDetailsModal(false)
openStatusModal(selectedOrder)
}}
className="px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center gap-2"
>
<Edit3 className="w-4 h-4" />
Update Status
</button>
)}
<button
onClick={closeModals}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
>
Close
</button>
</div>
</div>
</div>
</div>
)}
{/* Status Update Modal */}
{showStatusModal && selectedOrder && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg max-w-md w-full">
<div className="p-6 border-b border-gray-200">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-gray-900">
Update Order Status
</h3>
<button
onClick={closeModals}
className="text-gray-400 hover:text-gray-600 transition-colors"
>
<XCircle className="w-6 h-6" />
</button>
</div>
</div>
<div className="p-6">
<div className="mb-4">
<p className="text-sm text-gray-600 mb-2">
Order: #{selectedOrder._id?.slice(-8) || 'N/A'}
</p>
<p className="text-sm text-gray-600">
Current Status: <span className="font-medium capitalize">{selectedOrder.status}</span>
</p>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
New Status
</label>
<select
value={newStatus}
onChange={(e) => setNewStatus(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all"
>
{statusOptions.map(status => (
<option key={status} value={status}>
{status.charAt(0).toUpperCase() + status.slice(1)}
</option>
))}
</select>
</div>
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
Note (Optional)
</label>
<textarea
value={statusNote}
onChange={(e) => setStatusNote(e.target.value)}
placeholder="Add a note about this status change..."
rows={3}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all resize-none"
/>
</div>
<div className="flex gap-3">
<button
onClick={() => updateOrderStatus(selectedOrder._id, newStatus, statusNote)}
disabled={updatingStatus || !newStatus}
className="flex-1 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
{updatingStatus ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
Updating...
</>
) : (
<>
<CheckCircle className="w-4 h-4" />
Update Status
</>
)}
</button>
<button
onClick={closeModals}
disabled={updatingStatus}
className="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors disabled:opacity-50"
>
Cancel
</button>
</div>
</div>
</div>
</div>
)}
</div>
)
}
export default FramersMyOrders