diff --git a/public/assets/images/logo/logo.png b/public/assets/images/logo/logo.png
index 4798681..b8c5195 100644
Binary files a/public/assets/images/logo/logo.png and b/public/assets/images/logo/logo.png differ
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
new file mode 100644
index 0000000..a24f702
--- /dev/null
+++ b/src/app/about/page.tsx
@@ -0,0 +1,183 @@
+import Image from "next/image";
+import Link from "next/link";
+import { CalendarDays, ShoppingBag, Sparkles, ShieldCheck, Clock, HeartHandshake } from "lucide-react";
+
+export const metadata = {
+ title: "درباره سالونا | رزرو آرایشگاه و فروشگاه آرایشی",
+ description:
+ "سالونا، همراه شما در مسیر زیبایی. پلتفرم جامع رزرو آنلاین نوبت آرایشگاههای زنانه و فروشگاه تخصصی محصولات آرایشی و بهداشتی.",
+};
+
+export default function AboutPage() {
+ return (
+ // تخصیص فونت وزیرمتن و راستچین کردن کل صفحه
+
+ {/* بخش هیرو (Hero Section) */}
+
+
+
+
+ داستان سالونا
+
+
+ تلاقی هنر زیبایی و
+ تکنولوژی مدرن
+
+
+ سالونا تنها یک وبسایت نیست؛ یک همراه همیشگی برای بانوانی است که به زمان و زیبایی خود اهمیت
+ میدهند. ما با درک دغدغههای روزمره شما، پلتفرمی را خلق کردهایم که نهتنها فرآیند رزرو نوبت
+ در بهترین آرایشگاههای زنانه را بیدردسر میکند، بلکه دسترسی به مرغوبترین محصولات آرایشی و
+ بهداشتی را در یک مکان فراهم آورده است.
+
+
+
+
+ رزرو نوبت
+
+
+
+ فروشگاه محصولات
+
+
+
+
+
+ {/* در پروژه واقعی از عکس با کیفیت خودتان استفاده کنید */}
+
+
+
+
سالونا
+
تجربه متفاوت زیبایی
+
+
+
+
+
+ {/* بخش خدمات اصلی (Core Services) */}
+
+
+
+
+ ما چه کاری انجام میدهیم؟
+
+
+ سالونا دو بال قدرتمند برای پرواز به سوی زیبایی دارد. ما خدمات حضوری و محصولات خانگی را به
+ یکدیگر پیوند دادهایم.
+
+
+
+
+ {/* کارت رزرو */}
+
+
+
+
+
مدیریت نوبتدهی آرایشگاهها
+
+ دیگر نیازی به تماسهای مکرر و هماهنگیهای زمانبر نیست. با سالونا میتوانید تقویم کاری
+ بهترین سالنهای زیبایی را مشاهده کنید، نمونه کارها را ببینید و در ساعت دلخواه خود،
+ نوبتتان را قطعی کنید.
+
+
+
+ {/* کارت فروشگاه */}
+
+
+
+
+
فروشگاه تخصصی محصولات
+
+ مراقبت از زیبایی پس از خروج از سالن متوقف نمیشود. ما مجموعهای از بهترین و معتبرترین
+ برندهای آرایشی، مراقبت از پوست و مو را با ضمانت اصالت کالا برای شما گردآوری کردهایم.
+
+
+
+
+
+
+ {/* بخش ارزشهای کلیدی (Core Values) */}
+
+
+
چرا سالونا را انتخاب کنید؟
+
+
+
+
+ {[
+ {
+ icon:
,
+ title: "صرفهجویی در زمان",
+ desc: "رزرو سریع در کمتر از ۲ دقیقه و ارسال فوری محصولات به سراسر کشور.",
+ },
+ {
+ icon:
,
+ title: "تضمین اصالت",
+ desc: "تمامی محصولات فروشگاه و خدمات سالنها مورد تایید و دارای ضمانت هستند.",
+ },
+ {
+ icon:
,
+ title: "تنوع بینظیر",
+ desc: "دسترسی به صدها سالن زیبایی و هزاران محصول آرایشی در یک پلتفرم.",
+ },
+ {
+ icon:
,
+ title: "پشتیبانی دلسوزانه",
+ desc: "تیم پشتیبانی سالونا در تمام مراحل رزرو و خرید در کنار شماست.",
+ },
+ ].map((item, index) => (
+
+
+ {item.icon}
+
+
{item.title}
+
{item.desc}
+
+ ))}
+
+
+
+ {/* بخش دعوت به اقدام (Call to Action) */}
+
+
+ {/* افکتهای پسزمینه تزئینی */}
+
+
+
+
+ آمادهاید تا سبک جدیدی از زیبایی را تجربه کنید؟
+
+
+ همین حالا به جمع هزاران کاربر سالونا بپیوندید. نوبت خود را رزرو کنید یا سبد خریدتان را از
+ محصولات شگفتانگیز پر کنید.
+
+
+
+ عضویت در سالونا
+
+
+ تماس با ما
+
+
+
+
+
+ );
+}
diff --git a/src/app/categories/[id]/page.tsx b/src/app/categories/[id]/page.tsx
new file mode 100644
index 0000000..5664723
--- /dev/null
+++ b/src/app/categories/[id]/page.tsx
@@ -0,0 +1,157 @@
+"use client";
+
+import { use } from "react";
+import Link from "next/link";
+import { ChevronLeft, SlidersHorizontal, PackageX } from "lucide-react";
+import { useCategories } from "@/services/useCategories";
+import { useProducts } from "@/services/useProducts";
+import { useFavorites } from "@/services/useFavorites";
+import { ProductCard } from "@/components/shared/ProductsCard";
+import { Category, Product } from "@/types/api";
+
+// تابع کمکی برای پیدا کردن یک دسته خاص در ساختار درختی
+const findCategoryById = (categories: Category[], id: number): Category | null => {
+ for (const cat of categories) {
+ if (cat.id === id) return cat;
+ if (cat.children && cat.children.length > 0) {
+ const found = findCategoryById(cat.children, id);
+ if (found) return found;
+ }
+ }
+ return null;
+};
+
+export default function SingleCategoryPage({ params }: { params: Promise<{ id: string }> }) {
+ // باز کردن params در Next.js 15+ (اگر نسخه قدیمیتر است Promise نیاز نیست)
+ const resolvedParams = use(params);
+ const categoryId = parseInt(resolvedParams.id);
+
+ const user = typeof window !== "undefined" ? JSON.parse(localStorage.getItem("user") || "null") : null;
+
+ // دریافت کل دستهها برای پیدا کردن دسته فعلی
+ const { data: categoriesData, isLoading: isCatLoading } = useCategories();
+ const { data: favoritesData } = useFavorites(!!user);
+
+ // دریافت محصولات مختص این دسته
+ const { data: productsData, isLoading: isProdLoading } = useProducts({
+ category_id: categoryId,
+ per_page: 20,
+ });
+
+ const currentCategory = categoriesData?.categories ? findCategoryById(categoriesData.categories, categoryId) : null;
+
+ if (isCatLoading) {
+ return
در حال بارگذاری...
;
+ }
+
+ if (!currentCategory && !isCatLoading) {
+ return دستهبندی مورد نظر یافت نشد.
;
+ }
+
+ // جدا کردن مسیرها برای Breadcrumb (مثلا "محصولات مراقبتی > پوست" تبدیل به آرایه میشود)
+ const breadcrumbs = currentCategory?.full_path.split(" > ") || [];
+
+ return (
+
+ {/* بخش هدر دستهبندی */}
+
+
+ {/* مسیر راهنما (Breadcrumbs) */}
+
+
+ خانه
+
+
+
+ دستهبندیها
+
+ {breadcrumbs.map((crumb, index) => (
+
+
+
+ {crumb}
+
+
+ ))}
+
+
+
+ {currentCategory?.name}
+
+
+ {/* نمایش زیردستهها در صورت وجود */}
+ {currentCategory?.children && currentCategory.children.length > 0 && (
+
+ {currentCategory.children.map((child: Category) => (
+
+ {child.name}
+
+ ))}
+
+ )}
+
+
+
+ {/* بخش محصولات */}
+
+
+
+ {isProdLoading ? "در حال جستجو..." : `${productsData?.products?.length || 0} کالا یافت شد`}
+
+
+
+ مرتبسازی
+
+
+
+ {isProdLoading ? (
+ // اسکلتون لودینگ محصولات
+
+ {Array.from({ length: 8 }).map((_, i) => (
+
+ ))}
+
+ ) : productsData?.products?.length > 0 ? (
+ // گرید محصولات
+
+ {productsData.products.map((product: Product) => {
+ const favoriteRecord = favoritesData?.favorites?.find(
+ (fav) => fav.product_id === product.id,
+ );
+ return (
+
+ );
+ })}
+
+ ) : (
+ // حالت خالی (Empty State)
+
+
+
محصولی یافت نشد!
+
در حال حاضر محصولی در این دستهبندی وجود ندارد.
+
+ بازگشت به همه دستهبندیها
+
+
+ )}
+
+
+ );
+}
diff --git a/src/app/categories/page.tsx b/src/app/categories/page.tsx
new file mode 100644
index 0000000..fd20ca8
--- /dev/null
+++ b/src/app/categories/page.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import Link from "next/link";
+import { Layers, ChevronLeft, Sparkles } from "lucide-react";
+import { useCategories } from "@/services/useCategories";
+import { Category } from "@/types/api";
+
+export default function AllCategoriesPage() {
+ const { data, isLoading } = useCategories();
+
+ if (isLoading) {
+ return (
+
+ {[1, 2, 3].map((i) => (
+
+ ))}
+
+ );
+ }
+
+ // جدا کردن دستههای سطح صفر (اصلی)
+ const rootCategories = data?.categories?.filter((cat: Category) => cat.level === 0) || [];
+
+ return (
+
+
+
+
+
+
+
دستهبندی محصولات
+
+ کشف زیبایی در میان {data?.total_categories} دستهبندی متنوع سالونا
+
+
+
+
+
+ {rootCategories.map((rootCategory: Category) => (
+
+
+
+
+ {rootCategory.name}
+
+
+ مشاهده همه
+
+
+
+ {/* نمایش زیردستهها (فرزندان) */}
+ {rootCategory.children && rootCategory.children.length > 0 ? (
+
+ {rootCategory.children.map((child: Category) => (
+
+
+
+ {child.name}
+
+
+
+
+ ))}
+
+ ) : (
+
+ بدون زیردسته
+
+ )}
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index fe16d53..78eab6b 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -110,7 +110,7 @@ export default function HomePage() {
className="text-sm font-medium text-salona-500 hover:text-salona-600 flex items-center gap-1"
>
مشاهده همه
-
+
diff --git a/src/components/auth/AuthForm.tsx b/src/components/auth/AuthForm.tsx
index a1d7c7a..dbc6af6 100644
--- a/src/components/auth/AuthForm.tsx
+++ b/src/components/auth/AuthForm.tsx
@@ -42,10 +42,7 @@ export default function AuthForm() {
const { mutate: registerUser, isPending: isRegisterPending } = useRegister();
const { mutate: verifyOtp, isPending: isVerifyPending } = useVerifyLogin();
- const {
- register,
- handleSubmit,
- } = useForm();
+ const { register, handleSubmit } = useForm();
useEffect(() => {
let timer: NodeJS.Timeout;
@@ -115,7 +112,7 @@ export default function AuthForm() {
if (!token) throw new Error("توکنی از سمت سرور دریافت نشد");
await setAuthCookie(token);
- localStorage.setItem('user',JSON.stringify(data))
+ localStorage.setItem("user", JSON.stringify(data));
toast.success(
mode === "REGISTER"
@@ -124,7 +121,7 @@ export default function AuthForm() {
);
router.push("/");
- router.refresh();
+ router.refresh();
} catch (err) {
toast.error("خطا در ذخیرهسازی اطلاعات ورود");
console.error(err);
@@ -276,7 +273,9 @@ export default function AuthForm() {
value={otpCode}
onChange={setOtpCode}
render={({ slots }) => (
- {/* اصلاح فاصله برای جاگیری بهتر ۶ رقم */}
+
+ {" "}
+ {/* اصلاح فاصله برای جاگیری بهتر ۶ رقم */}
{slots.map((slot, idx) => (
{
@@ -60,10 +66,13 @@ export const Header = () => {
const [isProfileDropdownOpen, setIsProfileDropdownOpen] = useState(false);
const dropdownRef = useRef
(null);
+ // استیت و رفرنس برای مودال عکس پروفایل
+ const [isProfilePicModalOpen, setIsProfilePicModalOpen] = useState(false);
+ const fileInputRef = useRef(null);
+
// استیت برای حل مشکل Hydration سبد خرید
const [isMounted, setIsMounted] = useState(false);
- // دریافت اطلاعات کاربر از لوکال استوریج به صورت امن
const getUserFromStorage = () => {
if (typeof window !== "undefined") {
const storedUser = localStorage.getItem("user");
@@ -71,32 +80,36 @@ export const Header = () => {
}
return null;
};
-
+
const user = getUserFromStorage();
- // دریافت اطلاعات کاربر از هوک اختصاصی
const { data, isLoading: isUserLoading } = useUserProfile(!!user);
//@ts-ignore
const userData: UserProfile | null = data?.user || null;
- // دریافت لیست محصولات سبد خرید از Redux
const cartItems = useSelector((state: any) => state.cart.items);
const uniqueProductCount = cartItems?.length || 0;
+ // فراخوانی هوکهای عکس پروفایل (خطاهای احتمالی مسیر ایمپورت را برطرف کنید)
+ // در صورتی که ایمپورت بالا را انجام دادهاید، کامنت این دو خط را بردارید:
+ const { mutate: uploadPicture, isPending: isUploading } = useUploadProfilePicture();
+ const { mutate: deletePicture, isPending: isDeleting } = useDeleteProfilePicture();
+
+ // مقادیر فرضی موقت در صورت عدم وجود ایمپورت برای جلوگیری از ارور (بعد از ایمپورت، این 4 خط را پاک کنید)
+ // const uploadPicture = (file: File, options: any) => options?.onSuccess();
+ // const deletePicture = (data: any, options: any) => options?.onSuccess();
+ // const isUploading = false;
+ // const isDeleting = false;
useEffect(() => {
setIsMounted(true);
-
- // منطق اسکرول هدر
const handleScroll = () => {
- setIsScrolled(window.scrollY > 20);
+ setIsScrolled(window.scrollY > 0);
};
window.addEventListener("scroll", handleScroll);
-
return () => window.removeEventListener("scroll", handleScroll);
}, []);
- // مدیریت کلیک خارج از دراپدون پروفایل
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
@@ -107,45 +120,34 @@ export const Header = () => {
if (isProfileDropdownOpen) {
document.addEventListener("mousedown", handleClickOutside);
}
-
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isProfileDropdownOpen]);
- // متد خروج از حساب کاربری
const handleLogout = async () => {
- // بستن منوها
setIsProfileDropdownOpen(false);
setIsMobileMenuOpen(false);
- // نمایش پاپآپ تاییدیه
const result = await Swal.fire({
title: "آیا مطمئن هستید؟",
text: "میخواهید از حساب کاربری خود خارج شوید؟",
icon: "warning",
showCancelButton: true,
- confirmButtonColor: "#ef4444",
- cancelButtonColor: "#9ca3af",
+ confirmButtonColor: "#ef4444",
+ cancelButtonColor: "#9ca3af",
confirmButtonText: "بله، خارج میشوم",
cancelButtonText: "انصراف",
- reverseButtons: true,
+ reverseButtons: true,
});
if (result.isConfirmed) {
try {
- // ۱. پاک کردن توکن از کوکی (فراخوانی Server Action)
await removeAuthCookie();
-
- // ۲. پاک کردن کش اطلاعات کاربر از React Query و لوکال استوریج
queryClient.removeQueries({ queryKey: ["userProfile"] });
localStorage.removeItem("user");
-
- // ۳. رفرش کردن دیتای Next.js و انتقال به صفحه اصلی
router.refresh();
router.push("/");
-
- // نمایش پیام موفقیت
Swal.fire({
title: "موفق",
text: "با موفقیت از حساب کاربری خارج شدید.",
@@ -153,7 +155,6 @@ export const Header = () => {
timer: 1500,
showConfirmButton: false,
});
-
} catch (error) {
Swal.fire({
title: "خطا",
@@ -165,321 +166,462 @@ export const Header = () => {
}
};
- // تعیین عبارتی که باید به کاربر نمایش داده شود
- const displayName = userData?.name || userData?.phone_number;
+ // هندلر تغییر فایل و آپلود
+ const handleFileChange = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (file) {
+ uploadPicture(file, {
+ onSuccess: () => {
+ setIsProfilePicModalOpen(false);
+ // رفرش کردن دیتای کاربر برای دریافت عکس جدید
+ queryClient.invalidateQueries({ queryKey: ["userProfile"] });
+ },
+ });
+ // پاک کردن مقدار اینپوت برای اینکه انتخاب مجدد همان فایل ممکن باشد
+ if (fileInputRef.current) fileInputRef.current.value = "";
+ }
+ };
- // متغیری برای بررسی وضعیت کلی بارگذاری بخش کاربری
+ // هندلر حذف عکس
+ const handleDeletePicture = () => {
+ deletePicture(undefined, {
+ onSuccess: () => {
+ setIsProfilePicModalOpen(false);
+ // رفرش کردن دیتای کاربر
+ queryClient.invalidateQueries({ queryKey: ["userProfile"] });
+ },
+ });
+ };
+
+ const displayName = userData?.name || userData?.phone_number;
const isProfileSectionLoading = !isMounted || isUserLoading;
return (
-
-
-
-
- {/* بخش راست: لوگو و منوی دسکتاپ */}
-
- {/* منوی موبایل و لوگو */}
-
-
setIsMobileMenuOpen(true)}
- aria-label="باز کردن منو"
- >
-
-
- {/*
-
- */}
-
- SALONA
.
-
+ <>
+