login , register api / category provider api
This commit is contained in:
@@ -5,6 +5,7 @@ import Footer from "@/components/footer";
|
|||||||
import localFont from 'next/font/local';
|
import localFont from 'next/font/local';
|
||||||
import { CartProvider } from "@/components/context/cartcontext";
|
import { CartProvider } from "@/components/context/cartcontext";
|
||||||
import { categoryService } from "@/public/src/services/categories/api";
|
import { categoryService } from "@/public/src/services/categories/api";
|
||||||
|
import { CategoryProvider } from "@/components/context/categoryprovider";
|
||||||
|
|
||||||
const Yekanbakh = localFont({
|
const Yekanbakh = localFont({
|
||||||
src: [
|
src: [
|
||||||
@@ -35,8 +36,12 @@ export default async function RootLayout({
|
|||||||
<html lang="fa" dir="rtl" className={Yekanbakh.variable}>
|
<html lang="fa" dir="rtl" className={Yekanbakh.variable}>
|
||||||
<body>
|
<body>
|
||||||
<CartProvider>
|
<CartProvider>
|
||||||
<Header categories={categories}/>
|
<CategoryProvider>
|
||||||
|
|
||||||
|
<Header />
|
||||||
{children}
|
{children}
|
||||||
|
</CategoryProvider>
|
||||||
|
|
||||||
</CartProvider>
|
</CartProvider>
|
||||||
|
|
||||||
<Footer/>
|
<Footer/>
|
||||||
|
|||||||
82
app/page.tsx
82
app/page.tsx
@@ -1,13 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Headphones, FileText, Truck, ShieldCheck, Square, MoreVertical, Circle, Target, CookingPot, Settings, CircleDashed, Disc, Hexagon, Droplets, Wrench, Minus, MessageCircleCheckIcon, } from "lucide-react";
|
import {
|
||||||
|
Headphones, FileText, Truck, ShieldCheck, CircleDashed,
|
||||||
|
Disc,
|
||||||
|
Hexagon,
|
||||||
|
Settings,
|
||||||
|
Wrench,
|
||||||
|
Droplets, Square, MoreVertical, Circle, Target, CookingPot, Minus, MessageCircleCheckIcon,
|
||||||
|
} from "lucide-react";
|
||||||
import ProductCard from "@/components/productcard";
|
import ProductCard from "@/components/productcard";
|
||||||
import ArticleCard from "@/components/articlecard";
|
import ArticleCard from "@/components/articlecard";
|
||||||
import FAQItem from "@/components/faq";
|
import FAQItem from "@/components/faq";
|
||||||
import { products } from "@/lib/data";
|
import { products } from "@/lib/data";
|
||||||
import { articles } from "@/lib/data";
|
import { articles } from "@/lib/data";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useCategories } from "@/components/context/categoryprovider";
|
||||||
|
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
@@ -60,28 +67,35 @@ export default function Home() {
|
|||||||
const [activeTab, setActiveTab] = useState(0);
|
const [activeTab, setActiveTab] = useState(0);
|
||||||
const brands = ["NTN", "KOYO", "NACHI", "TIMKEN", "FAG", "SKF"];
|
const brands = ["NTN", "KOYO", "NACHI", "TIMKEN", "FAG", "SKF"];
|
||||||
const latestArticles = articles.slice(-4);
|
const latestArticles = articles.slice(-4);
|
||||||
|
const { rootCategories } = useCategories();
|
||||||
|
|
||||||
const uniqueCategories = Array.from(new Set(products.map((p) => p.category)));
|
// const uniqueCategories = Array.from(new Set(products.map((p) => p.category)));
|
||||||
const getCategoryIcon = (categoryName: string) => {
|
// const getCategoryIcon = (categoryName: string) => {
|
||||||
switch (categoryName) {
|
// switch (categoryName) {
|
||||||
case "شیار عمیق":
|
// case "شیار عمیق":
|
||||||
return CircleDashed;
|
// return CircleDashed;
|
||||||
case "مخروطی":
|
// case "مخروطی":
|
||||||
return Disc;
|
// return Disc;
|
||||||
case "شبکه ای":
|
// case "شبکه ای":
|
||||||
return Hexagon;
|
// return Hexagon;
|
||||||
case "سوزنی":
|
// case "سوزنی":
|
||||||
return Settings;
|
// return Settings;
|
||||||
case "یاتاقان":
|
// case "یاتاقان":
|
||||||
return Wrench;
|
// return Wrench;
|
||||||
case "گریس و روانکار":
|
// case "گریس و روانکار":
|
||||||
return Droplets;
|
// return Droplets;
|
||||||
default:
|
// default:
|
||||||
return Settings; // آیکون پیشفرض
|
// return Settings; // آیکون پیشفرض
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
const categoryIcons = [
|
||||||
|
CircleDashed,
|
||||||
|
Disc,
|
||||||
|
Hexagon,
|
||||||
|
Wrench,
|
||||||
|
Droplets
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className=" bg-gray-50">
|
<main className=" bg-gray-50">
|
||||||
@@ -225,26 +239,26 @@ export default function Home() {
|
|||||||
|
|
||||||
|
|
||||||
{/* cards */}
|
{/* cards */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-6">
|
<div className="grid justify-center grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-6">
|
||||||
{uniqueCategories.map((category, index) => {
|
|
||||||
const Icon = getCategoryIcon(category);
|
|
||||||
|
|
||||||
// فقط فاصلهها را به خط تیره تبدیل میکنیم
|
|
||||||
const categorySlug = category.replace(/\s+/g, "-");
|
|
||||||
|
{rootCategories.map((cat, index) => {
|
||||||
|
const Icon = categoryIcons[index % categoryIcons.length];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`/category/${categorySlug}`}
|
key={cat.id}
|
||||||
key={index}
|
href={`/category/${cat.slug}`}
|
||||||
className="bg-white border border-gray-200 rounded-xl shadow-sm hover:shadow-md hover:border-blue-400 transition-all duration-300 flex flex-col items-center justify-center py-8 cursor-pointer group"
|
className="bg-white border gap-2 border-gray-200 rounded-xl shadow-sm hover:shadow-md hover:border-blue-400 transition-all duration-300 flex flex-col items-center justify-center py-8 cursor-pointer group"
|
||||||
>
|
>
|
||||||
<Icon size={34} className="text-gray-400 mb-3 group-hover:text-blue-500 transition-colors" />
|
<Icon size={34} strokeWidth={1} className="text-gray-400 mb-3 group-hover:text-blue-500 transition-colors" />
|
||||||
<p className="text-sm font-medium text-gray-700 group-hover:text-blue-600">
|
<span className="text-sm font-medium text-gray-700 group-hover:text-blue-600">{cat.name}</span>
|
||||||
{category}
|
|
||||||
</p>
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
34
components/context/categoryprovider.tsx
Normal file
34
components/context/categoryprovider.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
import { getAllCategories } from "@/public/src/services/categories/store";
|
||||||
|
import { Category } from "@/public/src/types/categories";
|
||||||
|
|
||||||
|
interface CategoryContextType {
|
||||||
|
categories: Category[];
|
||||||
|
rootCategories: Category[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const CategoryContext = createContext<CategoryContextType>({
|
||||||
|
categories: [],
|
||||||
|
rootCategories: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
export function CategoryProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [categories, setCategories] = useState<Category[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getAllCategories().then((data) => setCategories(data));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const rootCategories = categories.filter((c) => !c.parent);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CategoryContext.Provider value={{ categories, rootCategories }}>
|
||||||
|
{children}
|
||||||
|
</CategoryContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCategories() {
|
||||||
|
return useContext(CategoryContext);
|
||||||
|
}
|
||||||
@@ -3,13 +3,13 @@ import React, { useState, useEffect, useRef } from 'react';
|
|||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { registerUser } from '@/public/src/services/auth/api';
|
|
||||||
import { Category } from '@/public/src/types/categories';
|
|
||||||
|
|
||||||
import { ShoppingCart, Trash2, Search, X, ChevronDown } from "lucide-react";
|
import { ShoppingCart, Trash2, Search, X, ChevronDown } from "lucide-react";
|
||||||
import { useCart } from './context/cartcontext';
|
import { useCart } from './context/cartcontext';
|
||||||
import { products } from '@/lib/data';
|
import { products } from '@/lib/data';
|
||||||
import '@/public/src/css/header.css';
|
import '@/public/src/css/header.css';
|
||||||
|
import { registerUser } from '@/public/src/services/auth/api';
|
||||||
|
import { loginUser } from '@/public/src/services/auth/api';
|
||||||
|
import { useCategories } from './context/categoryprovider';
|
||||||
|
|
||||||
const topBarLinks = [
|
const topBarLinks = [
|
||||||
{ label: "بخش صنعتی", href: "/" },
|
{ label: "بخش صنعتی", href: "/" },
|
||||||
@@ -25,7 +25,7 @@ const mainNavLinks = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
export function Header({ categories }: { categories: Category[] }) {
|
export function Header() {
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { cart, removeFromCart } = useCart();
|
const { cart, removeFromCart } = useCart();
|
||||||
@@ -34,54 +34,143 @@ export function Header({ categories }: { categories: Category[] }) {
|
|||||||
const [filteredProducts, setFilteredProducts] = useState<any[]>([]);
|
const [filteredProducts, setFilteredProducts] = useState<any[]>([]);
|
||||||
const searchRef = useRef<HTMLDivElement>(null);
|
const searchRef = useRef<HTMLDivElement>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const safeCategories = categories || [];
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [formType, setFormType] = useState("mobile");
|
const [formType, setFormType] = useState("mobile");
|
||||||
const [activeTab, setActiveTab] = useState("login");
|
const [activeTab, setActiveTab] = useState("login");
|
||||||
const [registerForm, setRegisterForm] = useState({
|
const [registerError, setRegisterError] = useState("");
|
||||||
phone: "",
|
const [loginError, setLoginError] = useState("");
|
||||||
|
const { rootCategories } = useCategories();
|
||||||
|
const [loginForm, setLoginForm] = useState({
|
||||||
username: "",
|
username: "",
|
||||||
firstName: "",
|
|
||||||
lastName: "",
|
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRegisterChange = (e: any) => {
|
const [registerForm, setRegisterForm] = useState({
|
||||||
const { name, value } = e.target;
|
phone: "",
|
||||||
|
fullName: "",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
setRegisterForm((prev) => ({
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
...prev,
|
setRegisterForm({
|
||||||
[name]: value,
|
...registerForm,
|
||||||
}));
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleRegister = async () => {
|
const handleRegister = async () => {
|
||||||
|
|
||||||
|
setRegisterError("");
|
||||||
|
|
||||||
|
const phone = registerForm.phone.trim();
|
||||||
|
const username = registerForm.username.trim();
|
||||||
|
const password = registerForm.password.trim();
|
||||||
|
const fullName = registerForm.fullName.trim();
|
||||||
|
|
||||||
|
// ✅ بررسی خالی بودن فیلدها
|
||||||
|
if (!phone || !username || !password || !fullName) {
|
||||||
|
setRegisterError("لطفاً فیلدها را پر کنید");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
phone: registerForm.phone,
|
phone,
|
||||||
username: registerForm.username,
|
username,
|
||||||
password: registerForm.password,
|
password,
|
||||||
|
fullName,
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await registerUser(payload);
|
const res = await registerUser(payload);
|
||||||
|
|
||||||
console.log("ثبت نام موفق", res);
|
localStorage.setItem("accessToken", res.data.accessToken);
|
||||||
|
localStorage.setItem("refreshToken", res.data.refreshToken);
|
||||||
|
|
||||||
} catch (err) {
|
setIsOpen(false);
|
||||||
console.log("خطا:", err);
|
router.push("/dashboard");
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
|
||||||
|
const message = error?.message?.toLowerCase() || "";
|
||||||
|
|
||||||
|
const usernameDuplicate = message.includes("username");
|
||||||
|
const phoneDuplicate = message.includes("phone");
|
||||||
|
|
||||||
|
if (usernameDuplicate && phoneDuplicate) {
|
||||||
|
setRegisterError("نام کاربری و شماره موبایل قبلاً ثبت شدهاند");
|
||||||
|
}
|
||||||
|
else if (usernameDuplicate) {
|
||||||
|
setRegisterError("این نام کاربری قبلاً ثبت شده است");
|
||||||
|
}
|
||||||
|
else if (phoneDuplicate) {
|
||||||
|
setRegisterError("این شماره موبایل قبلاً ثبت شده است");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setRegisterError("خطا در ثبت نام");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const rootCategories = safeCategories.filter(cat => cat.parent === null);
|
const handleLogin = async () => {
|
||||||
|
setLoginError("");
|
||||||
|
|
||||||
|
const username = loginForm.username.trim();
|
||||||
|
const password = loginForm.password.trim();
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
setLoginError("لطفاً نام کاربری و رمز عبور را وارد کنید");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await loginUser({ username, password });
|
||||||
|
|
||||||
|
localStorage.setItem("accessToken", res.data.accessToken);
|
||||||
|
localStorage.setItem("refreshToken", res.data.refreshToken);
|
||||||
|
setIsOpen(false);
|
||||||
|
router.push("/dashboard");
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
const msg = error?.message?.toLowerCase() || "";
|
||||||
|
|
||||||
|
console.log("LOGIN ERROR RAW:", msg); // این خیلی مهمه
|
||||||
|
|
||||||
|
// 1) اول بررسی کنیم که نام کاربری وجود ندارد
|
||||||
|
if (
|
||||||
|
msg.includes("not found") ||
|
||||||
|
msg.includes("user") && msg.includes("not") ||
|
||||||
|
msg.includes("username") && msg.includes("not")
|
||||||
|
) {
|
||||||
|
setLoginError("این نام کاربری وجود ندارد");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) سپس رمز عبور اشتباه
|
||||||
|
if (
|
||||||
|
msg.includes("password") ||
|
||||||
|
msg.includes("invalid") ||
|
||||||
|
msg.includes("incorrect")
|
||||||
|
) {
|
||||||
|
setLoginError("رمز عبور اشتباه است");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) خطای عمومی
|
||||||
|
setLoginError("خطا در ورود. لطفاً دوباره تلاش کنید.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const parsePrice = (priceStr?: string | null) => {
|
const parsePrice = (priceStr?: string | null) => {
|
||||||
if (!priceStr) return 0;
|
if (!priceStr) return 0;
|
||||||
return Number(priceStr.toString().replace(/,/g, ''));
|
return Number(priceStr.toString().replace(/,/g, ''));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const totalPrice = cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
|
const totalPrice = cart.reduce((total, item) => total + (parsePrice(item.price) * item.quantity), 0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -422,7 +511,7 @@ export function Header({ categories }: { categories: Category[] }) {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setActiveTab("register")}
|
onClick={() => setActiveTab("register")}
|
||||||
className={`flex-1 py-2 text-sm rounded-lg transition ${activeTab === "register"
|
className={`flex-1 cursor-pointer py-2 text-sm rounded-lg transition ${activeTab === "register"
|
||||||
? "bg-white shadow text-[#1A2332] font-semibold"
|
? "bg-white shadow text-[#1A2332] font-semibold"
|
||||||
: "text-gray-500"
|
: "text-gray-500"
|
||||||
}`}
|
}`}
|
||||||
@@ -467,19 +556,38 @@ export function Header({ categories }: { categories: Category[] }) {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
value={loginForm.username}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLoginForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
username: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
placeholder="نام کاربری"
|
placeholder="نام کاربری"
|
||||||
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
|
value={loginForm.password}
|
||||||
|
onChange={(e) =>
|
||||||
|
setLoginForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
password: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
placeholder="رمز عبور"
|
placeholder="رمز عبور"
|
||||||
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<button className="w-full cursor-pointer py-3.5 bg-[#ffb900] hover:bg-[#e5a600] text-[#1A2332] font-semibold rounded-2xl text-sm">
|
<button onClick={handleLogin} className="w-full cursor-pointer py-3.5 bg-[#ffb900] hover:bg-[#e5a600] text-[#1A2332] font-semibold rounded-2xl text-sm">
|
||||||
ورود
|
ورود
|
||||||
</button>
|
</button>
|
||||||
|
{loginError && (
|
||||||
|
<div className="text-red-500 text-sm text-center mt-2">
|
||||||
|
{loginError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setFormType("mobile")}
|
onClick={() => setFormType("mobile")}
|
||||||
@@ -495,40 +603,30 @@ export function Header({ categories }: { categories: Category[] }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ---------------- REGISTER ---------------- */}
|
{/* ---------------- REGISTER ---------------- */}
|
||||||
|
|
||||||
{activeTab === "register" && (
|
{activeTab === "register" && (
|
||||||
<div className="w-full space-y-4">
|
<div className="w-full space-y-4">
|
||||||
|
|
||||||
<input
|
<input
|
||||||
name="phone"
|
name="phone"
|
||||||
value={registerForm.phone}
|
value={registerForm.phone}
|
||||||
onChange={handleRegisterChange}
|
onChange={handleChange}
|
||||||
type="tel"
|
type="tel"
|
||||||
placeholder="شماره موبایل"
|
placeholder="شماره موبایل"
|
||||||
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
name="firstName"
|
name="fullName"
|
||||||
value={registerForm.firstName}
|
value={registerForm.fullName}
|
||||||
onChange={handleRegisterChange}
|
onChange={handleChange}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="نام"
|
placeholder="نام"
|
||||||
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
|
||||||
name="lastName"
|
|
||||||
value={registerForm.lastName}
|
|
||||||
onChange={handleRegisterChange}
|
|
||||||
type="text"
|
|
||||||
placeholder="نام خانوادگی"
|
|
||||||
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
|
||||||
/>
|
|
||||||
<input
|
<input
|
||||||
name="username"
|
name="username"
|
||||||
value={registerForm.username}
|
value={registerForm.username}
|
||||||
onChange={handleRegisterChange}
|
onChange={handleChange}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="نام کاربری"
|
placeholder="نام کاربری"
|
||||||
className='w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]'
|
className='w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]'
|
||||||
@@ -536,20 +634,19 @@ export function Header({ categories }: { categories: Category[] }) {
|
|||||||
<input
|
<input
|
||||||
name="password"
|
name="password"
|
||||||
value={registerForm.password}
|
value={registerForm.password}
|
||||||
onChange={handleRegisterChange}
|
onChange={handleChange} type="password"
|
||||||
type="password"
|
|
||||||
placeholder="رمز عبور"
|
placeholder="رمز عبور"
|
||||||
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<input
|
{/* <input
|
||||||
name="confirmPassword"
|
name="confirmPassword"
|
||||||
value={registerForm.confirmPassword}
|
value={registerForm.confirmPassword}
|
||||||
onChange={handleRegisterChange}
|
onChange={handleChange}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="تکرار رمز عبور"
|
placeholder="تکرار رمز عبور"
|
||||||
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
className="w-full px-4 py-3.5 bg-gray-50 border border-gray-200 rounded-2xl text-sm text-right focus:outline-none focus:border-[#ffb900]"
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleRegister}
|
onClick={handleRegister}
|
||||||
@@ -558,6 +655,11 @@ export function Header({ categories }: { categories: Category[] }) {
|
|||||||
ثبت نام
|
ثبت نام
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{registerError && (
|
||||||
|
<div className="text-red-500 text-sm text-center mt-2">
|
||||||
|
{registerError}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -590,21 +692,13 @@ export function Header({ categories }: { categories: Category[] }) {
|
|||||||
|
|
||||||
<div className="absolute top-[calc(100%+16px)] right-0 w-56 bg-white border border-gray-200 rounded-xl shadow-lg p-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 flex flex-col gap-1 z-50">
|
<div className="absolute top-[calc(100%+16px)] right-0 w-56 bg-white border border-gray-200 rounded-xl shadow-lg p-2 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200 flex flex-col gap-1 z-50">
|
||||||
|
|
||||||
{rootCategories.length > 0 ? (
|
|
||||||
rootCategories.map((category) => (
|
{rootCategories.map(cat => (
|
||||||
<Link
|
<Link className='className="block w-full text-right px-3 py-2 text-xs font-medium text-gray-600 hover:bg-gray-100 hover:text-blue-600 rounded-md transition-colors'
|
||||||
key={category.id}
|
href={`/category/${cat.slug}`} key={cat.id}>
|
||||||
href={`/category/${category.slug}`}
|
{cat.name}
|
||||||
className="block w-full text-right px-3 py-2 text-xs font-medium text-gray-600 hover:bg-gray-100 hover:text-blue-600 rounded-md transition-colors"
|
|
||||||
>
|
|
||||||
{category.name}
|
|
||||||
</Link>
|
</Link>
|
||||||
))
|
))}
|
||||||
) : (
|
|
||||||
<div className="text-center text-xs text-gray-500 py-2">
|
|
||||||
در حال بارگذاری...
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,55 @@
|
|||||||
import { API_BASE_URL } from "../config";
|
import { API_BASE_URL } from "../config";
|
||||||
|
|
||||||
export async function registerUser(data: any) {
|
export interface RegisterPayload {
|
||||||
const res = await fetch(`${API_BASE_URL}/auth/register/password`, {
|
phone: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
fullName: string;
|
||||||
|
}
|
||||||
|
export interface LoginPayload {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function registerUser(data: RegisterPayload) {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/auth/register/password`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
console.log( API_BASE_URL);
|
|
||||||
return res.json();
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.message || "Register failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export async function loginUser(data: LoginPayload) {
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`${API_BASE_URL}/auth/login/password`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(result.message || "Login failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,32 +1,18 @@
|
|||||||
// src/services/categories/categories.service.ts
|
import { API_BASE_URL } from "../config";
|
||||||
import { ApiResponse } from '../../types/global';
|
import { Category } from "../../types/categories";
|
||||||
import { Category } from '../../types/categories';
|
|
||||||
|
|
||||||
const BASE_URL = 'https://parsshop-back.mugit.ir';
|
|
||||||
|
|
||||||
export const categoryService = {
|
export const categoryService = {
|
||||||
/**
|
|
||||||
* دریافت تمام دستهبندیها با استفاده از کش Next.js
|
|
||||||
*/
|
|
||||||
getCategories: async (): Promise<Category[]> => {
|
getCategories: async (): Promise<Category[]> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${BASE_URL}/api/categories`, {
|
const response = await fetch(`${API_BASE_URL}/categories`, {
|
||||||
// بهینهترین حالت: دادهها تا ۱ ساعت کش میشوند (3600 ثانیه)
|
|
||||||
// اگر تغییرات شما لحظهای است، این عدد را کمتر کنید یا از تگهای On-Demand Revalidation استفاده کنید
|
|
||||||
next: { revalidate: 3600 },
|
next: { revalidate: 3600 },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
const json = await response.json();
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
return json.data || [];
|
||||||
}
|
|
||||||
|
|
||||||
const result: ApiResponse<Category[]> = await response.json();
|
|
||||||
|
|
||||||
// ما فقط به آرایه data نیاز داریم
|
|
||||||
return result.data;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching categories:', error);
|
console.error("Category API Error:", error);
|
||||||
return []; // در صورت خطا، یک آرایه خالی برمیگردانیم تا صفحه کرش نکند
|
return [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
19
public/src/services/categories/store.tsx
Normal file
19
public/src/services/categories/store.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Category } from "../../types/categories";
|
||||||
|
import { categoryService } from "./api";
|
||||||
|
|
||||||
|
|
||||||
|
let cachedCategories: Category[] | null = null;
|
||||||
|
|
||||||
|
export async function getAllCategories(): Promise<Category[]> {
|
||||||
|
if (cachedCategories) {
|
||||||
|
return cachedCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
const categories = await categoryService.getCategories();
|
||||||
|
cachedCategories = categories;
|
||||||
|
return categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearCategoryCache() {
|
||||||
|
cachedCategories = null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user