comment + favorive
This commit is contained in:
@@ -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' />
|
||||
|
||||
<GridWrapper desktop={5} mobile={2} className="mt-6 sm:mt-8 md:mt-10 lg:mt-14">
|
||||
<ProductCard />
|
||||
<ProductCard />
|
||||
<ProductCard />
|
||||
<ProductCard />
|
||||
</GridWrapper>
|
||||
{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">
|
||||
{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[];
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user