diff --git a/src/@core/utils/formatPersianDate.ts b/src/@core/utils/formatPersianDate.ts
new file mode 100644
index 0000000..f4dba7e
--- /dev/null
+++ b/src/@core/utils/formatPersianDate.ts
@@ -0,0 +1,35 @@
+const formatPersianDate = (dateString: string) => {
+ if (!dateString) return "";
+
+ // جدا کردن بخش تاریخ از ساعت: "1404-12-25"
+ const [datePart] = dateString.split(" ");
+ if (!datePart) return dateString;
+
+ // جدا کردن سال، ماه و روز
+ const [year, month, day] = datePart.split("-");
+
+ const persianMonths = [
+ "فروردین",
+ "اردیبهشت",
+ "خرداد",
+ "تیر",
+ "مرداد",
+ "شهریور",
+ "مهر",
+ "آبان",
+ "آذر",
+ "دی",
+ "بهمن",
+ "اسفند",
+ ];
+
+ // پیدا کردن نام ماه (ایندکس آرایه از 0 شروع میشود پس یکی کم میکنیم)
+ const monthName = persianMonths[parseInt(month, 10) - 1];
+
+ // حذف صفر پشت روز (مثلا 05 بشود 5)
+ const cleanDay = parseInt(day, 10);
+
+ return `${cleanDay} ${monthName} ${year}`;
+};
+
+export default formatPersianDate;
diff --git a/src/app/checkout/page.tsx b/src/app/checkout/page.tsx
index 3d76dfe..d2e4829 100644
--- a/src/app/checkout/page.tsx
+++ b/src/app/checkout/page.tsx
@@ -16,7 +16,8 @@ import {
ShieldCheck,
CreditCard,
Edit2,
- Trash2
+ Trash2,
+ Star
} from "lucide-react";
import { useUpdateUserProfile, useUserProfile } from "@/services/useUser";
import {
@@ -26,11 +27,140 @@ import {
useDeleteAddress
} from "@/services/useAddresses";
-// تعریف تایپ پروفایل کاربر (بر اساس نیاز پروژه خود آپدیت کنید)
+// === Types ===
interface UserProfile {
name: string;
}
+interface Address {
+ id: number;
+ address: string;
+ postal_code: string;
+ is_default: boolean;
+ latitude?: number;
+ longitude?: number;
+}
+
+// ==========================================
+// کامپوننت کارت آدرس (برای مدیریت مستقل هوک هر آدرس)
+// ==========================================
+const AddressCard = ({
+ addr,
+ selectedAddressId,
+ setSelectedAddressId,
+ handleOpenEditForm,
+ handleDeleteAddress,
+ isDeletingAddress,
+ deletingId
+}: {
+ addr: Address;
+ selectedAddressId: number | null;
+ setSelectedAddressId: (id: number) => void;
+ handleOpenEditForm: (id: number, address: string, postalCode: string) => void;
+ handleDeleteAddress: (id: number) => void;
+ isDeletingAddress: boolean;
+ deletingId: number | null;
+}) => {
+ // هوک آپدیت اختصاصی برای همین آدرس جهت تنظیم به عنوان پیشفرض
+ const { mutate: setAsDefault, isPending: isSettingDefault } = useUpdateAddress(addr.id);
+
+ const handleMakeDefault = (e: React.MouseEvent) => {
+ e.stopPropagation(); // جلوگیری از انتخاب شدن آدرس هنگام کلیک روی دکمه
+ setAsDefault(
+ {
+ address: addr.address,
+ postal_code: addr.postal_code,
+ is_default: true,
+ },
+ {
+ onSuccess: () => toast.success("آدرس پیشفرض با موفقیت تغییر کرد"),
+ onError: () => toast.error("خطا در تغییر آدرس پیشفرض"),
+ }
+ );
+ };
+
+ return (
+
setSelectedAddressId(addr.id)}
+ className={`relative flex flex-col sm:flex-row justify-between p-5 rounded-2xl border-2 cursor-pointer transition-all ${
+ selectedAddressId === addr.id
+ ? "border-salona-500 bg-salona-50/30"
+ : "border-gray-100 hover:border-salona-200"
+ }`}
+ >
+
+ {selectedAddressId === addr.id && (
+
+ )}
+
+ {/* نشانگر آدرس پیشفرض */}
+
+ {addr.is_default ? (
+
+
+ آدرس پیشفرض
+
+ ) : (
+
+ )}
+
+
+
{addr.address}
+
+
+ کد پستی: {addr.postal_code}
+
+
+
+
+ {/* اکشنهای ویرایش و حذف */}
+
+
+
+
+
+ );
+};
+
+// ==========================================
+// کامپوننت اصلی صفحه
+// ==========================================
export default function CheckoutPage() {
// === Redux State ===
const { totalAmount, totalQuantity, items } = useSelector((state: RootState) => state.cart);
@@ -47,7 +177,7 @@ export default function CheckoutPage() {
// === Local States ===
const [selectedAddressId, setSelectedAddressId] = useState(null);
- const [deletingId, setDeletingId] = useState(null); // برای نمایش لودینگ روی دکمه حذف خاص
+ const [deletingId, setDeletingId] = useState(null);
// استیتهای فرم نام گیرنده
const [recipientName, setRecipientName] = useState("");
@@ -61,7 +191,7 @@ export default function CheckoutPage() {
postal_code: "",
});
- // Hook ویرایش آدرس (چون آیدی نیاز دارد، آیدی آدرس در حال ویرایش یا 0 را پاس میدهیم)
+ // Hook ویرایش آدرس برای فرم (استفاده مجزا از فرم)
const { mutate: updateAddress, isPending: isUpdatingAddress } = useUpdateAddress(editingAddressId || 0);
// === Effects ===
@@ -73,10 +203,14 @@ export default function CheckoutPage() {
}
}, [userProfile, isProfileLoading]);
+ // انتخاب خودکار آدرس پیشفرض برای صورتحساب
useEffect(() => {
- if (addressesData?.addresses && addressesData.addresses.length > 0 && !selectedAddressId) {
- const defaultAddress = addressesData.addresses.find((a) => a.is_default);
- setSelectedAddressId(defaultAddress ? defaultAddress.id : addressesData.addresses[0].id);
+ if (addressesData?.addresses && addressesData.addresses.length > 0) {
+ const defaultAddress = addressesData.addresses.find((a: Address) => a.is_default);
+ // اگر کاربر به صورت دستی آدرسی انتخاب نکرده بود، آدرس پیشفرض را انتخاب کن
+ if (!selectedAddressId) {
+ setSelectedAddressId(defaultAddress ? defaultAddress.id : addressesData.addresses[0].id);
+ }
}
}, [addressesData, selectedAddressId]);
@@ -98,28 +232,24 @@ export default function CheckoutPage() {
);
};
- // باز کردن فرم برای افزودن آدرس جدید
const handleOpenAddForm = () => {
setEditingAddressId(null);
setAddressForm({ address: "", postal_code: "" });
setIsAddressFormOpen(true);
};
- // باز کردن فرم برای ویرایش آدرس موجود
const handleOpenEditForm = (addressId: number, currentAddress: string, currentPostalCode: string) => {
setEditingAddressId(addressId);
setAddressForm({ address: currentAddress, postal_code: currentPostalCode });
setIsAddressFormOpen(true);
};
- // بستن فرم آدرس
const handleCloseAddressForm = () => {
setIsAddressFormOpen(false);
setEditingAddressId(null);
setAddressForm({ address: "", postal_code: "" });
};
- // ارسال فرم آدرس (تصمیمگیری بین افزودن یا ویرایش)
const handleAddressSubmit = (e: FormEvent) => {
e.preventDefault();
if (!addressForm.address || !addressForm.postal_code) {
@@ -128,11 +258,12 @@ export default function CheckoutPage() {
}
if (editingAddressId) {
- // منطق ویرایش
updateAddress(
{
address: addressForm.address,
postal_code: addressForm.postal_code,
+ // حفظ وضعیت پیشفرض قبلی هنگام ویرایش متن
+ is_default: addressesData?.addresses?.find((a: Address) => a.id === editingAddressId)?.is_default || false
},
{
onSuccess: () => {
@@ -143,14 +274,14 @@ export default function CheckoutPage() {
}
);
} else {
- // منطق افزودن
addAddress(
{
address: addressForm.address,
postal_code: addressForm.postal_code,
latitude: 0,
longitude: 0,
- is_default: false,
+ // اگر اولین آدرس است، خودکار پیشفرض شود
+ is_default: (addressesData?.addresses?.length || 0) === 0,
},
{
onSuccess: () => {
@@ -163,14 +294,13 @@ export default function CheckoutPage() {
}
};
- // منطق حذف آدرس
const handleDeleteAddress = (addressId: number) => {
setDeletingId(addressId);
deleteAddress(addressId, {
onSuccess: () => {
toast.success("آدرس با موفقیت حذف شد");
if (selectedAddressId === addressId) {
- setSelectedAddressId(null); // ریست کردن آدرس انتخاب شده اگر همان آدرس حذف شود
+ setSelectedAddressId(null);
}
setDeletingId(null);
},
@@ -278,59 +408,17 @@ export default function CheckoutPage() {
{/* لیست آدرسهای موجود */}
- {addressesData?.addresses?.map((addr) => (
-
(
+
setSelectedAddressId(addr.id)}
- className={`relative flex flex-col sm:flex-row justify-between p-5 rounded-2xl border-2 cursor-pointer transition-all ${
- selectedAddressId === addr.id
- ? "border-salona-500 bg-salona-50/30"
- : "border-gray-100 hover:border-salona-200"
- }`}
- >
-
- {selectedAddressId === addr.id && (
-
- )}
-
{addr.address}
-
-
- کد پستی: {addr.postal_code}
-
-
-
-
- {/* اکشنهای ویرایش و حذف */}
-
-
-
-
-
+ addr={addr}
+ selectedAddressId={selectedAddressId}
+ setSelectedAddressId={setSelectedAddressId}
+ handleOpenEditForm={handleOpenEditForm}
+ handleDeleteAddress={handleDeleteAddress}
+ isDeletingAddress={isDeletingAddress}
+ deletingId={deletingId}
+ />
))}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 45a7d5e..fe16d53 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -6,7 +6,7 @@ import { ArrowLeft, Sparkles, TrendingUp } from "lucide-react";
import { useCategories } from "@/services/useCategories";
import { useProducts } from "@/services/useProducts";
import { useFavorites } from "@/services/useFavorites"; // اضافه شدن هوک علاقهمندیها
-import { ProductCard } from "@/components/products/ProductCard";
+import { ProductCard } from "@/components/shared/ProductsCard";
import { Category, Product } from "@/types/api";
export default function HomePage() {
@@ -16,14 +16,16 @@ export default function HomePage() {
per_page: 8,
});
+ const user = JSON.parse(localStorage.getItem('user'))
+
// دریافت لیست علاقهمندیها برای تطبیق با محصولات صفحه اصلی
- const { data: favoritesData } = useFavorites();
+ const { data: favoritesData } = useFavorites(!!user);
return (
{/* 1. Hero Section */}
-
+
جشنواره بهاره سالونا
@@ -45,7 +47,8 @@ export default function HomePage() {
-
+ {/* bg-linear-to-tr from-salona-200 to-salona-100 */}
+
diff --git a/src/app/products/[id]/page.tsx b/src/app/products/[id]/page.tsx
index 967bcfb..740e109 100644
--- a/src/app/products/[id]/page.tsx
+++ b/src/app/products/[id]/page.tsx
@@ -16,37 +16,40 @@ import {
Loader2, // اضافه شدن لودر برای دکمه قلب
} from "lucide-react";
import { useParams, useRouter } from "next/navigation";
-import { useState } from "react";
+import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import type { Product } from "@/types/api";
import { addToCart, removeFromCart } from "@/store/slices/cartSlice";
// پیشنهاد: برای پرفورمنس بهتر در Next.js از next/image استفاده کنید
import Image from "next/image";
+import ProductComments from "@/components/shared/ProductComments";
export default function ProductDetailPage() {
const params = useParams();
const router = useRouter();
const productId = Number(params.id);
- // راهاندازی Redux
const dispatch = useDispatch();
- // دریافت اطلاعات محصول در سبد خرید از استیت گلوبال
const cartItem = useSelector((state: any) => state.cart.items.find((item: any) => item.id === String(productId)));
const quantityInCart = cartItem ? cartItem.quantity : 0;
- // واکشی اطلاعات محصول از API
const { data: response, isLoading, isError } = useProductDetail(productId);
//@ts-ignore
const product: Product | undefined = response?.product;
- // --- بخش مربوط به علاقهمندیها (Favorites) ---
- const { data: favoritesData } = useFavorites();
+ // دریافت وضعیت کاربر به صورت ایمن
+ const [user, setUser] = useState
(null);
+ useEffect(() => {
+ const storedUser = localStorage.getItem("user");
+ if (storedUser) setUser(JSON.parse(storedUser));
+ }, []);
+
+ const { data: favoritesData } = useFavorites(!!user);
const { mutate: addFavorite, isPending: isAddingFavorite } = useAddFavorite();
const { mutate: deleteFavorite, isPending: isDeletingFavorite } = useDeleteFavorite();
- // بررسی اینکه آیا این محصول در لیست علاقهمندیهای کاربر وجود دارد یا خیر
const favoriteRecord = favoritesData?.favorites?.find(
(fav: any) => fav.product_id === product?.id || (fav.product && fav.product.id === product?.id),
);
@@ -54,22 +57,17 @@ export default function ProductDetailPage() {
const favoriteId = favoriteRecord?.id;
const isFavoriteLoading = isAddingFavorite || isDeletingFavorite;
- // تابع هندل کردن کلیک روی دکمه قلب
const handleToggleFavorite = () => {
if (!product) return;
-
if (isFavorited && favoriteId) {
deleteFavorite(favoriteId);
} else {
addFavorite(product.id);
}
};
- // ----------------------------------------------
- // استیت برای مدیریت تصویری که کاربر در گالری انتخاب کرده است
const [selectedImage, setSelectedImage] = useState(0);
- // توابع مدیریت سبد خرید
const handleAddToCart = () => {
if (!product) return;
dispatch(
@@ -87,7 +85,6 @@ export default function ProductDetailPage() {
dispatch(removeFromCart(String(productId)));
};
- // کامپوننت اسکلتون برای حالت لودینگ
if (isLoading) {
return (
@@ -115,7 +112,7 @@ export default function ProductDetailPage() {
if (isError || !product) {
return (
-
+
محصول مورد نظر یافت نشد!