order detail
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
'use client'
|
||||
import { FC } from 'react'
|
||||
|
||||
type Props = {
|
||||
orderId: number
|
||||
}
|
||||
|
||||
const TrackCode: FC<Props> = ({ orderId }) => {
|
||||
return (
|
||||
<div className="mt-4 p-3 bg-gray-50 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-[#7F7F7F] text-sm">کد پیگیری:</div>
|
||||
<div className="font-mono text-sm bg-white px-2 py-1 rounded border">
|
||||
{orderId.toString().padStart(8, '0')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TrackCode
|
||||
@@ -0,0 +1,417 @@
|
||||
'use client'
|
||||
import { FC, useState } from 'react'
|
||||
import { ArrowRight2, ShieldTick, Truck, MessageText, CloseCircle } from 'iconsax-react'
|
||||
import { useRouter, useParams } from 'next/navigation'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { yupResolver } from '@hookform/resolvers/yup'
|
||||
import * as yup from 'yup'
|
||||
import PageLoading from '@/components/PageLoading'
|
||||
import TrackCode from './components/TrackCode'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import Modal from '@/components/Modal'
|
||||
import Select from '@/components/Select'
|
||||
import Input from '@/components/Input'
|
||||
import withLayout from '@/hoc/withLayout'
|
||||
import Image from 'next/image'
|
||||
|
||||
// Mock data
|
||||
const mockOrderDetail = {
|
||||
payment: {
|
||||
transaction_id: 'TRX123456789'
|
||||
},
|
||||
order: {
|
||||
_id: 'ORD001',
|
||||
user: {
|
||||
fullName: 'احمد محمدی'
|
||||
},
|
||||
shipmentAddress: {
|
||||
postalCode: '1234567890',
|
||||
phone: '09123456789',
|
||||
address: 'تهران، خیابان ولیعصر، پلاک 123'
|
||||
},
|
||||
createdAt: '1402/12/15',
|
||||
orderItems: {
|
||||
_id: 'ITEM001',
|
||||
itemsCount: 3,
|
||||
status: 'processing',
|
||||
totalShipmentCost: '25000 تومان',
|
||||
totalPaymentPrice: '450000',
|
||||
shipper: {
|
||||
name: 'پست ایران',
|
||||
deliveryTime: 3
|
||||
},
|
||||
shipmentItems: [
|
||||
{
|
||||
_id: 'SHIP001',
|
||||
product: {
|
||||
_id: 'PROD001',
|
||||
title_fa: 'لپتاپ اپل مکبوک پرو 13 اینچی',
|
||||
imagesUrl: {
|
||||
cover: 'https://via.placeholder.com/80x80'
|
||||
}
|
||||
},
|
||||
quantity: 1,
|
||||
cancelled_quantity: 0,
|
||||
returned_quantity: 0,
|
||||
selling_price: 450000,
|
||||
variant: {
|
||||
color: {
|
||||
name: 'نقرهای'
|
||||
},
|
||||
size: {
|
||||
name: '13 اینچ'
|
||||
},
|
||||
meterages: {
|
||||
name: '256GB'
|
||||
},
|
||||
warranty: {
|
||||
duration: '2 سال',
|
||||
name: 'گارانتی'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mockCancelReasons = [
|
||||
{ _id: 'REASON1', title: 'تغییر نظر در خرید' },
|
||||
{ _id: 'REASON2', title: 'یافتن قیمت بهتر' },
|
||||
{ _id: 'REASON3', title: 'مشکل در محصول' },
|
||||
{ _id: 'REASON4', title: 'تأخیر در ارسال' }
|
||||
]
|
||||
|
||||
// Form validation schema
|
||||
const cancelOrderSchema = yup.object({
|
||||
reason: yup.string().required('لطفا دلیل لغو سفارش را انتخاب کنید'),
|
||||
description: yup.string().required('لطفا توضیحات لغو سفارش را وارد کنید'),
|
||||
count: yup.string().required('تعداد لغو سفارش را وارد کنید')
|
||||
})
|
||||
|
||||
type CancelOrderFormData = yup.InferType<typeof cancelOrderSchema>
|
||||
|
||||
const OrderDetail: FC = () => {
|
||||
const { id } = useParams()
|
||||
const [showModalCancel, setShowModalCancel] = useState<boolean>(false)
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const router = useRouter()
|
||||
|
||||
// Form with react-hook-form
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
reset
|
||||
} = useForm<CancelOrderFormData>({
|
||||
resolver: yupResolver(cancelOrderSchema)
|
||||
})
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap: Record<string, string> = {
|
||||
'processing': 'در حال پردازش',
|
||||
'shipped': 'ارسال شده',
|
||||
'delivered': 'تحویل داده شده',
|
||||
'cancelled': 'لغو شده'
|
||||
}
|
||||
return statusMap[status] || status
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
setIsLoading(true)
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setIsLoading(false)
|
||||
setShowModalCancel(false)
|
||||
reset() // Reset form
|
||||
alert('درخواست لغو سفارش با موفقیت ثبت شد')
|
||||
router.push('/profile/orders')
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='w-full lg:px-10 px-4 mt-20'>
|
||||
<div className='border p-6 rounded-2xl text-sm'>
|
||||
<div className='flex gap-2 items-center border-b pb-6'>
|
||||
<button
|
||||
onClick={() => router.push('/profile/orders')}
|
||||
className='p-2 hover:bg-gray-100 rounded-lg transition-colors'
|
||||
>
|
||||
<ArrowRight2 size={20} className="rotate-180" />
|
||||
</button>
|
||||
<div className="text-lg font-semibold">جزئیات سفارش</div>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<PageLoading />
|
||||
) : (
|
||||
<div className='mt-6 lg:flex-row flex-col flex gap-6 items-start'>
|
||||
{/* Sidebar */}
|
||||
<div className='lg:w-[280px] w-full p-5 rounded-2xl shadow-lg'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-[#7F7F7F]'>شناسه تراکنش :</div>
|
||||
<div className="font-mono">
|
||||
{mockOrderDetail.payment.transaction_id}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2 mt-4'>
|
||||
<div className='text-[#7F7F7F]'>گیرنده :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.user.fullName}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2 mt-4'>
|
||||
<div className='text-[#7F7F7F]'>کدپستی :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.shipmentAddress.postalCode}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2 mt-4'>
|
||||
<div className='text-[#7F7F7F] whitespace-nowrap'>تاریخ ثبت سفارش :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.createdAt}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2 mt-4'>
|
||||
<div className='text-[#7F7F7F]'>شماره موبایل :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.shipmentAddress.phone}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-start gap-2 mt-4'>
|
||||
<div className='text-[#7F7F7F] whitespace-nowrap'>آدرس :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.shipmentAddress.address}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TrackCode orderId={Number(id) || 0} />
|
||||
|
||||
<Button
|
||||
className='mt-3 w-full'
|
||||
variant="outline"
|
||||
>
|
||||
مشاهده فاکتور
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className='flex-1 w-full bg-white shadow-lg p-6 rounded-2xl'>
|
||||
<div className='flex md:flex-row flex-col gap-4 justify-between md:items-center'>
|
||||
<div className='flex md:flex-row flex-col gap-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-[#7F7F7F]'>تعداد مرسوله :</div>
|
||||
<div>1</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-[#7F7F7F]'>تعداد اقلام :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.orderItems.itemsCount}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='text-primary font-semibold'>
|
||||
{getStatusText(mockOrderDetail.order.orderItems.status)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2 mt-6'>
|
||||
<div className='text-[#7F7F7F]'>حداکثر مهلت ارسال :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.orderItems.shipper.deliveryTime} روز
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex md:flex-row flex-col gap-3 justify-between mt-6'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-[#7F7F7F]'>هزینه ارسال :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.orderItems.totalShipmentCost}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-[#7F7F7F]'>مبلغ مرسوله :</div>
|
||||
<div>
|
||||
{mockOrderDetail.order.orderItems.totalPaymentPrice} تومان
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='text-[#7F7F7F]'>شناسه سفارش :</div>
|
||||
<div className="font-mono">
|
||||
{mockOrderDetail.order._id}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Product Items */}
|
||||
{mockOrderDetail.order.orderItems.shipmentItems.map((item) => (
|
||||
<div key={item.product._id} className='mt-8 py-7 border-t items-start flex gap-6 sm:flex-row flex-col'>
|
||||
<Image
|
||||
src={item.product.imagesUrl.cover}
|
||||
width={80}
|
||||
height={80}
|
||||
className='w-20 h-20 object-cover rounded-lg border'
|
||||
alt={item.product.title_fa}
|
||||
/>
|
||||
|
||||
<div className='flex-1 w-full'>
|
||||
<div className='truncate max-w-[90%] font-medium'>
|
||||
{item.product.title_fa}
|
||||
</div>
|
||||
|
||||
<div className='mt-4 text-[#999999] space-y-3'>
|
||||
<div className='flex gap-2'>
|
||||
<div>تعداد اقلام :</div>
|
||||
<div>{item.quantity}</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div>تعداد کنسلی :</div>
|
||||
<div>{item.cancelled_quantity}</div>
|
||||
</div>
|
||||
<div className='flex gap-2'>
|
||||
<div>تعداد مرجوعی :</div>
|
||||
<div>{item.returned_quantity}</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3 items-center'>
|
||||
<div className='size-3 bg-black rounded-full'></div>
|
||||
<div>
|
||||
{item.variant.color?.name ||
|
||||
item.variant.size?.name ||
|
||||
item.variant.meterages?.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3 items-center'>
|
||||
<MessageText size={20} className="text-gray-400" />
|
||||
<div className="font-mono">
|
||||
SKP_{item.product._id}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3 items-center'>
|
||||
<ShieldTick size={20} className="text-green-500" />
|
||||
<div>
|
||||
گارانتی {item.variant.warranty.duration} {item.variant.warranty.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3 items-center'>
|
||||
<Truck size={20} className="text-blue-500" />
|
||||
<div>
|
||||
{mockOrderDetail.order.orderItems.shipper.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 flex justify-between items-center'>
|
||||
<div>
|
||||
<div className='text-lg font-semibold'>
|
||||
{Intl.NumberFormat('fa-IR').format(item.selling_price)}
|
||||
</div>
|
||||
<div className='text-xs mt-1 text-gray-500'>تومان</div>
|
||||
</div>
|
||||
|
||||
{item.quantity !== item.cancelled_quantity && (
|
||||
<button
|
||||
onClick={() => setShowModalCancel(true)}
|
||||
className='flex cursor-pointer gap-2 text-[#B3261E] hover:bg-red-50 p-2 rounded-lg transition-colors'
|
||||
>
|
||||
<div>لغو سفارش</div>
|
||||
<CloseCircle size={16} />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Cancel Order Modal */}
|
||||
<Modal
|
||||
open={showModalCancel}
|
||||
close={() => setShowModalCancel(false)}
|
||||
isHeader
|
||||
title='درخواست لغو سفارش'
|
||||
>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className='mt-6'>
|
||||
<Select
|
||||
placeholder='دلیل لغو سفارش'
|
||||
items={mockCancelReasons.map((item) => ({
|
||||
label: item.title,
|
||||
value: item._id
|
||||
}))}
|
||||
className='bg-white border'
|
||||
value={watch('reason')}
|
||||
name='reason'
|
||||
onChange={(e) => setValue('reason', e.target.value)}
|
||||
error_text={errors.reason?.message}
|
||||
/>
|
||||
|
||||
<Input
|
||||
variant='floating_outlined'
|
||||
label='تعداد'
|
||||
className='mt-6'
|
||||
{...register('count')}
|
||||
error_text={errors.count?.message}
|
||||
/>
|
||||
|
||||
<div className='mt-6'>
|
||||
<div className="text-sm font-medium mb-2">توضیحات :</div>
|
||||
<textarea
|
||||
{...register('description')}
|
||||
className='h-[123px] rounded-xl p-3 text-sm border w-full resize-none focus:outline-none focus:ring-2 focus:ring-primary/20'
|
||||
placeholder='متن توضیحات خود را بنویسید'
|
||||
/>
|
||||
{errors.description && (
|
||||
<div className='text-xs text-red-500 mt-1'>
|
||||
{errors.description.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='flex gap-2 text-xs text-[#7F7F7F] mt-4 p-3 bg-blue-50 rounded-lg'>
|
||||
<div className="size-5 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
<span className="text-blue-600 text-xs">!</span>
|
||||
</div>
|
||||
<span>
|
||||
توجه داشته باشید که بعد از ثبت لغو سفارش طبق
|
||||
</span>
|
||||
<span className='text-primary font-medium'>
|
||||
جدول جرائم
|
||||
</span>
|
||||
<span>
|
||||
جریمه میشود
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<Button
|
||||
type="submit"
|
||||
className='w-full'
|
||||
disabled={isLoading}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
لغو سفارش
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withLayout(OrderDetail)
|
||||
@@ -0,0 +1,65 @@
|
||||
'use client'
|
||||
import { FC, ReactNode, useEffect } from 'react'
|
||||
import { CloseCircle } from 'iconsax-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
close: () => void
|
||||
children: ReactNode
|
||||
title?: string
|
||||
isHeader?: boolean
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Modal: FC<Props> = ({ open, close, children, title, isHeader = false, className }) => {
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
document.body.style.overflow = 'hidden'
|
||||
} else {
|
||||
document.body.style.overflow = 'unset'
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.body.style.overflow = 'unset'
|
||||
}
|
||||
}, [open])
|
||||
|
||||
if (!open) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
||||
onClick={close}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
<div className={cn(
|
||||
"relative bg-white rounded-2xl shadow-xl max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto",
|
||||
className
|
||||
)}>
|
||||
{/* Header */}
|
||||
{isHeader && (
|
||||
<div className="flex items-center justify-between p-6 border-b border-gray-200">
|
||||
<h2 className="text-lg font-semibold text-gray-900">{title}</h2>
|
||||
<button
|
||||
onClick={close}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
<CloseCircle size={20} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
||||
@@ -0,0 +1,15 @@
|
||||
'use client'
|
||||
import { FC } from 'react'
|
||||
|
||||
const PageLoading: FC = () => {
|
||||
return (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
<div className="size-10 border-4 border-primary/20 border-t-primary rounded-full animate-spin"></div>
|
||||
<p className="text-gray-600">در حال بارگذاری...</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageLoading
|
||||
@@ -0,0 +1,114 @@
|
||||
'use client'
|
||||
import { FC, useState, useRef, useEffect } from 'react'
|
||||
import { ArrowDown2 } from 'iconsax-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type SelectItem = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
type Props = {
|
||||
placeholder?: string
|
||||
items?: SelectItem[]
|
||||
value?: string
|
||||
name?: string
|
||||
onChange?: (e: { target: { name: string; value: string } }) => void
|
||||
className?: string
|
||||
error_text?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const Select: FC<Props> = ({
|
||||
placeholder,
|
||||
items = [],
|
||||
value,
|
||||
name,
|
||||
onChange,
|
||||
className,
|
||||
error_text,
|
||||
disabled = false
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [selectedItem, setSelectedItem] = useState<SelectItem | null>(
|
||||
items.find(item => item.value === value) || null
|
||||
)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside)
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const item = items.find(item => item.value === value)
|
||||
setSelectedItem(item || null)
|
||||
}, [value, items])
|
||||
|
||||
const handleSelect = (item: SelectItem) => {
|
||||
setSelectedItem(item)
|
||||
setIsOpen(false)
|
||||
|
||||
if (onChange && name) {
|
||||
onChange({
|
||||
target: {
|
||||
name,
|
||||
value: item.value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full relative" ref={dropdownRef}>
|
||||
<div
|
||||
className={cn(
|
||||
"w-full h-10 px-4 py-2 text-sm border border-[#D0D0D0] rounded-xl bg-white cursor-pointer flex items-center justify-between",
|
||||
disabled && "bg-gray-100 cursor-not-allowed",
|
||||
error_text && "border-red-500",
|
||||
className
|
||||
)}
|
||||
onClick={() => !disabled && setIsOpen(!isOpen)}
|
||||
>
|
||||
<span className={selectedItem ? "text-black" : "text-gray-500"}>
|
||||
{selectedItem ? selectedItem.label : placeholder}
|
||||
</span>
|
||||
<ArrowDown2
|
||||
size={16}
|
||||
className={cn(
|
||||
"transition-transform duration-200",
|
||||
isOpen && "rotate-180"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute top-full left-0 right-0 mt-1 bg-white border border-[#D0D0D0] rounded-xl shadow-lg z-50 max-h-60 overflow-y-auto">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="px-4 py-2 hover:bg-gray-50 cursor-pointer text-sm"
|
||||
onClick={() => handleSelect(item)}
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error_text && (
|
||||
<div className="text-xs text-red-500 mt-1">
|
||||
{error_text}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Select
|
||||
@@ -1,12 +1,11 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { ClipLoader } from "react-spinners"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 dark:aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
@@ -61,7 +60,7 @@ function Button({
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<ClipLoader size={16} color="currentColor" />
|
||||
<div className="size-4 border-2 border-current/20 border-t-current rounded-full animate-spin"></div>
|
||||
<span>در حال بارگذاری...</span>
|
||||
</>
|
||||
) : (
|
||||
|
||||
Reference in New Issue
Block a user