finnal pages

This commit is contained in:
soheilGh 2025-12-24 16:00:30 +03:30
parent 872a98fd9f
commit e72cd844f5
7 changed files with 666 additions and 531 deletions

View File

@ -1,11 +1,9 @@
import "./styles/App.css"; import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import Layout from "./components/layout/layout"; import Login, { ProtectedRoute } from './pages/Login/Login';
import Login from "./pages/Login/Login"; import Layout from './components/Layout/Layout';
import Dashboard from "./pages/Dashboard/Dashboard"; import Dashboard from './pages/Dashboard/Dashboard';
import CafeManagement from "./pages/CafeManagement/CafeManagement"; import CafeManagement from './pages/CafeManagement/CafeManagement';
import EditCafe from "./pages/CafeManagement/EditCafe"; import EditCafe from './pages/CafeManagement/EditCafe';
import Stats from "./pages/Stats/Stats";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
function App() { function App() {
return ( return (
@ -13,17 +11,23 @@ function App() {
<Routes> <Routes>
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/" element={<Layout />}> <Route
<Route index element={<Navigate to="/dashboard" replace />} /> path="/"
element={
<ProtectedRoute>
<Layout />
</ProtectedRoute>
}
>
<Route path="dashboard" element={<Dashboard />} /> <Route path="dashboard" element={<Dashboard />} />
<Route path="cafe-management" element={<CafeManagement />} /> <Route path="cafe-management" element={<CafeManagement />} />
<Route path="edit-cafe/:id" element={<EditCafe />} /> <Route path="edit-cafe/:id" element={<EditCafe />} />
<Route path="stats" element={<div>صفحه آمار و تحلیل</div>} />
<Route path="stats" element={<Stats />} /> <Route index element={<Navigate to="/dashboard" replace />} />
</Route> </Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
); );

View File

@ -1,41 +1,47 @@
import { Outlet, useLocation } from "react-router-dom"; import { Outlet, useLocation } from "react-router-dom";
import Sidebar from "./sidebar"; import Sidebar from "./Sidebar";
import Header from "./header"; import Header from "./Header";
export default function Layout() { export default function Layout() {
const location = useLocation(); const location = useLocation();
const path = location.pathname; const path = location.pathname;
// لیست الگوهایی که باید هدر نشون داده بشه // لیست الگوهایی که باید هدر نشون داده بشه
const headerPaths = [ const headerPaths = [
"/management", "/management",
"/management/", "/management/",
"/management/cafes", "/management/cafes",
"/edit-cafe", "/edit-cafe",
"/edit-cafe/", "/edit-cafe/",
"/cafe", "/cafe",
"/cafes", "/cafes",
"/admin", "/admin",
]; ];
// بررسی: اگر هرکدوم از الگوها داخل path بود، هدر نمایش داده میشه // بررسی: اگر هرکدوم از الگوها داخل path بود، هدر نمایش داده میشه
const showHeader = headerPaths.some( const showHeader = headerPaths.some(
(p) => path.startsWith(p) || path.includes(p) (p) => path.startsWith(p) || path.includes(p)
); );
return ( return (
<div className="flex flex-row h-screen bg-[#FDF8F4]"> <div className="flex flex-row min-h-screen bg-[#FDF8F4]">
<div className="fixed right-0 top-0 bottom-0 z-40"> {/* Sidebar - در موبایل overlay، در دسکتاپ ثابت */}
<Sidebar /> <Sidebar />
</div>
<div className="flex-1 flex flex-col mr-[220px] h-screen"> {/* Main Content */}
{showHeader && <Header title="مدیریت کافه‌ها" />} <div className="flex-1 flex flex-col min-h-screen w-full lg:mr-[220px]">
{showHeader && (
<div className="fixed top-0 right-0 left-0 lg:right-[220px] z-30 bg-[#FDF8F4]">
<Header title="مدیریت کافه‌ها" />
</div>
)}
<main className="flex-1 overflow-y-auto p-6 bg-[#FDF8F4] pt-[80px]"> <main className={`flex-1 w-full overflow-x-hidden p-4 md:p-6 bg-[#FDF8F4] ${showHeader ? 'pt-20 lg:pt-24' : 'pt-16 lg:pt-6'}`}>
<Outlet /> <div className="max-w-full mx-auto">
</main> <Outlet />
</div> </div>
</div> </main>
); </div>
} </div>
);
}

View File

@ -1,109 +1,172 @@
import React from "react"; import React, { useState } from "react";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import LogoDM from "../../assets/icons/LogoDM.svg"; import LogoDM from "../../assets/icons/LogoDM.svg";
import { BiBarChartAlt2 } from "react-icons/bi"; import { BiBarChartAlt2 } from "react-icons/bi";
import { AiOutlinePieChart } from "react-icons/ai"; import { AiOutlinePieChart } from "react-icons/ai";
import { PiCoffee } from "react-icons/pi"; import { PiCoffee } from "react-icons/pi";
import { HiOutlineLogout } from "react-icons/hi"; import { HiOutlineLogout } from "react-icons/hi";
import { FiMenu, FiX } from "react-icons/fi";
const Sidebar = () => { const Sidebar = () => {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const [isOpen, setIsOpen] = useState(false);
// چک کردن صفحه فعال
const isActive = (path) => location.pathname === path; const isActive = (path) => location.pathname === path;
// تابع خروج
const handleLogout = () => { const handleLogout = () => {
localStorage.removeItem("token"); try {
navigate("/login"); localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('token');
localStorage.removeItem('adminInfo');
console.log('Tokens cleared from localStorage');
console.log('Current token:', localStorage.getItem('token'));
window.location.href = '/login';
} catch (error) {
console.error('Logout error:', error);
window.location.href = '/login';
}
}; };
const closeSidebar = () => setIsOpen(false);
return ( return (
<aside <>
dir="rtl" <button
className="fixed right-0 top-0 z-50 flex h-screen w-[220px] flex-col bg-[#EFEEEE] rounded-tl-3xl rounded-bl-3xl shadow-xl border-l border-[#D9CAB3]" onClick={() => setIsOpen(!isOpen)}
> className="sm:hidden fixed top-3 right-3 z-[70] bg-[#7F4629] text-white p-2.5 rounded-lg shadow-lg"
<div className="flex justify-center mt-6"> aria-label="Toggle menu"
<img src={LogoDM} className="h-12 w-12" alt="Logo" /> >
</div> {isOpen ? <FiX className="w-5 h-5" /> : <FiMenu className="w-5 h-5" />}
</button>
<nav className="w-[85%] mr-4 mt-8 space-y-4"> {isOpen && (
{/* داشبورد */} <div
<Link className="sm:hidden fixed inset-0 bg-black/50 z-[60]"
to="/dashboard" onClick={closeSidebar}
className={`group relative flex items-center gap-3 rounded-xl p-3 transition-all duration-300 hover:shadow-md ${ />
isActive("/dashboard") ? "bg-[#7F4629] shadow-md" : "hover:bg-[#7F4629]" )}
}`}
> <aside
<BiBarChartAlt2 dir="rtl"
className={`w-6 h-6 transition-colors ${ className={`
isActive("/dashboard") ? "text-white" : "text-[#402E32] group-hover:text-white" fixed top-0 right-0 z-[65]
}`} h-screen w-[260px]
/> flex flex-col
<span bg-[#EFEEEE]
className={`font-medium transition-colors ${ rounded-tl-2xl rounded-bl-2xl
isActive("/dashboard") ? "text-white" : "text-[#402E32] group-hover:text-white" shadow-xl border-l border-[#D9CAB3]
transition-transform duration-300 ease-in-out
${isOpen ? 'translate-x-0' : 'translate-x-full'}
sm:translate-x-0 sm:w-[220px]
`}
>
<div className="flex justify-center mt-5 mb-2">
<img src={LogoDM} className="h-10 w-10 sm:h-12 sm:w-12" alt="Logo" />
</div>
<nav className="flex-1 px-3 mt-6 space-y-3 overflow-y-auto">
<Link
to="/dashboard"
onClick={closeSidebar}
className={`group flex items-center gap-2.5 rounded-lg p-2.5 transition-all duration-200 ${
isActive("/dashboard")
? "bg-[#7F4629] shadow-md"
: "hover:bg-[#7F4629]"
}`} }`}
> >
داشبورد <BiBarChartAlt2
</span> className={`w-5 h-5 flex-shrink-0 transition-colors ${
</Link> isActive("/dashboard")
? "text-white"
: "text-[#402E32] group-hover:text-white"
}`}
/>
<span
className={`text-sm font-medium transition-colors ${
isActive("/dashboard")
? "text-white"
: "text-[#402E32] group-hover:text-white"
}`}
>
داشبورد
</span>
</Link>
{/* مدیریت کافه‌ها */} <Link
<Link to="/cafe-management"
to="/cafe-management" onClick={closeSidebar}
className={`group flex items-center gap-3 rounded-xl p-3 transition-all duration-300 hover:shadow-md ${ className={`group flex items-center gap-2.5 rounded-lg p-2.5 transition-all duration-200 ${
isActive("/cafe-management") ? "bg-[#7F4629] shadow-md" : "hover:bg-[#7F4629]" isActive("/cafe-management")
}`} ? "bg-[#7F4629] shadow-md"
> : "hover:bg-[#7F4629]"
<PiCoffee
className={`w-6 h-6 transition-colors ${
isActive("/cafe-management") ? "text-white" : "text-[#402E32] group-hover:text-white"
}`}
/>
<span
className={`font-medium transition-colors ${
isActive("/cafe-management") ? "text-white" : "text-[#402E32] group-hover:text-white"
}`} }`}
> >
مدیریت کافه ها <PiCoffee
</span> className={`w-5 h-5 flex-shrink-0 transition-colors ${
</Link> isActive("/cafe-management")
? "text-white"
: "text-[#402E32] group-hover:text-white"
}`}
/>
<span
className={`text-sm font-medium transition-colors ${
isActive("/cafe-management")
? "text-white"
: "text-[#402E32] group-hover:text-white"
}`}
>
مدیریت کافه ها
</span>
</Link>
{/* آمار و تحلیل */} <Link
<Link to="/stats"
to="/stats" onClick={closeSidebar}
className={`group flex items-center gap-3 rounded-xl p-3 transition-all duration-300 hover:shadow-md ${ className={`group flex items-center gap-2.5 rounded-lg p-2.5 transition-all duration-200 ${
isActive("/stats") ? "bg-[#7F4629] shadow-md" : "hover:bg-[#7F4629]" isActive("/stats")
}`} ? "bg-[#7F4629] shadow-md"
> : "hover:bg-[#7F4629]"
<AiOutlinePieChart
className={`w-6 h-6 transition-colors ${
isActive("/stats") ? "text-white" : "text-[#402E32] group-hover:text-white"
}`}
/>
<span
className={`font-medium transition-colors ${
isActive("/stats") ? "text-white" : "text-[#402E32] group-hover:text-white"
}`} }`}
> >
آمار و تحلیل <AiOutlinePieChart
</span> className={`w-5 h-5 flex-shrink-0 transition-colors ${
</Link> isActive("/stats")
? "text-white"
: "text-[#402E32] group-hover:text-white"
}`}
/>
<span
className={`text-sm font-medium transition-colors ${
isActive("/stats")
? "text-white"
: "text-[#402E32] group-hover:text-white"
}`}
>
آمار و تحلیل
</span>
</Link>
</nav>
{/* خروج */} <div className="p-3 border-t border-[#D9CAB3]">
<button <button
onClick={handleLogout} onClick={() => {
className="group flex items-center gap-3 rounded-xl p-3 transition-all duration-300 hover:bg-[#7F4629] hover:shadow-md mt-18 w-full text-right" handleLogout();
> closeSidebar();
<HiOutlineLogout className="w-6 h-6 text-[#402E32] transition-colors group-hover:text-white" /> }}
<span className="text-[#402E32] font-medium transition-colors group-hover:text-white"> className="group flex items-center gap-2.5 rounded-lg p-2.5 transition-all duration-200 hover:bg-[#7F4629] w-full"
خروج >
</span> <HiOutlineLogout className="w-5 h-5 flex-shrink-0 text-[#402E32] transition-colors group-hover:text-white" />
</button> <span className="text-sm text-[#402E32] font-medium transition-colors group-hover:text-white">
</nav> خروج
</aside> </span>
</button>
</div>
</aside>
</>
); );
}; };

View File

@ -1,209 +1,182 @@
import React from "react"; import React, { useState, useEffect } from "react";
import { BiEdit } from "react-icons/bi"; import { BiEdit } from "react-icons/bi";
import Search from "../../assets/icons/search.svg"; import axios from "axios";
import Arrow from "../../assets/icons/arrow.svg";
import Vector7 from "../../assets/icons/Vector7.svg";
import Vector8 from "../../assets/icons/Vector8.svg";
import Vector9 from "../../assets/icons/Vector9.svg"; import Vector9 from "../../assets/icons/Vector9.svg";
import Star1 from "../../assets/icons/Star1.svg"; import Star1 from "../../assets/icons/Star1.svg";
import Group from "../../assets/icons/Group.svg"; import Group from "../../assets/icons/Group.svg";
import Pic from "../../assets/icons/pic.png";
import Pic1 from "../../assets/icons/pic1.svg"; import Pic1 from "../../assets/icons/pic1.svg";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
const cafes = [
{
id: 1,
name: "کافه ترنج",
location: "اصفهان - خیابان آذر",
rating: 3.7,
category: "دسته بندی",
logo: Pic1,
},
{
id: 2,
name: "کافه سیب",
location: "تهران - خیابان ولیعصر",
rating: 4.2,
category: "کافه سنتی",
logo: Pic1,
},
{
id: 3,
name: "کافه داون‌تاون",
location: "شیراز - خیابان زند",
rating: 4.0,
category: "کافه فرهنگی",
logo: Pic1,
},
];
const CafeManagement = () => { const CafeManagement = () => {
return ( const [cafes, setCafes] = useState([]);
<section dir="rtl" className="-mt-12"> const [loading, setLoading] = useState(true);
{/* ===== Header ===== */} const [error, setError] = useState("");
{/* <header className="flex items-center gap-4">
<h3 className="text-[#402E32] w-[90%] font-bold">مدیریت کافه</h3>
<div className="w-12 h-10 border-2 border-[#8B8886] flex justify-center items-center rounded-2xl bg-[#E6DBCC]"> // دریافت لیست کافهها از API
<img src={Search} alt="لوگو" /> useEffect(() => {
</div> const fetchCafes = () => {
setLoading(true);
setError("");
<div className="w-45 h-10 border-2 border-[#8B8886] flex justify-center items-center rounded-2xl bg-[#E6DBCC]"> axios
<img src={Pic} className="w-10 h-10 shadow-2xl ml-1" alt="لوگو" /> .get("https://cafeju.maksiran.ir/api/cafe/v1/get-cafe-list")
<p className="text-[#402E32] -mt-1.5 pl-1.5">سارا راد</p> .then((response) => {
<img src={Arrow} alt="لوگو" /> console.log("✅ Cafes loaded successfully:", response.data);
</div>
<img src={Vector7} alt="لوگو" /> if (response.data.success && response.data.data) {
</header> */} setCafes(response.data.data);
} else {
setError("داده‌های دریافتی معتبر نیست");
}
setLoading(false);
})
.catch((error) => {
console.error("❌ Error loading cafes:", error);
setLoading(false);
{/* ===== Add Button ===== */} if (error.response) {
<div className="flex items-center justify-between"> setError(error.response.data.message || "خطا در دریافت لیست کافه‌ها");
<button className="w-45 h-12 bg-[#7F4629] text-white mt-8 rounded-3xl flex items-center justify-center gap-3 hover:bg-amber-950 transition-all cursor-pointer"> } else if (error.request) {
<p className="-mt-0.5">افزودن شعبه جدید</p> setError("خطا در برقراری ارتباط با سرور");
<img src={Vector9} alt="لوگو" /> } else {
</button> setError("خطای نامشخص رخ داده است");
}
});
};
</div> fetchCafes();
}, []);
{/* ===== Title ===== */} return (
<h3 className="text-[#402E32] w-[90%] font-bold mt-10">کافه های شما</h3> <section dir="rtl" className="-mt-12 w-full max-w-full overflow-x-hidden">
{/* دکمه افزودن */}
{/* ===== Table Header ===== */} <div className="flex items-center justify-between">
<div className="mt-10 grid grid-cols-6 gap-x-42 bg-[#EFEEEE] px-4 py-3 rounded-xl text-[#402E32] font-medium"> <button className="w-45 h-12 bg-[#7F4629] text-white mt-8 rounded-3xl flex items-center justify-center gap-3 hover:bg-amber-950 transition-all cursor-pointer">
<h3>لوگو</h3> <p className="-mt-0.5">افزودن شعبه جدید</p>
<h3>اسم</h3> <img src={Vector9} alt="لوگو" />
<h3>لوکیشن</h3> </button>
<h3>ریتینگ</h3>
<h3>دسته</h3>
<h3>ادیت</h3>
</div>
{/* ===== Table Rows ===== */}
<div className="grid grid-cols-6 gap-y-9 gap-x-33 mt-10 text-[#402E32] font-medium pl-4">
{cafes.map((cafe) => (
<React.Fragment key={cafe.id}>
<img src={cafe.logo} className="w-10 h-10" alt="لوگو" />
<h3 className="whitespace-nowrap -mr-2">{cafe.name}</h3>
<h3 className="whitespace-nowrap -mr-7">{cafe.location}</h3>
<div className="flex gap-1 mr-5">
<img className="w-6 h-6" src={Star1} alt="ستاره" />
<h3>{cafe.rating}</h3>
</div> </div>
<div className="flex gap-1 whitespace-nowrap"> {/* عنوان */}
<img className="w-5 h-5" src={Group} alt="لوگو" /> <h3 className="text-[#402E32] w-[90%] font-bold mt-10">کافه های شما</h3>
<h3 className="-mt-1">{cafe.category}</h3>
</div>
{/* ===== دکمه ادیت ===== */} {/* نمایش خطا */}
<Link {error && (
to={`/edit-cafe/${cafe.id}`} <div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl mt-4">
className="flex justify-center whitespace-nowrap gap-2 border-2 border-[#BB8F70] px-10 py-3 rounded-3xl font-light -mt-1.5 -mx-4 hover:bg-[#7F4629] hover:text-white hover:border-none cursor-pointer transition-all duration-500" {error}
> </div>
<h3>ادیت کافه</h3> )}
<BiEdit className="w-4.5 h-4.5 mt-1.5" />
</Link> {/* نمایش لودینگ */}
</React.Fragment> {loading ? (
))} <div className="flex justify-center items-center mt-20">
</div> <div className="text-[#7F4629] text-xl font-bold">در حال بارگذاری...</div>
</section> </div>
); ) : (
<>
{/* هدر جدول */}
<div className="mt-10 hidden md:grid grid-cols-6 gap-x-42 bg-[#EFEEEE] px-4 py-3 rounded-xl text-[#402E32] font-medium">
<h3>لوگو</h3>
<h3>اسم</h3>
<h3>آدرس</h3>
<h3>ریتینگ</h3>
<h3>ساعت کاری</h3>
<h3>ادیت</h3>
</div>
{/* ردیف‌های جدول - دسکتاپ */}
{cafes.length > 0 ? (
<>
{/* نمای دسکتاپ */}
<div className="hidden md:grid grid-cols-6 gap-y-9 gap-x-33 mt-10 text-[#402E32] font-medium pl-4">
{cafes.map((cafe) => (
<React.Fragment key={cafe._id}>
{/* لوگو */}
<img
src={cafe.photo || Pic1}
className="w-10 h-10 rounded-full object-cover"
alt="لوگو"
/>
{/* نام */}
<h3 className="whitespace-nowrap -mr-2">{cafe.Name}</h3>
{/* آدرس */}
<h3 className="whitespace-nowrap -mr-7 overflow-hidden text-ellipsis">
{cafe.address}
</h3>
{/* ریتینگ */}
<div className="flex gap-1 mr-5">
<img className="w-6 h-6" src={Star1} alt="ستاره" />
<h3>{cafe.rating || 0}</h3>
</div>
{/* ساعت کاری */}
<div className="flex gap-1 whitespace-nowrap">
<img className="w-5 h-5" src={Group} alt="لوگو" />
<h3 className="-mt-1">{cafe.openinghour || "نامشخص"}</h3>
</div>
{/* دکمه ادیت */}
<Link
to={`/edit-cafe/${cafe._id}`}
className="flex justify-center whitespace-nowrap gap-2 border-2 border-[#BB8F70] px-10 py-3 rounded-3xl font-light -mt-1.5 -mx-4 hover:bg-[#7F4629] hover:text-white hover:border-none cursor-pointer transition-all duration-500"
>
<h3>ادیت کافه</h3>
<BiEdit className="w-4.5 h-4.5 mt-1.5" />
</Link>
</React.Fragment>
))}
</div>
{/* نمای موبایل - کارت‌ها */}
<div className="md:hidden mt-6 space-y-4">
{cafes.map((cafe) => (
<div key={cafe._id} className="bg-white border-2 border-[#8b8886] rounded-xl p-4">
<div className="flex items-start gap-4 mb-4">
<img
src={cafe.photo || Pic1}
className="w-16 h-16 rounded-full object-cover flex-shrink-0"
alt="لوگو"
/>
<div className="flex-1">
<h3 className="font-bold text-[#402E32] mb-1">{cafe.Name}</h3>
<p className="text-sm text-gray-600">{cafe.address}</p>
</div>
</div>
<div className="flex items-center justify-between mb-3 text-sm">
<div className="flex items-center gap-1">
<img className="w-5 h-5" src={Star1} alt="ستاره" />
<span>{cafe.rating || 0}</span>
</div>
<div className="flex items-center gap-1">
<img className="w-4 h-4" src={Group} alt="لوگو" />
<span>{cafe.openinghour || "نامشخص"}</span>
</div>
</div>
<Link
to={`/edit-cafe/${cafe._id}`}
className="flex justify-center items-center gap-2 w-full border-2 border-[#BB8F70] py-2 rounded-full font-medium hover:bg-[#7F4629] hover:text-white hover:border-[#7F4629] transition-all duration-300"
>
<span>ادیت کافه</span>
<BiEdit className="w-4 h-4" />
</Link>
</div>
))}
</div>
</>
) : (
<div className="text-center mt-20 text-gray-500">
هیچ کافهای یافت نشد
</div>
)}
</>
)}
</section>
);
}; };
export default CafeManagement; export default CafeManagement;
// const CafeManagement = () => {
// return (
// <section dir="rtl" className="">
// <header className="flex items-center gap-4">
// <h3 className="text-[#402E32] w-[90%] font-bold">مدیریت کافه</h3>
// <div className="w-12 h-10 border-2 border-[#8B8886] flex justify-center items-center rounded-2xl bg-[#E6DBCC]">
// <img className="" src={Search} alt="لوگو" />
// </div>
// <div className="w-35 h-10 border-2 border-[#8B8886] flex justify-center items-center rounded-2xl bg-[#E6DBCC]">
// <img src={Pic} className="w-10 h-10 shadow-2xl ml-1" alt="لوگو" />
// <p className="text-[#402E32] -mt-1.5 pl-1.5">سارا راد</p>
// <img className="" src={Arrow} alt="لوگو" />
// </div>
// <img src={Vector7} />
// </header>
// <div className="flex items-center justify-between">
// <button className="w-45 h-10 bg-[#7F4629] text-white mt-8 rounded-3xl flex items-center justify-center gap-3 hover:bg-amber-950 transition-all cursor-pointer">
// <p className="-mt-1.5">افزودن شعبه جدید</p>
// <img className="" src={Vector9} alt="لوگو" />
// </button>
// <img className="mt-8" src={Vector8} alt="لوگو" />
// </div>
// <h3 className="text-[#402E32] w-[90%] font-bold mt-10">کافه های شما</h3>
// <div className=" mt-32 grid grid-cols-6 gap-x-42 bg-[#EFEEEE] px-4 py-3 rounded-xl text-[#402E32] font-medium ">
// <h3>لوگو</h3>
// <h3>اسم</h3>
// <h3>لوکیشن</h3>
// <h3>ریتینگ</h3>
// <h3>دسته</h3>
// <h3 className="">ادیت</h3>
// </div>
// <div className="grid grid-cols-6 grid-rows-4 gap-y-9 gap-x-33 mt-10 text-[#402E32] font-medium pl-4">
// <img src={Pic1} className="w-10 h-10" />
// <h3 className="whitespace-nowrap -mr-2">کافه ترنج</h3>
// <h3 className="whitespace-nowrap -mr-7">اصفهان - خیابان آذر </h3>
// <div className="flex gap-1 mr-5">
// <img className="w-6 h-6" src={Star1} />
// <h3>3.7</h3>
// </div>
// <div className="flex gap-1 whitespace-nowrap">
// <img className="w-5 h-5" src={Group} alt="لوگو" />
// <h3 className="-mt-1">دسته بندی</h3>
// </div>
// <div className="flex justify-center whitespace-nowrap gap-1 border-2 border-[#BB8F70] px-7 py-2 rounded-3xl font-light -mt-1.5 -mx-4 hover:bg-[#7F4629] hover:text-white hover:border-none cursor-pointer duration-600">
// <h3>ادیت کافه</h3>
// {/* <img className="w-3 h-3 mt-2" src={Vector10} alt="لوگو" /> */}
// <BiEdit className="w-3.5 h-3.5 mt-2" />
// </div>
// <img src={Pic1} className="w-10 h-10" />
// <h3 className="whitespace-nowrap -mr-2">کافه ترنج</h3>
// <h3 className="whitespace-nowrap -mr-7">اصفهان - خیابان آذر </h3>
// <div className="flex gap-1 mr-5">
// <img className="w-6 h-6" src={Star1} />
// <h3>3.7</h3>
// </div>
// <div className="flex gap-1 whitespace-nowrap">
// <img className="w-5 h-5" src={Group} alt="لوگو" />
// <h3 className="-mt-1">دسته بندی</h3>
// </div>
// <div className="flex justify-center whitespace-nowrap gap-1 border-2 border-[#BB8F70] px-7 py-2 rounded-3xl font-light -mt-1.5 -mx-4 hover:bg-[#7F4629] hover:text-white hover:border-none cursor-pointer duration-600">
// <h3>ادیت کافه</h3>
// {/* <img className="w-3 h-3 mt-2" src={Vector10} alt="لوگو" /> */}
// <BiEdit className="w-3.5 h-3.5 mt-2" />
// </div>
// <img src={Pic1} className="w-10 h-10" />
// <h3 className="whitespace-nowrap -mr-2">کافه ترنج</h3>
// <h3 className="whitespace-nowrap -mr-7">اصفهان - خیابان آذر </h3>
// <div className="flex gap-1 mr-5">
// <img className="w-6 h-6" src={Star1} alt="لوگو" />
// <h3>3.7</h3>
// </div>
// <div className="flex gap-1 whitespace-nowrap">
// <img className="w-5 h-5" src={Group} alt="لوگو" />
// <h3 className="-mt-1">دسته بندی</h3>
// </div>
// <div className="flex justify-center whitespace-nowrap gap-1 border-2 border-[#BB8F70] px-5 py-2 rounded-3xl font-light -mt-1.5 -mx-4 hover:bg-[#7F4629] hover:text-white hover:border-none cursor-pointer transition-all duration-600">
// <h3>ادیت کافه</h3>
// {/* <img className="w-3 h-3 mt-2" src={Vector10} alt="لوگو" /> */}
// <BiEdit className="w-3.5 h-3.5 mt-2" />
// </div>
// </div>
// </section>
// );
// };
// export default CafeManagement;

View File

@ -1,11 +1,10 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useParams, useNavigate } from "react-router-dom";
import axios from "axios";
import Bg1 from "../../assets/icons/bg1.svg"; import Bg1 from "../../assets/icons/bg1.svg";
import {GrLocation} from "react-icons/gr"; import {GrLocation} from "react-icons/gr";
import {BiEdit} from "react-icons/bi"; import {BiEdit} from "react-icons/bi";
import {FaRegStar} from "react-icons/fa"; import {FaRegStar} from "react-icons/fa";
import {PiCoffee} from "react-icons/pi";
import {IoMdTime} from "react-icons/io";
import {LuCalendar1} from "react-icons/lu";
import Vector11 from "../../assets/icons/Vector11.svg"; import Vector11 from "../../assets/icons/Vector11.svg";
import Vector12 from "../../assets/icons/Vector12.svg"; import Vector12 from "../../assets/icons/Vector12.svg";
import Vector13 from "../../assets/icons/Vector13.svg"; import Vector13 from "../../assets/icons/Vector13.svg";
@ -18,11 +17,46 @@ import Coffee2 from "../../assets/icons/coffee2.svg";
import Coffee1 from "../../assets/icons/coffee1.svg"; import Coffee1 from "../../assets/icons/coffee1.svg";
import Coffee3 from "../../assets/icons/coffee3.svg"; import Coffee3 from "../../assets/icons/coffee3.svg";
import Edit from "../../assets/icons/edit.svg"; import Edit from "../../assets/icons/edit.svg";
import { MdDelete } from "react-icons/md";
import { IoMdCheckmark, IoMdClose } from "react-icons/io"; import { IoMdCheckmark, IoMdClose } from "react-icons/io";
const EditCafe = () => { const EditCafe = () => {
// State برای نگهداری عنوانها با localStorage const { id } = useParams();
const navigate = useNavigate();
const [cafeData, setCafeData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
if (id) {
setLoading(true);
setError("");
axios
.get(`https://cafeju.maksiran.ir/api/cafe/v1/get-cafe-profile-by-cafe/${id}`)
.then((response) => {
console.log("✅ Cafe data loaded:", response.data);
if (response.data.success && response.data.data) {
setCafeData(response.data.data);
} else {
setError("داده‌های کافه معتبر نیست");
}
setLoading(false);
})
.catch((error) => {
console.error("❌ Error loading cafe:", error);
setLoading(false);
if (error.response) {
setError(error.response.data.message || "خطا در دریافت اطلاعات کافه");
} else if (error.request) {
setError("خطا در برقراری ارتباط با سرور");
} else {
setError("خطای نامشخص رخ داده است");
}
});
}
}, [id]);
const [categories, setCategories] = useState(() => { const [categories, setCategories] = useState(() => {
const saved = localStorage.getItem('cafeCategories'); const saved = localStorage.getItem('cafeCategories');
return saved ? JSON.parse(saved) : [ return saved ? JSON.parse(saved) : [
@ -35,52 +69,40 @@ const EditCafe = () => {
]; ];
}); });
// ذخیره تغییرات در localStorage
useEffect(() => { useEffect(() => {
localStorage.setItem('cafeCategories', JSON.stringify(categories)); localStorage.setItem('cafeCategories', JSON.stringify(categories));
}, [categories]); }, [categories]);
// State برای فعال کردن حالت ویرایش
const [isEditMode, setIsEditMode] = useState(false); const [isEditMode, setIsEditMode] = useState(false);
// State برای مدیریت حالت اضافه کردن
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const [newCategory, setNewCategory] = useState(""); const [newCategory, setNewCategory] = useState("");
// State برای مدیریت حالت ویرایش
const [editingIndex, setEditingIndex] = useState(null); const [editingIndex, setEditingIndex] = useState(null);
const [editValue, setEditValue] = useState(""); const [editValue, setEditValue] = useState("");
// تابع اضافه کردن عنوان جدید
const handleAddCategory = () => { const handleAddCategory = () => {
if (newCategory.trim()) { if (newCategory.trim()) {
setCategories([...categories, newCategory.trim()]); setCategories([...categories, newCategory.trim()]);
setNewCategory(""); setNewCategory("");
setIsAdding(false); setIsAdding(false);
// اسکرول رو به ابتدا (راست برای RTL) برمیگردونیم
setTimeout(() => { setTimeout(() => {
const scrollContainer = document.querySelector('.categories-scroll'); const scrollContainer = document.querySelector('.categories-scroll');
if (scrollContainer) { if (scrollContainer) {
// برای RTL باید scrollLeft رو به حداکثر ببریم
scrollContainer.scrollLeft = scrollContainer.scrollWidth; scrollContainer.scrollLeft = scrollContainer.scrollWidth;
} }
}, 0); }, 0);
} }
}; };
// تابع حذف عنوان
const handleDeleteCategory = (index) => { const handleDeleteCategory = (index) => {
const newCategories = categories.filter((_, i) => i !== index); const newCategories = categories.filter((_, i) => i !== index);
setCategories(newCategories); setCategories(newCategories);
}; };
// تابع شروع ویرایش
const handleStartEdit = (index) => { const handleStartEdit = (index) => {
setEditingIndex(index); setEditingIndex(index);
setEditValue(categories[index]); setEditValue(categories[index]);
}; };
// تابع ذخیره ویرایش
const handleSaveEdit = () => { const handleSaveEdit = () => {
if (editValue.trim()) { if (editValue.trim()) {
const newCategories = [...categories]; const newCategories = [...categories];
@ -91,12 +113,37 @@ const EditCafe = () => {
} }
}; };
// تابع لغو ویرایش
const handleCancelEdit = () => { const handleCancelEdit = () => {
setEditingIndex(null); setEditingIndex(null);
setEditValue(""); setEditValue("");
}; };
if (loading) {
return (
<div className="flex justify-center items-center h-screen">
<div className="text-[#7F4629] text-2xl font-bold">در حال بارگذاری...</div>
</div>
);
}
if (error) {
return (
<div className="p-6">
<div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl">
{error}
</div>
</div>
);
}
if (!cafeData) {
return (
<div className="p-6 text-center text-gray-500">
اطلاعات کافه یافت نشد
</div>
);
}
return ( return (
<> <>
<style> <style>
@ -117,104 +164,107 @@ const EditCafe = () => {
} }
`} `}
</style> </style>
<section dir="rtl"> <section dir="rtl" className="lg:-mt-12">
<h3 className="text-[#402E32] font-bold">ادیت کافه ترنج</h3> <h3 className="text-[#402E32] font-bold mb-4 lg:mb-0">ادیت {cafeData.Name}</h3>
<div className="mt-9 border-2 border-[#8b8886] p-10 rounded-2xl flex gap-4"> <div className="mt-4 lg:mt-9 border-2 border-[#8b8886] p-4 lg:p-10 rounded-2xl flex flex-col lg:flex-row gap-4">
<div> <div className="w-full lg:w-auto">
<img className="w-[461px]" src={Bg1} alt="Logo"/> <img className="w-full lg:w-[461px] rounded-lg" src={cafeData.photo || Bg1} alt="Logo"/>
</div> </div>
<div className="relative flex flex-col gap-4.5"> <div className="relative flex flex-col gap-4 lg:gap-4.5 w-full">
<div className="absolute top-2 left-0 -mt-5"> <div className="flex gap-2 justify-end lg:absolute lg:top-2 lg:left-0 lg:-mt-5">
<button <button
className="border-2 border-[#bb8f70] w-25 h-10 rounded-3xl text-[#402e32] hover:bg-[#7f4629] hover:text-white transition-all duration-300 hover:border-none"> onClick={() => navigate('/cafe-management')}
className="border-2 border-[#bb8f70] w-20 lg:w-25 h-10 rounded-3xl text-sm lg:text-base text-[#402e32] hover:bg-[#7f4629] hover:text-white transition-all duration-300 hover:border-none">
انصراف انصراف
</button> </button>
<button <button
className="border-2 border-[#bb8f70] bg-[#7f4629] text-white w-25 h-10 rounded-3xl mr-4 hover:bg-[#5f494f] transition-all duration-300 hover:border-none"> className="border-2 border-[#bb8f70] bg-[#7f4629] text-white w-20 lg:w-25 h-10 rounded-3xl text-sm lg:text-base hover:bg-[#5f494f] transition-all duration-300 hover:border-none">
تایید تایید
</button> </button>
</div> </div>
<BiEdit className="w-4.5 h-4.5 -mt-1"/>
<h3 className="font-bold text-base">کافه ترنج</h3> <BiEdit className="w-5 h-5 mt-0 lg:-mt-1"/>
<hr className="w-38 -mt-3 border-1 text-[#80931e]"/> <h3 className="font-bold text-base">{cafeData.Name}</h3>
<hr className="w-full lg:w-38 -mt-3 border-1 text-[#80931e]"/>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<GrLocation/> <GrLocation/>
<span className="text-base">اصفهان - خیابان آذر </span> <span className="text-sm lg:text-base">{cafeData.address || "آدرس موجود نیست"}</span>
</div> </div>
<hr className="w-38 -mt-3 border-1 text-[#80931e]"/> <hr className="w-full lg:w-38 -mt-3 border-1 text-[#80931e]"/>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<FaRegStar/> <FaRegStar/>
<span className="text-base">3.9</span> <span className="text-sm lg:text-base">{cafeData.rating || 0}</span>
</div> </div>
<h3 className="font-bold text-base">درباره کافه</h3>
<span className="text-base">
کافه ترنج، یک کافی شاپ با محیطی دنج و دلچسب در شهر اصفهان و یکی از
ممتازترین کافی شاپهای ابن شهر است. از جمله خدمات این کافه میتوان
به اینترنت رایگان و ارائه کتابهایی برای مطالعه در داخل کافه اشاره
کرد.
</span>
<hr className="w-[104%] -mt-3 border-1 text-[#80931e]"/>
<h3 className="text-[#402E32] font-bold mt-10 relative -right-64"> <h3 className="font-bold text-base">درباره کافه</h3>
<span className="text-sm lg:text-base">
{cafeData.description || "توضیحاتی برای این کافه وجود ندارد."}
</span>
<hr className="w-full lg:w-[104%] -mt-3 border-1 text-[#80931e]"/>
<h3 className="text-[#402E32] font-bold mt-6 lg:mt-10 lg:relative lg:-right-64">
ویژگی ها ویژگی ها
</h3> </h3>
<div className="relative -mr-71 -ml-7 mt-5 flex gap-3"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:flex lg:relative lg:-mr-71 lg:-ml-7 mt-5 gap-3">
<div <div
className="bg-[#e1d5c2] w-[140px] h-[137.6px] flex flex-col items-center gap-8 text-center rounded-4xl transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="bg-[#e1d5c2] w-full lg:w-[140px] h-[137.6px] flex flex-col items-center gap-4 lg:gap-8 text-center rounded-2xl lg:rounded-4xl transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<img src={Coffee3} className="w-6 h-6 text-[#402E32] mt-5"/> <img src={Coffee3} className="w-6 h-6 text-[#402E32] mt-5"/>
<span className="text-[#402E32] font-medium whitespace-nowrap"> <span className="text-[#402E32] font-medium text-sm lg:text-base whitespace-nowrap px-2">
منو کافه: منو کافه:
</span> </span>
</div> </div>
<div <div
className="flex flex-col gap-4 text-[#402E32] font-medium w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-4xl pr-3 transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<img src={Vector15} className="w-6 h-6 text-[#402E32] mt-5"/> <img src={Vector15} className="w-6 h-6 text-[#402E32] mt-5"/>
<span>ساعت کاری:</span> <span>ساعت کاری:</span>
<span> 23 - 8</span> <span> 23 - 8</span>
</div> </div>
<div <div
className="flex flex-col gap-4 text-[#402E32] font-medium w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-4xl pr-3 transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<img src={Vector14} className="w-6 h-6 text-[#402E32] mt-5"/> <img src={Vector14} className="w-6 h-6 text-[#402E32] mt-5"/>
<span>رزرو :</span> <span>رزرو :</span>
<span>رزرو آنلاین</span> <span>رزرو آنلاین</span>
</div> </div>
<div <div
className="flex flex-col gap-4 text-[#402E32] font-medium w-[170.5px] h-[137.6px] bg-[#e1d5c2] rounded-4xl pr-3 transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[170.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<img src={Vector11} className="w-6 h-6 text-[#402E32] mt-5"/> <img src={Vector11} className="w-6 h-6 text-[#402E32] mt-5"/>
<span>موسیقی :</span> <span>موسیقی :</span>
<span>موسیقی زنده آخر هفته</span> <span>موسیقی زنده آخر هفته</span>
</div> </div>
<div <div
className="flex flex-col gap-4 text-[#402E32] font-medium w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-4xl pr-3 transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<img src={Vector13} className="w-6 h-6 text-[#402E32] mt-5"/> <img src={Vector13} className="w-6 h-6 text-[#402E32] mt-5"/>
<span>پارکینگ :</span> <span>پارکینگ :</span>
<span>عمومی</span> <span>عمومی</span>
</div> </div>
<div <div
className="flex flex-col gap-4 text-[#402E32] font-medium w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-4xl pr-3 transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<img src={Vector12} className="w-6 h-6 text-[#402E32] mt-5"/> <img src={Vector12} className="w-6 h-6 text-[#402E32] mt-5"/>
<span>دسترسی آسان :</span> <span>دسترسی آسان :</span>
<span>مناسب افراد ناتوان</span> <span>مناسب افراد ناتوان</span>
</div> </div>
<div <div
className="flex justify-center items-center w-[150.5px] h-[137.6px] bg-[#5e5450] rounded-4xl transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="flex justify-center items-center w-full lg:w-[150.5px] h-[137.6px] bg-[#5e5450] rounded-2xl lg:rounded-4xl transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<img src={Vector9} className="w-6 h-6 text-[#402E32]"/> <img src={Vector9} className="w-6 h-6 text-[#402E32]"/>
</div> </div>
</div> </div>
<div> <div>
<div className="mt-10 -mr-64 text-[#402E32] font-medium w-[132%]"> <div className="mt-10 lg:-mr-64 text-[#402E32] font-medium w-full lg:w-[132%]">
<div className="flex items-center gap-4"> <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
<div className="max-w-[930px] overflow-hidden flex-1"> <div className="w-full sm:max-w-[930px] overflow-hidden flex-1">
<div className="categories-scroll flex gap-10 overflow-x-auto scrollbar-hide"> <div className="categories-scroll flex gap-6 lg:gap-10 overflow-x-auto scrollbar-hide">
<div className="flex items-center gap-2 justify-center flex-shrink-0"> <div className="flex items-center gap-2 justify-center flex-shrink-0">
<img <img
src={Vector16} src={Vector16}
@ -223,23 +273,20 @@ const EditCafe = () => {
onClick={() => setIsEditMode(!isEditMode)} onClick={() => setIsEditMode(!isEditMode)}
title={isEditMode ? "خروج از حالت ویرایش" : "ویرایش عنوان‌ها"} title={isEditMode ? "خروج از حالت ویرایش" : "ویرایش عنوان‌ها"}
/> />
<span className="whitespace-nowrap">عنوان</span> <span className="whitespace-nowrap text-sm lg:text-base">عنوان</span>
</div> </div>
{!isEditMode ? ( {!isEditMode ? (
// حالت عادی - فقط نمایش عنوانها
<> <>
{categories.map((category, index) => ( {categories.map((category, index) => (
<span key={index} className="whitespace-nowrap flex-shrink-0">{category}</span> <span key={index} className="whitespace-nowrap flex-shrink-0 text-sm lg:text-base">{category}</span>
))} ))}
</> </>
) : ( ) : (
// حالت ویرایش - نمایش دکمههای ویرایش و حذف بالای عنوان
<> <>
{categories.map((category, index) => ( {categories.map((category, index) => (
<div key={index} className="flex flex-col items-center gap-1 flex-shrink-0"> <div key={index} className="flex flex-col items-center gap-1 flex-shrink-0">
{editingIndex === index ? ( {editingIndex === index ? (
// حالت ویرایش
<> <>
<div className="flex items-center gap-1 mb-1"> <div className="flex items-center gap-1 mb-1">
<IoMdCheckmark <IoMdCheckmark
@ -262,21 +309,20 @@ const EditCafe = () => {
/> />
</> </>
) : ( ) : (
// حالت عادی - آیکنها بالای عنوان
<> <>
<div className="flex items-center gap-1 mb-1"> <div className="flex items-center gap-2">
<BiEdit <BiEdit
className="text-[#7f4629] cursor-pointer hover:text-[#5f494f] w-4 h-4 flex-shrink-0" className="text-[#7f4629] cursor-pointer hover:text-[#5f494f] w-5 h-5 flex-shrink-0"
onClick={() => handleStartEdit(index)} onClick={() => handleStartEdit(index)}
title="ویرایش" title="ویرایش"
/> />
<MdDelete <span className="whitespace-nowrap text-sm lg:text-base">{category}</span>
className="text-red-600 cursor-pointer hover:text-red-800 w-4 h-4 flex-shrink-0" <IoMdClose
className="text-white bg-[#a79fa1] rounded-md cursor-pointer size-5 flex-shrink-0"
onClick={() => handleDeleteCategory(index)} onClick={() => handleDeleteCategory(index)}
title="حذف" title="حذف"
/> />
</div> </div>
<span className="whitespace-nowrap">{category}</span>
</> </>
)} )}
</div> </div>
@ -286,13 +332,12 @@ const EditCafe = () => {
</div> </div>
</div> </div>
{/* دکمه اضافه کردن عنوان جدید - بیرون از container اسکرول */}
{isEditMode && ( {isEditMode && (
<div className="flex-shrink-0" style={{minWidth: '120px'}}> <div className="flex-shrink-0 w-full sm:w-auto" style={{minWidth: '120px'}}>
{!isAdding ? ( {!isAdding ? (
<button <button
onClick={() => setIsAdding(true)} onClick={() => setIsAdding(true)}
className="text-[#7f4629] hover:text-[#5f494f] font-bold transition-colors whitespace-nowrap w-full" className="text-[#7f4629] hover:text-[#5f494f] font-bold transition-colors whitespace-nowrap w-full text-sm lg:text-base"
title="اضافه کردن عنوان جدید" title="اضافه کردن عنوان جدید"
> >
+ افزودن + افزودن
@ -306,7 +351,7 @@ const EditCafe = () => {
title="ذخیره" title="ذخیره"
/> />
<IoMdClose <IoMdClose
className="text-red-600 cursor-pointer hover:text-red-800 w-4 h-4 flex-shrink-0" className="text-white bg-[#a79fa1] rounded-md cursor-pointer w-4 h-4 flex-shrink-0"
onClick={() => { onClick={() => {
setIsAdding(false); setIsAdding(false);
setNewCategory(""); setNewCategory("");
@ -328,35 +373,35 @@ const EditCafe = () => {
)} )}
</div> </div>
</div> </div>
<hr className="w-[132%] -mr-66 mt-3 border-2 rounded-3xl text-[#939393]"/> <hr className="w-full lg:w-[132%] lg:-mr-66 mt-3 border-2 rounded-3xl text-[#939393]"/>
<div className="mt-9 flex items-center gap-2 -mr-64 text-[#a79fa1] font-bold"> <div className="mt-9 flex items-center gap-2 lg:-mr-64 text-[#a79fa1] font-bold text-sm lg:text-base">
<img src={Vector16} alt="Logo"/> <img src={Vector16} alt="Logo"/>
<h3>افزودن زیر عنوان</h3> <h3>افزودن زیر عنوان</h3>
</div> </div>
<div className="mt-9 flex items-center gap-2 -mr-64 text-[#66585b] font-bold"> <div className="mt-9 flex items-center gap-2 lg:-mr-64 text-[#66585b] font-bold text-sm lg:text-base">
<img className="w-5.5 h-5.5" src={Edit} alt="Logo"/> <img className="w-5.5 h-5.5" src={Edit} alt="Logo"/>
<img className="w-6 h-6" src={Coffee3} alt="Logo"/> <img className="w-6 h-6" src={Coffee3} alt="Logo"/>
<h3>قهوه ها</h3> <h3>قهوه ها</h3>
<hr className="mt-9 -mr-15 w-[5%] border-1 text-[#80931e]"/> <hr className="mt-9 -mr-15 w-[5%] border-1 text-[#80931e] hidden lg:block"/>
</div> </div>
<div className="mt-9 flex items-center gap-2 -mr-64 text-[#66585b] font-bold"> <div className="mt-9 flex items-center gap-2 lg:-mr-64 text-[#66585b] font-bold text-sm lg:text-base">
<img src={Vector16} alt="Logo"/> <img src={Vector16} alt="Logo"/>
<h3>آیتم</h3> <h3>آیتم</h3>
</div> </div>
<div className="grid grid-cols-3 gap-5 -mr-64"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 lg:-mr-64">
<div <div
className="mt-13 flex items-center flex-col justify-center w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="mt-8 lg:mt-13 flex items-center flex-col justify-center w-full lg:w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<h1 className="text-[#402E32] font-bold -mr-50">اسپرسو100%</h1> <h1 className="text-[#402E32] font-bold text-sm lg:text-base lg:-mr-50">اسپرسو100%</h1>
<img src={Sperso} alt="Logo" className="mt-5"/> <img src={Sperso} alt="Logo" className="mt-5"/>
<div className="flex justify-between gap-50 mt-3 text-[#66585b] font-medium"> <div className="flex justify-between gap-20 lg:gap-50 mt-3 text-[#66585b] font-medium text-sm lg:text-base">
<span>قیمت</span> <span>قیمت</span>
<span>118.000</span> <span>118.000</span>
</div> </div>
<span className="font-light text-[#66585b] mt-2 break-words mr-4 text-[14.90px]"> <span className="font-light text-[#66585b] mt-2 break-words px-4 lg:mr-4 text-xs lg:text-[14.90px]">
45 میلی لیتر، قهوه، 100% عربیکا، دم شده با دستگاه اسپرسو ساز، 45 میلی لیتر، قهوه، 100% عربیکا، دم شده با دستگاه اسپرسو ساز،
به همراه یک عدد آب معدنی مینی به همراه یک عدد آب معدنی مینی
</span> </span>
@ -364,16 +409,16 @@ const EditCafe = () => {
</div> </div>
<div <div
className="mt-10 flex items-center flex-col justify-center w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="mt-8 lg:mt-10 flex items-center flex-col justify-center w-full lg:w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<h1 className="text-[#402E32] font-bold -mr-50"> <h1 className="text-[#402E32] font-bold text-sm lg:text-base lg:-mr-50">
کارامل ماکیاتو کارامل ماکیاتو
</h1> </h1>
<img src={Coffee1} alt="Logo" className="mt-5"/> <img src={Coffee1} alt="Logo" className="mt-5"/>
<div className="flex justify-between gap-50 mt-3 text-[#66585b] font-medium"> <div className="flex justify-between gap-20 lg:gap-50 mt-3 text-[#66585b] font-medium text-sm lg:text-base">
<span>قیمت</span> <span>قیمت</span>
<span>149.000</span> <span>149.000</span>
</div> </div>
<span className="font-light text-[#66585b] mt-2 break-words mr-4 text-[14.90px]"> <span className="font-light text-[#66585b] mt-2 break-words px-4 lg:mr-4 text-xs lg:text-[14.90px]">
220 میلی لیتر، 2 شات اسپرسو 30% روبوستا، 70% عربیکا، یک لکه 220 میلی لیتر، 2 شات اسپرسو 30% روبوستا، 70% عربیکا، یک لکه
فوم شیر، سیروپ کارامل فوم شیر، سیروپ کارامل
</span> </span>
@ -381,72 +426,20 @@ const EditCafe = () => {
</div> </div>
<div <div
className="mt-10 flex items-center flex-col justify-center w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"> className="mt-8 lg:mt-10 flex items-center flex-col justify-center w-full lg:w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<h1 className="text-[#402E32] font-bold -mr-47"> <h1 className="text-[#402E32] font-bold text-sm lg:text-base lg:-mr-47">
اسپرسو آفوگاتو اسپرسو آفوگاتو
</h1> </h1>
<img src={Coffee2} alt="Logo" className="mt-5"/> <img src={Coffee2} alt="Logo" className="mt-5"/>
<div className="flex justify-between gap-50 mt-3 text-[#66585b] font-medium"> <div className="flex justify-between gap-20 lg:gap-50 mt-3 text-[#66585b] font-medium text-sm lg:text-base">
<span>قیمت</span> <span>قیمت</span>
<span>118.000</span> <span>118.000</span>
</div> </div>
<span <span
className="font-light text-[#66585b] mt-2 mb-10 break-words -mr-23 text-[14.90px]"> className="font-light text-[#66585b] mt-2 mb-10 break-words px-4 lg:-mr-23 text-xs lg:text-[14.90px]">
اسپرسو، یک اسکوپ بستنی وانیلی اسپرسو، یک اسکوپ بستنی وانیلی
</span> </span>
<hr className=" mt- w-[90%] border-3 rounded-4xl text-[#e6e2de]"/> <hr className="w-[90%] border-3 rounded-4xl text-[#e6e2de]"/>
</div>
<div
className="mt-10 flex items-center flex-col justify-center w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<h1 className="text-[#402E32] font-bold -mr-47">
اسپرسو آفوگاتو
</h1>
<img src={Coffee2} alt="Logo" className="mt-5"/>
<div className="flex justify-between gap-50 mt-3 text-[#66585b] font-medium">
<span>قیمت</span>
<span>118.000</span>
</div>
<span
className="font-light text-[#66585b] mt-2 mb-10 break-words -mr-23 text-[14.90px]">
اسپرسو، یک اسکوپ بستنی وانیلی
</span>
<hr className=" mt- w-[90%] border-3 rounded-4xl text-[#e6e2de]"/>
</div>
<div
className="mt-10 flex items-center flex-col justify-center w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<h1 className="text-[#402E32] font-bold -mr-47">
اسپرسو آفوگاتو
</h1>
<img src={Coffee2} alt="Logo" className="mt-5"/>
<div className="flex justify-between gap-50 mt-3 text-[#66585b] font-medium">
<span>قیمت</span>
<span>118.000</span>
</div>
<span
className="font-light text-[#66585b] mt-2 mb-10 break-words -mr-23 text-[14.90px]">
اسپرسو، یک اسکوپ بستنی وانیلی
</span>
<hr className=" mt- w-[90%] border-3 rounded-4xl text-[#e6e2de]"/>
</div>
<div
className="mt-10 flex items-center flex-col justify-center w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
<h1 className="text-[#402E32] font-bold -mr-47">
اسپرسو آفوگاتو
</h1>
<img src={Coffee2} alt="Logo" className="mt-5"/>
<div className="flex justify-between gap-50 mt-3 text-[#66585b] font-medium">
<span>قیمت</span>
<span>118.000</span>
</div>
<span
className="font-light text-[#66585b] mt-2 mb-10 break-words -mr-23 text-[14.90px]">
اسپرسو، یک اسکوپ بستنی وانیلی
</span>
<hr className=" mt- w-[90%] border-3 rounded-4xl text-[#e6e2de]"/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,65 +1,81 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import Loginpic from '../../assets/image/loginpic.jpg'; import Loginpic from '../../assets/image/loginpic.jpg';
import { FiLock } from 'react-icons/fi'; import { FiLock } from 'react-icons/fi';
import { HiOutlineDevicePhoneMobile } from "react-icons/hi2"; import { FaRegUser } from "react-icons/fa6";
import axios from 'axios'; import axios from 'axios';
import LogoDM from "../../assets/icons/LogoDM.svg"; import LogoDM from "../../assets/icons/LogoDM.svg";
import { useNavigate, Navigate } from 'react-router-dom';
// تنظیم base URL برای axios
axios.defaults.baseURL = 'https://cafeju.maksiran.ir';
const Login = () => { const Login = () => {
const [phoneNumber, setPhoneNumber] = useState(''); const [userName, setUserName] = useState('');
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(''); const [error, setError] = useState('');
const navigate = useNavigate();
const handlePhoneChange = (e) => { // چک کردن token موقع لود شدن صفحه
const value = e.target.value; useEffect(() => {
// فقط اجازه ورود اعداد const token = localStorage.getItem('token');
if (value === '' || /^[0-9\b]+$/.test(value)) { // اگر قبلاً لاگین کرده، به داشبورد هدایت میشه
// محدود کردن به 11 رقم if (token) {
if (value.length <= 11) { navigate('/dashboard');
setPhoneNumber(value);
}
} }
}; }, [navigate]);
const handleLogin = (e) => { const handleLogin = (e) => {
e.preventDefault(); e.preventDefault();
setError(''); setError('');
setLoading(true); setLoading(true);
// آمادهسازی داده برای ارسال
const loginData = { const loginData = {
userName: phoneNumber, userName: userName,
password: password password: password
}; };
// ارسال درخواست به API console.log('Sending login request with:', loginData);
axios.post('/api/admin/v1/login', loginData) axios.post('/api/admin/v1/login', loginData)
.then((response) => { .then((response) => {
setLoading(false); setLoading(false);
console.log('Login successful:', response.data); console.log('✅ Login successful!');
console.log('Response:', response.data);
// ذخیره توکن در localStorage if (response.data.success && response.data.data && response.data.data.tokens) {
if (response.data.token) { const accessToken = response.data.data.tokens.accessToken;
localStorage.setItem('token', response.data.token); const refreshToken = response.data.data.tokens.refreshToken;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
localStorage.setItem('token', accessToken);
localStorage.setItem('adminInfo', JSON.stringify(response.data.data.admin));
console.log('Tokens saved to localStorage');
console.log('Admin info:', response.data.data.admin);
console.log('Navigating to dashboard...');
navigate('/dashboard');
} else {
console.error('❌ Invalid response format');
setError('پاسخ سرور معتبر نیست');
} }
// هدایت به صفحه داشبورد یا صفحه اصلی
// window.location.href = '/dashboard';
alert('ورود موفقیت‌آمیز بود!');
}) })
.catch((error) => { .catch((error) => {
setLoading(false); setLoading(false);
console.error('Login error:', error); console.error('❌ Login error:', error);
// مدیریت خطاها
if (error.response) { if (error.response) {
// سرور پاسخ داد اما با خطا console.error('Response status:', error.response.status);
setError(error.response.data.message || 'نام کاربری یا رمز عبور اشتباه است'); console.error('Response data:', error.response.data);
setError(error.response.data.message || error.response.data.en_message || 'نام کاربری یا رمز عبور اشتباه است');
} else if (error.request) { } else if (error.request) {
// درخواست ارسال شد اما پاسخی دریافت نشد console.error('No response received from server');
setError('خطا در برقراری ارتباط با سرور'); setError('خطا در برقراری ارتباط با سرور');
} else { } else {
console.error('Error setting up request');
setError('خطای نامشخص رخ داده است'); setError('خطای نامشخص رخ داده است');
} }
}); });
@ -134,7 +150,6 @@ const Login = () => {
</div> </div>
<form onSubmit={handleLogin} className="space-y-6"> <form onSubmit={handleLogin} className="space-y-6">
{/* نمایش پیغام خطا */}
{error && ( {error && (
<div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl text-sm"> <div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl text-sm">
{error} {error}
@ -143,21 +158,18 @@ const Login = () => {
<div className="relative"> <div className="relative">
<label className="absolute -top-2.5 right-4 bg-[#f5f0e8] px-2 text-sm text-gray-600 z-10"> <label className="absolute -top-2.5 right-4 bg-[#f5f0e8] px-2 text-sm text-gray-600 z-10">
شماره موبایل نام کاربری
</label> </label>
<div className="relative"> <div className="relative">
<input <input
type="text" type="text"
value={phoneNumber} value={userName}
onChange={handlePhoneChange} onChange={(e) => setUserName(e.target.value)}
placeholder="09132659856" placeholder="userName"
className="w-full pl-14 pr-6 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors" className="w-full pl-14 pr-6 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors"
dir="ltr" dir="ltr"
maxLength={11}
inputMode="numeric"
pattern="[0-9]*"
/> />
<HiOutlineDevicePhoneMobile className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-400 w-6 h-10" /> <FaRegUser className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-400 w-6 h-5" />
</div> </div>
</div> </div>
@ -170,7 +182,7 @@ const Login = () => {
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="2569876nb*" placeholder="password"
className="w-full pl-14 pr-6 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors" className="w-full pl-14 pr-6 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors"
dir="ltr" dir="ltr"
/> />
@ -189,7 +201,7 @@ const Login = () => {
<button <button
type="submit" type="submit"
disabled={loading || !phoneNumber || !password} disabled={loading || !userName || !password}
className="w-full bg-[#7f4629] text-white py-3 rounded-full font-bold hover:bg-[#5f494f] transition-all duration-300 shadow-lg hover:shadow-xl disabled:bg-gray-400 disabled:cursor-not-allowed" className="w-full bg-[#7f4629] text-white py-3 rounded-full font-bold hover:bg-[#5f494f] transition-all duration-300 shadow-lg hover:shadow-xl disabled:bg-gray-400 disabled:cursor-not-allowed"
> >
{loading ? 'در حال ورود...' : 'ورود'} {loading ? 'در حال ورود...' : 'ورود'}
@ -210,4 +222,18 @@ const Login = () => {
); );
}; };
export const ProtectedRoute = ({ children }) => {
const token = localStorage.getItem('token');
console.log('ProtectedRoute - Token check:', token ? 'Token exists' : 'No token');
if (!token) {
console.log('ProtectedRoute - Redirecting to login');
return <Navigate to="/login" replace />;
}
console.log('ProtectedRoute - Rendering protected content');
return children;
};
export default Login; export default Login;

View File

@ -1,42 +1,112 @@
/* #root { @tailwind utilities;
max-width: 1280px;
margin: 0 auto; /* Custom Breakpoints for Mobile-First Design */
padding: 2rem; @layer base {
text-align: center; /* Base styles - Mobile First (320px+) */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
overflow-x: hidden;
width: 100%;
max-width: 100vw;
}
#root {
width: 100%;
min-height: 100vh;
overflow-x: hidden;
}
} }
.logo { /* Custom Responsive Classes */
height: 6em; @layer utilities {
padding: 1.5em; /* Hide scrollbar globally */
will-change: filter; .scrollbar-hide::-webkit-scrollbar {
transition: filter 300ms; display: none;
} }
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa); .scrollbar-hide {
} -ms-overflow-style: none;
.logo.react:hover { scrollbar-width: none;
filter: drop-shadow(0 0 2em #61dafbaa); }
/* Prevent horizontal scroll */
.no-scroll-x {
overflow-x: hidden;
max-width: 100vw;
}
} }
@keyframes logo-spin { /* Responsive Container Sizes */
from { @layer components {
transform: rotate(0deg); .container-responsive {
} width: 100%;
to { margin: 0 auto;
transform: rotate(360deg); padding-left: 1rem;
} padding-right: 1rem;
} }
@media (prefers-reduced-motion: no-preference) { /* iPhone SE and smaller (320px - 374px) */
a:nth-of-type(2) .logo { @media (min-width: 320px) {
animation: logo-spin infinite 20s linear; .container-responsive {
} max-width: 320px;
} }
}
.card { /* iPhone 12/13 Mini (375px - 389px) */
padding: 2em; @media (min-width: 375px) {
} .container-responsive {
max-width: 375px;
}
}
.read-the-docs { /* iPhone 14 (390px - 429px) */
color: #888; @media (min-width: 390px) {
} */ .container-responsive {
max-width: 390px;
}
}
/* iPhone 14 Pro Max (430px - 639px) */
@media (min-width: 430px) {
.container-responsive {
max-width: 430px;
}
}
/* Small devices (640px - 767px) - Tailwind sm */
@media (min-width: 640px) {
.container-responsive {
max-width: 640px;
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
/* Medium devices (768px - 1023px) - Tailwind md */
@media (min-width: 768px) {
.container-responsive {
max-width: 768px;
padding-left: 2rem;
padding-right: 2rem;
}
}
/* Large devices (1024px+) - Tailwind lg */
@media (min-width: 1024px) {
.container-responsive {
max-width: 1024px;
}
}
/* Extra Large devices (1280px+) - Tailwind xl */
@media (min-width: 1280px) {
.container-responsive {
max-width: 1280px;
}
}
}