comment + favorive
This commit is contained in:
Generated
+31
@@ -33,6 +33,7 @@
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-spinners": "^0.17.0",
|
||||
"swiper": "^11.2.10",
|
||||
@@ -6244,6 +6245,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-date-object": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.9.tgz",
|
||||
"integrity": "sha512-BHxD/quWOTo9fLKV/cfL/M31ePoj4a1JaJ/CnOf8Ndg3mrkh4x9wEMMkCfTrzduxDOgU8ZgR8uarhqI5G71sTg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.1.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||
@@ -6256,6 +6263,16 @@
|
||||
"react": "^19.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-element-popper": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.7.tgz",
|
||||
"integrity": "sha512-tuM2OxKlW32h+6uFSK6EENHPeZ2OGgOipHfOAl+VLWEv9/j3QkSGbD+ADX3A9uJlmq24i37n28RjJmAbGTfpEg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.62.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||
@@ -6303,6 +6320,20 @@
|
||||
"react-dom": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-multi-date-picker": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz",
|
||||
"integrity": "sha512-FgWjZB3Z6IA6XpcWiLPk85PwcRUhOiYhKK42o5k672gD/n2I6rzPfQ8bUrldOIiF/Z7FfOCdH7a6FeubzqteLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-date-object": "^2.1.8",
|
||||
"react-element-popper": "^2.1.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-otp-input": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/react-otp-input/-/react-otp-input-3.1.1.tgz",
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
"react-hook-form": "^7.62.0",
|
||||
"react-infinite-scroll-component": "^6.1.0",
|
||||
"react-leaflet": "^5.0.0",
|
||||
"react-multi-date-picker": "^4.5.2",
|
||||
"react-otp-input": "^3.1.1",
|
||||
"react-spinners": "^0.17.0",
|
||||
"swiper": "^11.2.10",
|
||||
|
||||
@@ -16,6 +16,6 @@ export const verifyOtp = async (params: VerifyOtpType) => {
|
||||
};
|
||||
|
||||
export const refreshToken = async (params: RefreshTokenType) => {
|
||||
const { data } = await axios.post(`/auth/refresh`, params);
|
||||
const { data } = await axios.post(`/user/token`, params);
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -265,3 +265,20 @@ textarea.place-black::placeholder {
|
||||
|
||||
backdrop-filter: blur(44px);
|
||||
}
|
||||
|
||||
.rmdp-input {
|
||||
min-height: 40px;
|
||||
border-radius: 12px !important;
|
||||
/* border: 1px solid #d0d0d0 !important; */
|
||||
font-size: 12px !important;
|
||||
width: 100% !important;
|
||||
margin: 0px;
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
.readOny .rmdp-input {
|
||||
background-color: #f5f5f5 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.rmdp-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import "../../public/css/fontiran.css";
|
||||
import 'swiper/css';
|
||||
import 'rc-rate/assets/index.css'
|
||||
import QueryProvider from "@/config/QueryProvider";
|
||||
import ToastContainer from "@/components/Toast";
|
||||
import MobileBottomMenu from "@/components/MobileBottomMenu";
|
||||
|
||||
@@ -9,7 +9,6 @@ import { yupResolver } from '@hookform/resolvers/yup'
|
||||
import { Add } from 'iconsax-react'
|
||||
import { PRIMARY_COLOR } from '@/config/const'
|
||||
import Rate from 'rc-rate'
|
||||
import 'rc-rate/assets/index.css'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { useAddComment } from '../hooks/useProductData'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import * as api from "../service/Service";
|
||||
import { ProductsResponse } from "@/types/products.types";
|
||||
|
||||
@@ -36,13 +36,23 @@ export const useCategoryProductsData = (
|
||||
};
|
||||
|
||||
export const useAddWishlist = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: api.addWishlist,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["with-list"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useRemoveWishlist = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: api.removeWishlist,
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["with-list"] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,85 +1,137 @@
|
||||
'use client'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { NextPage } from 'next'
|
||||
import Menu from '../components/Menu'
|
||||
import Image from 'next/image'
|
||||
import PageLoading from '@/components/PageLoading'
|
||||
import { useGetComments } from '../hooks/useProfileData'
|
||||
import { Comment } from '../types/ProfileTypes'
|
||||
import Rate from 'rc-rate'
|
||||
|
||||
const CommentItem: React.FC<{ comment: Comment }> = ({ comment }) => {
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleDateString('fa-IR', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric'
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='bg-white rounded-[20px] p-4 sm:p-6 mb-4 sm:mb-6 shadow-sm border border-border'>
|
||||
<div className='flex flex-col sm:flex-row gap-4 sm:gap-6'>
|
||||
<div className='flex justify-center sm:justify-start'>
|
||||
<Image
|
||||
src={comment.product.imagesUrl.cover || 'https://picsum.photos/200/300'}
|
||||
alt={comment.product.title_fa}
|
||||
width={120}
|
||||
height={120}
|
||||
className='max-w-[100px] sm:max-w-[120px] max-h-[100px] sm:max-h-[120px] object-contain rounded-lg'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex-1'>
|
||||
<div className='flex flex-col sm:flex-row sm:items-start justify-between gap-2 mb-3'>
|
||||
<h3 className='text-sm sm:text-base font-medium text-[#383E43] leading-5'>
|
||||
{comment.product.title_fa}
|
||||
</h3>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Rate value={comment.rate} disabled />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex flex-wrap items-center gap-2 text-xs text-[#999999] mb-4'>
|
||||
<span>{comment.user.fullName}</span>
|
||||
<span className="size-1 rounded-full bg-[#E5E5E5]"></span>
|
||||
<span>{formatDate(comment.createdAt)}</span>
|
||||
<span className="size-1 rounded-full bg-[#E5E5E5]"></span>
|
||||
<span className='px-2 py-0.5 rounded bg-[#F1F1F1] text-[#7F7F7F]'>خریدار</span>
|
||||
</div>
|
||||
|
||||
{comment.title && (
|
||||
<div className='text-sm font-medium text-[#333333] mb-2'>
|
||||
{comment.title}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{comment.content && (
|
||||
<div className='text-xs sm:text-sm text-[#666666] leading-6 sm:leading-7 mb-3'>
|
||||
{comment.content}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className='grid grid-cols-1 sm:grid-cols-2 gap-3'>
|
||||
{comment.advantage && comment.advantage.length > 0 && (
|
||||
<div className='bg-green-50 rounded-lg p-3'>
|
||||
<div className='text-xs sm:text-sm text-green-700 font-medium mb-2'>مزایا:</div>
|
||||
<ul className='text-xs sm:text-sm text-green-600 space-y-1'>
|
||||
{comment.advantage.map((adv, index) => (
|
||||
<li key={index} className='flex items-start gap-2'>
|
||||
<span className='text-green-500 mt-0.5'>•</span>
|
||||
<span>{adv}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{comment.disAdvantage && comment.disAdvantage.length > 0 && (
|
||||
<div className='bg-red-50 rounded-lg p-3'>
|
||||
<div className='text-xs sm:text-sm text-red-700 font-medium mb-2'>معایب:</div>
|
||||
<ul className='text-xs sm:text-sm text-red-600 space-y-1'>
|
||||
{comment.disAdvantage.map((disadv, index) => (
|
||||
<li key={index} className='flex items-start gap-2'>
|
||||
<span className='text-red-500 mt-0.5'>•</span>
|
||||
<span>{disadv}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Comments: NextPage = () => {
|
||||
|
||||
const { data: comments, isLoading } = useGetComments()
|
||||
|
||||
if (isLoading) {
|
||||
return <PageLoading />
|
||||
}
|
||||
|
||||
if (!comments?.results?.comments?.length) {
|
||||
return (
|
||||
<div className='p-3 sm:p-4 md:p-6'>
|
||||
<Menu pageActive='comments' />
|
||||
<div className='mt-6 sm:mt-8 md:mt-10 lg:mt-14'>
|
||||
|
||||
<div className='bg-white rounded-[20px] p-8 sm:p-12 text-center shadow-sm border border-border'>
|
||||
<div className='text-lg sm:text-xl text-gray-600 mb-2'>
|
||||
کامنتی یافت نشد
|
||||
</div>
|
||||
<div className='text-sm text-gray-500'>
|
||||
شما هنوز کامنتی ثبت نکردهاید
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='p-3 sm:p-4 md:p-6'>
|
||||
<Menu pageActive='comments' />
|
||||
|
||||
<div className='mt-6 sm:mt-8 md:mt-10 lg:mt-14'>
|
||||
<div className='flex flex-col sm:flex-row gap-3 sm:gap-4 lg:gap-6 border-b border-border pb-4 sm:pb-6'>
|
||||
<Image
|
||||
src='https://picsum.photos/200/300'
|
||||
alt='comment'
|
||||
width={100}
|
||||
height={100}
|
||||
className='max-w-16 sm:max-w-20 max-h-16 sm:max-h-20 object-contain mx-auto sm:mx-0'
|
||||
/>
|
||||
|
||||
<div className='flex-1'>
|
||||
<div className='border-b border-border pb-3 sm:pb-4'>
|
||||
<div className='text-sm sm:text-base'>
|
||||
نان بربری سنتی سبوس دار
|
||||
</div>
|
||||
<div className='mt-1.5 text-xs sm:text-sm text-[#999999]'>
|
||||
2 آذر 1401
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-3 sm:mt-4 text-[#4C4C4C] text-xs sm:text-sm'>
|
||||
خیلی راضیم از خریدم
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col sm:flex-row gap-3 sm:gap-4 lg:gap-6 border-b border-border py-4 sm:py-6'>
|
||||
<Image
|
||||
src='https://picsum.photos/200/300'
|
||||
alt='comment'
|
||||
width={100}
|
||||
height={100}
|
||||
className='max-w-16 sm:max-w-20 max-h-16 sm:max-h-20 object-contain mx-auto sm:mx-0'
|
||||
/>
|
||||
|
||||
<div className='flex-1'>
|
||||
<div className='border-b border-border pb-3 sm:pb-4'>
|
||||
<div className='text-sm sm:text-base'>
|
||||
نان بربری سنتی سبوس دار
|
||||
</div>
|
||||
<div className='mt-1.5 text-xs sm:text-sm text-[#999999]'>
|
||||
2 آذر 1401
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-3 sm:mt-4 text-[#4C4C4C] text-xs sm:text-sm'>
|
||||
خیلی راضیم از خریدم
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col sm:flex-row gap-3 sm:gap-4 lg:gap-6 border-b border-border py-4 sm:py-6'>
|
||||
<Image
|
||||
src='https://picsum.photos/200/300'
|
||||
alt='comment'
|
||||
width={100}
|
||||
height={100}
|
||||
className='max-w-16 sm:max-w-20 max-h-16 sm:max-h-20 object-contain mx-auto sm:mx-0'
|
||||
/>
|
||||
|
||||
<div className='flex-1'>
|
||||
<div className='border-b border-border pb-3 sm:pb-4'>
|
||||
<div className='text-sm sm:text-base'>
|
||||
نان بربری سنتی سبوس دار
|
||||
</div>
|
||||
<div className='mt-1.5 text-xs sm:text-sm text-[#999999]'>
|
||||
2 آذر 1401
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='mt-3 sm:mt-4 text-[#4C4C4C] text-xs sm:text-sm'>
|
||||
خیلی راضیم از خریدم
|
||||
</div>
|
||||
</div>
|
||||
<div className='space-y-4 sm:space-y-6'>
|
||||
{comments?.results?.comments.map((comment) => (
|
||||
<CommentItem key={comment._id} comment={comment} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,87 @@
|
||||
'use client'
|
||||
import Layout from "@/hoc/Layout"
|
||||
import { NextPage } from "next"
|
||||
import Menu from "../components/Menu"
|
||||
import GridWrapper from "@/components/GridWrapper"
|
||||
import ProductCard from "@/components/ProductCard"
|
||||
import { useGetWithList } from "../hooks/useProfileData"
|
||||
import { IProduct } from "@/types/landing.types"
|
||||
import { WishlistItem } from "../types/ProfileTypes"
|
||||
|
||||
|
||||
const FavoritePage: NextPage = () => {
|
||||
|
||||
const { data: withList, isLoading } = useGetWithList()
|
||||
|
||||
// تبدیل دادههای wishlist به فرمت IProduct
|
||||
const transformWishlistToProducts = (wishlistItems: WishlistItem[]): IProduct[] => {
|
||||
return wishlistItems.map((item) => ({
|
||||
_id: item.product._id,
|
||||
title_fa: item.product.title_fa,
|
||||
title_en: item.product.title_en,
|
||||
seoTitle: item.product.seoTitle,
|
||||
seoDescription: item.product.seoDescription,
|
||||
model: '', // این فیلد در wishlist وجود ندارد
|
||||
description: item.product.description,
|
||||
tags: item.product.tags,
|
||||
shop: {
|
||||
_id: item.variant.shop._id,
|
||||
shopName: item.variant.shop.shopName,
|
||||
shopDescription: item.variant.shop.shopDescription,
|
||||
logo: item.variant.shop.logo,
|
||||
},
|
||||
status: 'active', // فرض میکنیم فعال است
|
||||
imagesUrl: item.product.imagesUrl,
|
||||
url: item.product.url,
|
||||
default_variant: {
|
||||
_id: item.variant._id,
|
||||
market_status: item.variant.market_status,
|
||||
price: item.variant.price,
|
||||
stock: item.variant.stock,
|
||||
isFreeShip: item.variant.isFreeShip,
|
||||
isWholeSale: item.variant.isWholeSale,
|
||||
},
|
||||
variants: [{
|
||||
_id: item.variant._id,
|
||||
market_status: item.variant.market_status as "Marketable" | "Unmarketable" | "OutOfStock",
|
||||
price: item.variant.price,
|
||||
stock: item.variant.stock,
|
||||
postingTime: item.variant.postingTime,
|
||||
isFreeShip: item.variant.isFreeShip,
|
||||
isWholeSale: item.variant.isWholeSale,
|
||||
shop: {
|
||||
_id: item.variant.shop._id,
|
||||
shopName: item.variant.shop.shopName,
|
||||
shopDescription: item.variant.shop.shopDescription,
|
||||
logo: item.variant.shop.logo,
|
||||
},
|
||||
shipmentMethod: [], // این فیلد در wishlist وجود ندارد
|
||||
warranty: item.variant.warranty,
|
||||
meterage: item.variant.meterage ? { _id: 0, value: item.variant.meterage.toString() } : undefined,
|
||||
size: item.variant.size ? { _id: 0, value: item.variant.size.toString() } : undefined,
|
||||
}],
|
||||
isWishlist: true, // چون در لیست علاقهمندیها است
|
||||
}))
|
||||
}
|
||||
|
||||
const products = withList?.results?.wishlist ? transformWishlistToProducts(withList.results.wishlist) : []
|
||||
|
||||
return (
|
||||
<div className="p-3 sm:p-4 md:p-6">
|
||||
<Menu pageActive='favorite' />
|
||||
|
||||
{products.length === 0 && !isLoading ? (
|
||||
<div className="flex flex-col items-center justify-center mt-12 text-center">
|
||||
<div className="text-gray-400 text-lg mb-4">لیست علاقهمندیهای شما خالی است</div>
|
||||
<div className="text-gray-500 text-sm">محصولات مورد علاقه خود را اضافه کنید</div>
|
||||
</div>
|
||||
) : (
|
||||
<GridWrapper desktop={5} mobile={2} className="mt-6 sm:mt-8 md:mt-10 lg:mt-14">
|
||||
<ProductCard />
|
||||
<ProductCard />
|
||||
<ProductCard />
|
||||
<ProductCard />
|
||||
{products.map((product) => (
|
||||
<ProductCard key={product._id} item={product} />
|
||||
))}
|
||||
</GridWrapper>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -38,3 +38,17 @@ export const useUpdateAddress = () => {
|
||||
api.updateAddress(addressData),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetWithList = () => {
|
||||
return useQuery({
|
||||
queryKey: ["with-list"],
|
||||
queryFn: api.getWithList,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetComments = () => {
|
||||
return useQuery({
|
||||
queryKey: ["comments"],
|
||||
queryFn: api.getComments,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -7,11 +7,12 @@ import { Button } from '@/components/ui/button'
|
||||
import Input from '@/components/Input'
|
||||
import { Edit2 } from 'lucide-react'
|
||||
import Layout from '@/hoc/Layout'
|
||||
import { Heart, ShoppingCart, Edit } from 'iconsax-react'
|
||||
import { Heart, ShoppingCart } from 'iconsax-react'
|
||||
import Menu from './components/Menu'
|
||||
import AddressModal from './components/AddressModal'
|
||||
import { useGetProfile, useUpdateProfile } from './hooks/useProfileData'
|
||||
import { UpdateAddressType } from './types/ProfileTypes'
|
||||
import DatePickerComponent from '@/components/DatePicker'
|
||||
|
||||
// Schema validation
|
||||
const profileSchema = yup.object({
|
||||
@@ -146,11 +147,13 @@ const ProfilePage = () => {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Input
|
||||
<DatePickerComponent
|
||||
label="تاریخ تولد"
|
||||
{...register('dateOfBirth')}
|
||||
readOnly={!isEditing}
|
||||
placeholder="تاریخ تولد خود را انتخاب کنید"
|
||||
onChange={(date) => setValue('dateOfBirth', date)}
|
||||
defaulValue={profile?.results?.info?.dateOfBirth || ''}
|
||||
error_text={errors.dateOfBirth?.message}
|
||||
readOnly={!isEditing}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
UpdateProfileResponse,
|
||||
AddressLocationResponse,
|
||||
UpdateAddressType,
|
||||
WishlistResponse,
|
||||
CommentsResponse,
|
||||
} from "../types/ProfileTypes";
|
||||
|
||||
export const getProfile = async () => {
|
||||
@@ -31,3 +33,13 @@ export const updateAddress = async (addressData: UpdateAddressType) => {
|
||||
const { data } = await axios.post("/address/user/save", addressData);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getWithList = async (): Promise<WishlistResponse> => {
|
||||
const { data } = await axios.get("/user/profile/wishlist");
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getComments = async (): Promise<CommentsResponse> => {
|
||||
const { data } = await axios.get("/user/profile/comments");
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -90,3 +90,193 @@ export type UpdateAddressType = {
|
||||
address: string;
|
||||
plaque: string;
|
||||
};
|
||||
|
||||
// Wishlist Types
|
||||
export interface WishlistUser {
|
||||
_id: string;
|
||||
fullName: string;
|
||||
phoneNumber: string;
|
||||
dateOfBirth: string;
|
||||
address: {
|
||||
_id: string;
|
||||
};
|
||||
nationalCode: string;
|
||||
homeNumber: string | null;
|
||||
}
|
||||
|
||||
export interface WishlistBrand {
|
||||
_id: string;
|
||||
}
|
||||
|
||||
export interface WishlistCategory {
|
||||
_id: string;
|
||||
}
|
||||
|
||||
export interface WishlistSpecification {
|
||||
title: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface WishlistImagesUrl {
|
||||
cover: string;
|
||||
list: string[];
|
||||
}
|
||||
|
||||
export interface WishlistProduct {
|
||||
_id: number;
|
||||
url: string;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
seoTitle: string | null;
|
||||
seoDescription: string | null;
|
||||
source: string;
|
||||
description: string;
|
||||
metaDescription: string;
|
||||
tags: string[];
|
||||
advantages: string[];
|
||||
disAdvantages: string[];
|
||||
imagesUrl: WishlistImagesUrl;
|
||||
isFake: string;
|
||||
voice: string | null;
|
||||
specifications: WishlistSpecification[];
|
||||
brand: WishlistBrand;
|
||||
category: WishlistCategory;
|
||||
}
|
||||
|
||||
export interface WishlistShop {
|
||||
_id: string;
|
||||
shopName: string;
|
||||
shopCode: number;
|
||||
owner: string;
|
||||
shopDescription: string;
|
||||
telephoneNumber: string;
|
||||
isChatActive: boolean;
|
||||
shopPostalCode: string;
|
||||
logo: string;
|
||||
}
|
||||
|
||||
export interface WishlistWarranty {
|
||||
_id: number;
|
||||
duration: string;
|
||||
logoUrl: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface WishlistPrice {
|
||||
order_limit: number;
|
||||
retailPrice: number;
|
||||
selling_price: number;
|
||||
is_specialSale: boolean;
|
||||
discount_percent: number;
|
||||
specialSale_order_limit: number | null;
|
||||
specialSale_quantity: number | null;
|
||||
specialSale_endDate: string | null;
|
||||
}
|
||||
|
||||
export interface WishlistVariant {
|
||||
_id: string;
|
||||
market_status: string;
|
||||
price: WishlistPrice;
|
||||
stock: number;
|
||||
postingTime: number;
|
||||
isFreeShip: boolean;
|
||||
isWholeSale: boolean;
|
||||
shop: WishlistShop;
|
||||
warranty: WishlistWarranty;
|
||||
size?: number;
|
||||
meterage?: number;
|
||||
}
|
||||
|
||||
export interface WishlistItem {
|
||||
_id: string;
|
||||
user: WishlistUser;
|
||||
product: WishlistProduct;
|
||||
variant: WishlistVariant;
|
||||
}
|
||||
|
||||
export interface WishlistResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
wishlist: WishlistItem[];
|
||||
};
|
||||
}
|
||||
|
||||
// Comments Types
|
||||
export interface CommentUser {
|
||||
fullName: string;
|
||||
}
|
||||
|
||||
export interface CommentBrand {
|
||||
_id: string;
|
||||
status: string;
|
||||
title_en: string;
|
||||
title_fa: string;
|
||||
images: string[];
|
||||
logoUrl: string;
|
||||
}
|
||||
|
||||
export interface CommentCategory {
|
||||
_id: string;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
icon: string;
|
||||
imageUrl: string;
|
||||
theme: string;
|
||||
description: string;
|
||||
leaf: boolean;
|
||||
parent: string;
|
||||
}
|
||||
|
||||
export interface CommentSpecification {
|
||||
title: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
export interface CommentImagesUrl {
|
||||
cover: string;
|
||||
list: string[];
|
||||
}
|
||||
|
||||
export interface CommentProduct {
|
||||
_id: number;
|
||||
url: string;
|
||||
title_fa: string;
|
||||
title_en: string;
|
||||
seoTitle: string;
|
||||
seoDescription: string | null;
|
||||
source: string;
|
||||
description: string;
|
||||
metaDescription: string;
|
||||
tags: string[];
|
||||
advantages: string[];
|
||||
disAdvantages: string[];
|
||||
imagesUrl: CommentImagesUrl;
|
||||
isFake: string;
|
||||
voice: string | null;
|
||||
specifications: CommentSpecification[];
|
||||
brand: CommentBrand;
|
||||
category: CommentCategory;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
_id: string;
|
||||
title: string;
|
||||
advantage: string[];
|
||||
disAdvantage: string[];
|
||||
content: string;
|
||||
status: string;
|
||||
rate: number;
|
||||
user: CommentUser;
|
||||
product: CommentProduct;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface CommentsResponse {
|
||||
status: number;
|
||||
success: boolean;
|
||||
results: {
|
||||
comments: Comment[];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useState, useEffect, FC } from 'react';
|
||||
import DatePicker from 'react-multi-date-picker';
|
||||
import persian from 'react-date-object/calendars/persian';
|
||||
import persian_fa from 'react-date-object/locales/persian_fa';
|
||||
import DateObject from 'react-date-object';
|
||||
import { clx } from '../helpers/utils';
|
||||
|
||||
type Props = {
|
||||
onChange: (date: string) => void;
|
||||
defaulValue?: string;
|
||||
error_text?: string;
|
||||
placeholder: string;
|
||||
reset?: boolean;
|
||||
isDateTime?: boolean;
|
||||
className?: string;
|
||||
label: string,
|
||||
readOnly?: boolean;
|
||||
};
|
||||
|
||||
const DatePickerComponent: FC<Props> = (props: Props) => {
|
||||
const [value, setValue] = useState<DateObject | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.reset) {
|
||||
setValue(null);
|
||||
}
|
||||
}, [props.reset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
const formattedDate = `${value.year}/${value.month.number}/${value.day}`;
|
||||
props.onChange(formattedDate);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.defaulValue && !value) {
|
||||
const defaultDate = new DateObject({
|
||||
date: props.defaulValue,
|
||||
calendar: persian,
|
||||
locale: persian_fa,
|
||||
});
|
||||
setValue(defaultDate);
|
||||
}
|
||||
}, [props.defaulValue, value]);
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className='text-sm'>
|
||||
{props.label}
|
||||
</div>
|
||||
<div className={clx(
|
||||
'relative mt-1.5',
|
||||
props.readOnly && 'readOny'
|
||||
)}>
|
||||
<DatePicker
|
||||
placeholder={props.placeholder}
|
||||
value={value}
|
||||
onChange={(date) => setValue(date as DateObject)}
|
||||
calendar={persian}
|
||||
locale={persian_fa}
|
||||
calendarPosition="bottom-right"
|
||||
className={props.className}
|
||||
readOnly={props.readOnly}
|
||||
/>
|
||||
{props.error_text && props.error_text !== '' && (
|
||||
<div className="text-xs text-right text-red-600 mt-2 mr-2 font-medium">
|
||||
{props.error_text}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* <img src={CalenderIcon} className='absolute top-0 bottom-0 my-auto left-2' /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatePickerComponent;
|
||||
@@ -22,7 +22,10 @@ const ProductCard: FC<Props> = ({ item }) => {
|
||||
const { mutate: removeWishlist } = useRemoveWishlist()
|
||||
|
||||
|
||||
const handleWish = () => {
|
||||
const handleWish = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
if (!isLogin) return
|
||||
if (!item?._id || !item?.variants[0]?._id) return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user