diff --git a/package-lock.json b/package-lock.json
index 7706633..8ef3c9a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index acaf2d6..c0b4e96 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/app/auth/service/Service.ts b/src/app/auth/service/Service.ts
index 535b50f..3e7d1b2 100644
--- a/src/app/auth/service/Service.ts
+++ b/src/app/auth/service/Service.ts
@@ -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;
};
diff --git a/src/app/globals.css b/src/app/globals.css
index b918994..1db13e4 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -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%;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 5a7ff96..f91834f 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -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";
diff --git a/src/app/product/components/AddComment.tsx b/src/app/product/components/AddComment.tsx
index 0bc6228..2369226 100644
--- a/src/app/product/components/AddComment.tsx
+++ b/src/app/product/components/AddComment.tsx
@@ -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'
diff --git a/src/app/products/hooks/useProductsData.ts b/src/app/products/hooks/useProductsData.ts
index be30b70..f1c6083 100644
--- a/src/app/products/hooks/useProductsData.ts
+++ b/src/app/products/hooks/useProductsData.ts
@@ -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"] });
+ },
});
};
diff --git a/src/app/profile/comments/page.tsx b/src/app/profile/comments/page.tsx
index c8d7556..e1bea30 100644
--- a/src/app/profile/comments/page.tsx
+++ b/src/app/profile/comments/page.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+ {comment.product.title_fa}
+
+
+
+
+
+
+
+ {comment.user.fullName}
+
+ {formatDate(comment.createdAt)}
+
+ خریدار
+
+
+ {comment.title && (
+
+ {comment.title}
+
+ )}
+
+ {comment.content && (
+
+ {comment.content}
+
+ )}
+
+
+ {comment.advantage && comment.advantage.length > 0 && (
+
+
مزایا:
+
+ {comment.advantage.map((adv, index) => (
+ -
+ •
+ {adv}
+
+ ))}
+
+
+ )}
+
+ {comment.disAdvantage && comment.disAdvantage.length > 0 && (
+
+
معایب:
+
+ {comment.disAdvantage.map((disadv, index) => (
+ -
+ •
+ {disadv}
+
+ ))}
+
+
+ )}
+
+
+
+
+ )
+}
const Comments: NextPage = () => {
+
+ const { data: comments, isLoading } = useGetComments()
+
+ if (isLoading) {
+ return
+ }
+
+ if (!comments?.results?.comments?.length) {
+ return (
+
+
+
+
+
+
+ کامنتی یافت نشد
+
+
+ شما هنوز کامنتی ثبت نکردهاید
+
+
+
+
+ )
+ }
+
return (
-
-
-
-
-
-
- نان بربری سنتی سبوس دار
-
-
- 2 آذر 1401
-
-
-
-
- خیلی راضیم از خریدم
-
-
-
-
-
-
-
-
-
- نان بربری سنتی سبوس دار
-
-
- 2 آذر 1401
-
-
-
-
- خیلی راضیم از خریدم
-
-
-
-
-
-
-
-
-
- نان بربری سنتی سبوس دار
-
-
- 2 آذر 1401
-
-
-
-
- خیلی راضیم از خریدم
-
-
+
+ {comments?.results?.comments.map((comment) => (
+
+ ))}
diff --git a/src/app/profile/favorite/page.tsx b/src/app/profile/favorite/page.tsx
index dcf1acd..5185155 100644
--- a/src/app/profile/favorite/page.tsx
+++ b/src/app/profile/favorite/page.tsx
@@ -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 (
-
-
-
-
-
-
+ {products.length === 0 && !isLoading ? (
+
+
لیست علاقهمندیهای شما خالی است
+
محصولات مورد علاقه خود را اضافه کنید
+
+ ) : (
+
+ {products.map((product) => (
+
+ ))}
+
+ )}
)
}
diff --git a/src/app/profile/hooks/useProfileData.ts b/src/app/profile/hooks/useProfileData.ts
index 1126d4b..11b5e82 100644
--- a/src/app/profile/hooks/useProfileData.ts
+++ b/src/app/profile/hooks/useProfileData.ts
@@ -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,
+ });
+};
diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx
index 82742d5..b035d75 100644
--- a/src/app/profile/page.tsx
+++ b/src/app/profile/page.tsx
@@ -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 = () => {
- setValue('dateOfBirth', date)}
+ defaulValue={profile?.results?.info?.dateOfBirth || ''}
error_text={errors.dateOfBirth?.message}
+ readOnly={!isEditing}
/>
diff --git a/src/app/profile/service/Service.ts b/src/app/profile/service/Service.ts
index aec687c..ea4db5a 100644
--- a/src/app/profile/service/Service.ts
+++ b/src/app/profile/service/Service.ts
@@ -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 => {
+ const { data } = await axios.get("/user/profile/wishlist");
+ return data;
+};
+
+export const getComments = async (): Promise => {
+ const { data } = await axios.get("/user/profile/comments");
+ return data;
+};
diff --git a/src/app/profile/types/ProfileTypes.ts b/src/app/profile/types/ProfileTypes.ts
index 9e5552f..2310ca6 100644
--- a/src/app/profile/types/ProfileTypes.ts
+++ b/src/app/profile/types/ProfileTypes.ts
@@ -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[];
+ };
+}
diff --git a/src/components/DatePicker.tsx b/src/components/DatePicker.tsx
new file mode 100644
index 0000000..b94d9ce
--- /dev/null
+++ b/src/components/DatePicker.tsx
@@ -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) => {
+ const [value, setValue] = useState(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 (
+
+
+ {props.label}
+
+
+
setValue(date as DateObject)}
+ calendar={persian}
+ locale={persian_fa}
+ calendarPosition="bottom-right"
+ className={props.className}
+ readOnly={props.readOnly}
+ />
+ {props.error_text && props.error_text !== '' && (
+
+ {props.error_text}
+
+ )}
+
+ {/*
*/}
+
+
+ );
+};
+
+export default DatePickerComponent;
\ No newline at end of file
diff --git a/src/components/ProductCard.tsx b/src/components/ProductCard.tsx
index 5e923eb..48bb4fa 100644
--- a/src/components/ProductCard.tsx
+++ b/src/components/ProductCard.tsx
@@ -22,7 +22,10 @@ const ProductCard: FC = ({ 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