From 5b494855c833c89a7f695735aef2950cdd983664 Mon Sep 17 00:00:00 2001 From: MHR81 Date: Wed, 24 Dec 2025 20:50:21 +0330 Subject: [PATCH] change project base --- package-lock.json | 157 ++++- package.json | 3 + src/App.jsx | 31 +- src/components/Toast.jsx | 63 ++ src/components/layout/{ => Header}/header.jsx | 8 +- .../layout/{ => Sidebar}/sidebar.jsx | 6 +- src/components/layout/layout.jsx | 4 +- src/hooks/useProfile.js | 29 + src/main.jsx | 19 +- src/pages/CafeManagement/CafeManagement.jsx | 48 +- src/pages/CafeManagement/EditCafe.jsx | 618 +++++++++--------- src/pages/Dashboard/Dashboard.jsx | 5 + src/pages/Login/Login.jsx | 99 +-- src/redux/slices/authSlice.js | 26 + src/redux/slices/loadingSlice.js | 13 + src/redux/slices/profileSlice.js | 20 + src/redux/slices/toastSlice.js | 26 + src/redux/store.js | 14 + src/routes/ProtectedRoute/ProtectedRoute.jsx | 15 + src/routes/Routes.jsx | 28 + src/services/api/base-api.js | 115 ++++ src/services/api/toastIgnore.js | 3 + src/services/auth.js | 15 + src/services/cafe.js | 8 + src/services/dashboard.js | 7 + src/services/profile.js | 10 + src/styles/App.css | 112 ---- 27 files changed, 944 insertions(+), 558 deletions(-) create mode 100644 src/components/Toast.jsx rename src/components/layout/{ => Header}/header.jsx (90%) rename src/components/layout/{ => Sidebar}/sidebar.jsx (97%) create mode 100644 src/hooks/useProfile.js create mode 100644 src/redux/slices/authSlice.js create mode 100644 src/redux/slices/loadingSlice.js create mode 100644 src/redux/slices/profileSlice.js create mode 100644 src/redux/slices/toastSlice.js create mode 100644 src/redux/store.js create mode 100644 src/routes/ProtectedRoute/ProtectedRoute.jsx create mode 100644 src/routes/Routes.jsx create mode 100644 src/services/api/base-api.js create mode 100644 src/services/api/toastIgnore.js create mode 100644 src/services/auth.js create mode 100644 src/services/cafe.js create mode 100644 src/services/dashboard.js create mode 100644 src/services/profile.js delete mode 100644 src/styles/App.css diff --git a/package-lock.json b/package-lock.json index 8464f23..a774968 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,14 @@ "name": "my-project", "version": "0.0.0", "dependencies": { + "@reduxjs/toolkit": "^2.11.2", "@tailwindcss/vite": "^4.1.14", "axios": "^1.13.2", + "framer-motion": "^12.23.26", "react": "^19.1.1", "react-dom": "^19.1.1", "react-icons": "^5.5.0", + "react-redux": "^9.2.0", "react-router-dom": "^7.9.4", "tailwind-scrollbar-hide": "^4.0.0", "tailwindcss": "^4.1.14" @@ -639,6 +642,32 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.0-beta.41", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.41.tgz", @@ -893,6 +922,18 @@ } } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -1681,7 +1722,7 @@ "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1697,6 +1738,12 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.0.4.tgz", @@ -2053,7 +2100,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { @@ -2545,6 +2592,33 @@ "node": ">= 6" } }, + "node_modules/framer-motion": { + "version": "12.23.26", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.26.tgz", + "integrity": "sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.23.23", + "motion-utils": "^12.23.6", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2718,6 +2792,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.0.tgz", + "integrity": "sha512-dlzb07f5LDY+tzs+iLCSXV2yuhaYfezqyZQc+n6baLECWkOMEWxkECAOnXL0ba7lsA25fM9b2jtzpu/uxo1a7g==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3261,6 +3345,21 @@ "node": ">= 18" } }, + "node_modules/motion-dom": { + "version": "12.23.23", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", + "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.23.6" + } + }, + "node_modules/motion-utils": { + "version": "12.23.6", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", + "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3525,6 +3624,29 @@ "react": "*" } }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -3573,6 +3695,27 @@ "react-dom": ">=18" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3793,7 +3936,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "devOptional": true, "license": "0BSD" }, "node_modules/type-check": { @@ -3850,6 +3992,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "name": "rolldown-vite", "version": "7.1.14", diff --git a/package.json b/package.json index ca55b67..47bcafa 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,14 @@ "preview": "vite preview" }, "dependencies": { + "@reduxjs/toolkit": "^2.11.2", "@tailwindcss/vite": "^4.1.14", "axios": "^1.13.2", + "framer-motion": "^12.23.26", "react": "^19.1.1", "react-dom": "^19.1.1", "react-icons": "^5.5.0", + "react-redux": "^9.2.0", "react-router-dom": "^7.9.4", "tailwind-scrollbar-hide": "^4.0.0", "tailwindcss": "^4.1.14" diff --git a/src/App.jsx b/src/App.jsx index 161a802..0bcb932 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,35 +1,8 @@ -import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; -import Login, { ProtectedRoute } from './pages/Login/Login'; -import Layout from './components/Layout/Layout'; -import Dashboard from './pages/Dashboard/Dashboard'; -import CafeManagement from './pages/CafeManagement/CafeManagement'; -import EditCafe from './pages/CafeManagement/EditCafe'; +import AppRoutes from "./routes/routes"; function App() { return ( - - - } /> - - - - - } - > - } /> - } /> - } /> - صفحه آمار و تحلیل} /> - - } /> - - - } /> - - + ); } diff --git a/src/components/Toast.jsx b/src/components/Toast.jsx new file mode 100644 index 0000000..7ee8656 --- /dev/null +++ b/src/components/Toast.jsx @@ -0,0 +1,63 @@ +import { useEffect } from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { hideToast } from "../redux/slices/toastSlice"; +import { motion, AnimatePresence } from "framer-motion"; //eslint-disable-line no-unused-vars + +export default function Toast() { + const { message, type, visible } = useSelector((state) => state.toast); + const dispatch = useDispatch(); + + useEffect(() => { + if (visible) { + const timer = setTimeout(() => dispatch(hideToast()), 3000); + return () => clearTimeout(timer); + } + }, [visible, dispatch]); + + const bgColor = + type === "success" + ? "bg-green-500" + : type === "error" + ? "bg-red-500" + : "bg-blue-500"; + + // موبایل: از پایین بیا و برگرد پایین + const mobileVariants = { + initial: { y: 100, opacity: 0 }, + animate: { y: 0, opacity: 1 }, + exit: { y: 100, opacity: 0 } + }; + + // دسکتاپ: از چپ بیا + const desktopVariants = { + initial: { x: -100, opacity: 0 }, + animate: { x: 0, opacity: 1 }, + exit: { x: -100, opacity: 0 } + }; + + // تعیین حالت بر اساس اندازه صفحه + const isMobile = typeof window !== "undefined" && window.innerWidth < 768; + const variants = isMobile ? mobileVariants : desktopVariants; + + return ( + + {visible && ( + + {message} + + )} + + ); +} diff --git a/src/components/layout/header.jsx b/src/components/layout/Header/header.jsx similarity index 90% rename from src/components/layout/header.jsx rename to src/components/layout/Header/header.jsx index 6328df8..119cfe3 100644 --- a/src/components/layout/header.jsx +++ b/src/components/layout/Header/header.jsx @@ -25,10 +25,10 @@ // src/components/Header.jsx import React from "react"; -import Search from "../../assets/icons/search.svg"; -import Arrow from "../../assets/icons/arrow.svg"; -import Vector7 from "../../assets/icons/Vector7.svg"; -import Pic from "../../assets/icons/pic.png"; +import Search from "../../../assets/icons/search.svg"; +import Arrow from "../../../assets/icons/arrow.svg"; +import Vector7 from "../../../assets/icons/Vector7.svg"; +import Pic from "../../../assets/icons/pic.png"; const Header = ({ title }) => { return ( diff --git a/src/components/layout/sidebar.jsx b/src/components/layout/Sidebar/sidebar.jsx similarity index 97% rename from src/components/layout/sidebar.jsx rename to src/components/layout/Sidebar/sidebar.jsx index 3152a90..f225438 100644 --- a/src/components/layout/sidebar.jsx +++ b/src/components/layout/Sidebar/sidebar.jsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; 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 { AiOutlinePieChart } from "react-icons/ai"; import { PiCoffee } from "react-icons/pi"; @@ -24,10 +24,10 @@ const Sidebar = () => { console.log('Tokens cleared from localStorage'); console.log('Current token:', localStorage.getItem('token')); - window.location.href = '/login'; + navigate('/login'); } catch (error) { console.error('Logout error:', error); - window.location.href = '/login'; + navigate('/login'); } }; diff --git a/src/components/layout/layout.jsx b/src/components/layout/layout.jsx index b1b7466..a527fee 100644 --- a/src/components/layout/layout.jsx +++ b/src/components/layout/layout.jsx @@ -1,6 +1,6 @@ import { Outlet, useLocation } from "react-router-dom"; -import Sidebar from "./Sidebar"; -import Header from "./Header"; +import Sidebar from "./Sidebar/sidebar"; +import Header from "./Header/header"; export default function Layout() { const location = useLocation(); diff --git a/src/hooks/useProfile.js b/src/hooks/useProfile.js new file mode 100644 index 0000000..c41f14c --- /dev/null +++ b/src/hooks/useProfile.js @@ -0,0 +1,29 @@ +import { useEffect } from "react"; +import { useDispatch } from "react-redux"; +import { setProfile, clearProfile } from "../redux/slices/profileSlice"; +import profileService from "../services/profile"; + +export const useProfile = (shouldFetch = true) => { + const dispatch = useDispatch(); + + useEffect(() => { + if (!shouldFetch) return; + + const fetchProfileData = async () => { + try { + const res = await profileService.getProfile(); + if (res?.data?.data) { + dispatch(setProfile(res.data.data)); + console.log("Profile data fetched:", res.data.data); + } else { + dispatch(clearProfile()); + } + } catch (error) { + console.error("Error fetching profile:", error); + dispatch(clearProfile()); + } + }; + + fetchProfileData(); + }, [shouldFetch, dispatch]); +}; diff --git a/src/main.jsx b/src/main.jsx index 62820cb..04731fc 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,10 +1,19 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './styles/index.css' -import App from './App.jsx' +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { Provider } from 'react-redux'; +import { store } from './redux/store.js'; +import './styles/index.css'; +import App from './App.jsx'; +import Toast from './components/Toast.jsx'; createRoot(document.getElementById('root')).render( - + + + + + + , ) \ No newline at end of file diff --git a/src/pages/CafeManagement/CafeManagement.jsx b/src/pages/CafeManagement/CafeManagement.jsx index b536c5d..7d7d8a5 100644 --- a/src/pages/CafeManagement/CafeManagement.jsx +++ b/src/pages/CafeManagement/CafeManagement.jsx @@ -1,11 +1,11 @@ import React, { useState, useEffect } from "react"; import { BiEdit } from "react-icons/bi"; -import axios from "axios"; import Vector9 from "../../assets/icons/Vector9.svg"; import Star1 from "../../assets/icons/Star1.svg"; import Group from "../../assets/icons/Group.svg"; import Pic1 from "../../assets/icons/pic1.svg"; import { Link } from "react-router-dom"; +import cafeService from "../../services/cafe"; const CafeManagement = () => { const [cafes, setCafes] = useState([]); @@ -14,34 +14,30 @@ const CafeManagement = () => { // دریافت لیست کافه‌ها از API useEffect(() => { - const fetchCafes = () => { + const fetchCafes = async () => { setLoading(true); setError(""); - axios - .get("https://cafeju.maksiran.ir/api/cafe/v1/get-cafe-list") - .then((response) => { - console.log("✅ Cafes loaded successfully:", response.data); - - if (response.data.success && response.data.data) { - setCafes(response.data.data); - } else { - setError("داده‌های دریافتی معتبر نیست"); - } - setLoading(false); - }) - .catch((error) => { - console.error("❌ Error loading cafes:", error); - setLoading(false); - - if (error.response) { - setError(error.response.data.message || "خطا در دریافت لیست کافه‌ها"); - } else if (error.request) { - setError("خطا در برقراری ارتباط با سرور"); - } else { - setError("خطای نامشخص رخ داده است"); - } - }); + try { + const res = await cafeService.getCafeList(); + if (res.data.success && res.data.data) { + setCafes(res.data.data); + } else { + setError("داده‌های دریافتی معتبر نیست"); + } + console.log("✅ Cafes loaded successfully:", res.data); + } catch (error) { + if (error.response) { + setError(error.response.data.message || "خطا در دریافت لیست کافه‌ها"); + } else if (error.request) { + setError("خطا در برقراری ارتباط با سرور"); + } else { + setError("خطای نامشخص رخ داده است"); + } + console.error("❌ Error loading cafes:", error); + } finally { + setLoading(false); + } }; fetchCafes(); diff --git a/src/pages/CafeManagement/EditCafe.jsx b/src/pages/CafeManagement/EditCafe.jsx index 157178d..8a18a1d 100644 --- a/src/pages/CafeManagement/EditCafe.jsx +++ b/src/pages/CafeManagement/EditCafe.jsx @@ -1,10 +1,9 @@ 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 {GrLocation} from "react-icons/gr"; -import {BiEdit} from "react-icons/bi"; -import {FaRegStar} from "react-icons/fa"; +import { GrLocation } from "react-icons/gr"; +import { BiEdit } from "react-icons/bi"; +import { FaRegStar } from "react-icons/fa"; import Vector11 from "../../assets/icons/Vector11.svg"; import Vector12 from "../../assets/icons/Vector12.svg"; import Vector13 from "../../assets/icons/Vector13.svg"; @@ -18,45 +17,15 @@ import Coffee1 from "../../assets/icons/coffee1.svg"; import Coffee3 from "../../assets/icons/coffee3.svg"; import Edit from "../../assets/icons/edit.svg"; import { IoMdCheckmark, IoMdClose } from "react-icons/io"; +import cafeService from "../../services/cafe"; -const EditCafe = () => { +export default function EditCafe() { 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 saved = localStorage.getItem('cafeCategories'); return saved ? JSON.parse(saved) : [ @@ -69,16 +38,55 @@ const EditCafe = () => { ]; }); - useEffect(() => { - localStorage.setItem('cafeCategories', JSON.stringify(categories)); - }, [categories]); - const [isEditMode, setIsEditMode] = useState(false); const [isAdding, setIsAdding] = useState(false); const [newCategory, setNewCategory] = useState(""); const [editingIndex, setEditingIndex] = useState(null); const [editValue, setEditValue] = useState(""); + // دریافت اطلاعات کافه + useEffect(() => { + const getCafeData = async () => { + setLoading(true); + setError(""); + try { + const res = await cafeService.getCafeList(); + if (res.data.success && res.data.data) { + const cafe = res.data.data.find(c => c._id === id); + if (cafe) { + setCafeData(cafe); + console.log("✅ Cafe data loaded:", cafe); + } else { + setError("کافه مورد نظر یافت نشد"); + } + } else { + setError("داده‌های کافه معتبر نیست"); + } + } catch (error) { + console.error("❌ Error loading cafe:", error); + if (error.response) { + setError(error.response.data.message || "خطا در دریافت اطلاعات کافه"); + } else if (error.request) { + setError("خطا در برقراری ارتباط با سرور"); + } else { + setError("خطای نامشخص رخ داده است"); + } + } finally { + setLoading(false); + } + }; + + if (id) { + getCafeData(); + } + }, [id]); + + // ذخیره دسته‌بندی‌ها در localStorage + useEffect(() => { + localStorage.setItem('cafeCategories', JSON.stringify(categories)); + }, [categories]); + + // Handlers const handleAddCategory = () => { if (newCategory.trim()) { setCategories([...categories, newCategory.trim()]); @@ -144,10 +152,10 @@ const EditCafe = () => { ); } - return ( - <> - -
-

ادیت {cafeData.Name}

+ +
+

ادیت {cafeData.Name}

-
-
- Logo -
- -
-
- - +
+
+ Logo
- -

{cafeData.Name}

-
+
+
+ + +
-
- - {cafeData.address || "آدرس موجود نیست"} -
-
+ +

{cafeData.Name}

+
-
- - {cafeData.rating || 0} -
+
+ + {cafeData.address || "آدرس موجود نیست"} +
+
-

درباره کافه

- - {cafeData.description || "توضیحاتی برای این کافه وجود ندارد."} - -
+
+ + {cafeData.rating || 0} +
-

- ویژگی ها -

- -
-
- - - منو کافه: +

درباره کافه

+ + {cafeData.description || "توضیحاتی برای این کافه وجود ندارد."} +
+ +

+ ویژگی ها +

+ +
+
+ + + منو کافه: + +
+ +
+ + ساعت کاری: + 23 - 8 +
+ +
+ + رزرو : + رزرو آنلاین +
+ +
+ + موسیقی : + موسیقی زنده آخر هفته +
+ +
+ + پارکینگ : + عمومی +
+ +
+ + دسترسی آسان : + مناسب افراد ناتوان +
+ +
+ +
-
- - ساعت کاری: - 23 - 8 -
+
+
+
+
+
+
+ Logo setIsEditMode(!isEditMode)} + title={isEditMode ? "خروج از حالت ویرایش" : "ویرایش عنوان‌ها"} + /> + عنوان +
-
- - رزرو : - رزرو آنلاین -
- -
- - موسیقی : - موسیقی زنده آخر هفته -
- -
- - پارکینگ : - عمومی -
- -
- - دسترسی آسان : - مناسب افراد ناتوان -
- -
- -
-
- -
-
-
-
-
-
- Logo setIsEditMode(!isEditMode)} - title={isEditMode ? "خروج از حالت ویرایش" : "ویرایش عنوان‌ها"} - /> - عنوان + {!isEditMode ? ( + <> + {categories.map((category, index) => ( + {category} + ))} + + ) : ( + <> + {categories.map((category, index) => ( +
+ {editingIndex === index ? ( + <> +
+ + +
+ setEditValue(e.target.value)} + className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-sm focus:outline-none focus:border-[#7f4629] w-[100px] text-center" + autoFocus + /> + + ) : ( + <> +
+ handleStartEdit(index)} + title="ویرایش" + /> + {category} + handleDeleteCategory(index)} + title="حذف" + /> +
+ + )} +
+ ))} + + )}
- - {!isEditMode ? ( - <> - {categories.map((category, index) => ( - {category} - ))} - - ) : ( - <> - {categories.map((category, index) => ( -
- {editingIndex === index ? ( - <> -
- - -
- setEditValue(e.target.value)} - className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-sm focus:outline-none focus:border-[#7f4629] w-[100px] text-center" - autoFocus - /> - - ) : ( - <> -
- handleStartEdit(index)} - title="ویرایش" - /> - {category} - handleDeleteCategory(index)} - title="حذف" - /> -
- - )} -
- ))} - - )}
-
- {isEditMode && ( -
- {!isAdding ? ( - - ) : ( -
-
- - { - setIsAdding(false); - setNewCategory(""); - }} - title="لغو" + {isEditMode && ( +
+ {!isAdding ? ( + + ) : ( +
+
+ + { + setIsAdding(false); + setNewCategory(""); + }} + title="لغو" + /> +
+ setNewCategory(e.target.value)} + placeholder="عنوان جدید" + className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-sm focus:outline-none focus:border-[#7f4629] w-[100px] text-center" + autoFocus />
- setNewCategory(e.target.value)} - placeholder="عنوان جدید" - className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-sm focus:outline-none focus:border-[#7f4629] w-[100px] text-center" - autoFocus - /> -
- )} + )} +
+ )} +
+
+
+ +
+ Logo +

افزودن زیر عنوان

+
+ +
+ Logo + Logo +

قهوه ها

+
+
+ +
+ Logo +

آیتم

+
+ +
+
+

اسپرسو100%

+ Logo +
+ قیمت + 118.000
- )} -
-
-
- -
- Logo -

افزودن زیر عنوان

-
- -
- Logo - Logo -

قهوه ها

-
-
- -
- Logo -

آیتم

-
- -
-
-

اسپرسو100%

- Logo -
- قیمت - 118.000 + + 45 میلی لیتر، قهوه، 100% عربیکا، دم شده با دستگاه اسپرسو ساز، + به همراه یک عدد آب معدنی مینی + +
- - 45 میلی لیتر، قهوه، 100% عربیکا، دم شده با دستگاه اسپرسو ساز، - به همراه یک عدد آب معدنی مینی - -
-
-
-

- کارامل ماکیاتو -

- Logo -
- قیمت - 149.000 +
+

+ کارامل ماکیاتو +

+ Logo +
+ قیمت + 149.000 +
+ + 220 میلی لیتر، 2 شات اسپرسو 30% روبوستا، 70% عربیکا، یک لکه + فوم شیر، سیروپ کارامل + +
- - 220 میلی لیتر، 2 شات اسپرسو 30% روبوستا، 70% عربیکا، یک لکه - فوم شیر، سیروپ کارامل - -
-
-
-

- اسپرسو آفوگاتو -

- Logo -
- قیمت - 118.000 +
+

+ اسپرسو آفوگاتو +

+ Logo +
+ قیمت + 118.000 +
+ + اسپرسو، یک اسکوپ بستنی وانیلی + +
- - اسپرسو، یک اسکوپ بستنی وانیلی - -
-
-
- - ); -}; - -export default EditCafe; \ No newline at end of file +
+ + ); + } diff --git a/src/pages/Dashboard/Dashboard.jsx b/src/pages/Dashboard/Dashboard.jsx index 809df27..5be2154 100644 --- a/src/pages/Dashboard/Dashboard.jsx +++ b/src/pages/Dashboard/Dashboard.jsx @@ -1,4 +1,9 @@ +import {useProfile} from "../../hooks/useProfile"; + const Dashboard = () => { + useProfile(); + + return (
صفحه داشبورد diff --git a/src/pages/Login/Login.jsx b/src/pages/Login/Login.jsx index 0c12f8d..cba36af 100644 --- a/src/pages/Login/Login.jsx +++ b/src/pages/Login/Login.jsx @@ -1,13 +1,14 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import Loginpic from '../../assets/image/loginpic.jpg'; -import { FiLock } from 'react-icons/fi'; +import { FiLock } from 'react-icons/fi'; import { FaRegUser } from "react-icons/fa6"; -import axios from 'axios'; import LogoDM from "../../assets/icons/LogoDM.svg"; import { useNavigate, Navigate } from 'react-router-dom'; +import authService from '../../services/auth'; +import { useDispatch } from 'react-redux'; +import { setToken } from '../../redux/slices/authSlice'; +import { setProfile } from '../../redux/slices/profileSlice'; -// تنظیم base URL برای axios -axios.defaults.baseURL = 'https://cafeju.maksiran.ir'; const Login = () => { const [userName, setUserName] = useState(''); @@ -15,17 +16,10 @@ const Login = () => { const [loading, setLoading] = useState(false); const [error, setError] = useState(''); const navigate = useNavigate(); + const dispatch = useDispatch(); - // چک کردن token موقع لود شدن صفحه - useEffect(() => { - const token = localStorage.getItem('token'); - // اگر قبلاً لاگین کرده، به داشبورد هدایت میشه - if (token) { - navigate('/dashboard'); - } - }, [navigate]); - const handleLogin = (e) => { + const handleLogin = async (e) => { e.preventDefault(); setError(''); setLoading(true); @@ -35,50 +29,31 @@ const Login = () => { password: password }; - console.log('Sending login request with:', loginData); + try { + const res = await authService.login(loginData); + if (res.data.success && res.data.data && res.data.data.tokens.accessToken) { + dispatch(setToken(res.data.data.tokens.accessToken)); + navigate('/dashboard'); + dispatch(setProfile(res.data.data.admin)); + console.log('login:', res.data.data.admin); + } - axios.post('/api/admin/v1/login', loginData) - .then((response) => { - setLoading(false); - console.log('✅ Login successful!'); - console.log('Response:', response.data); + console.log('Response:', res); + } catch (error) { + setLoading(false); + console.error('❌ Login error:', error); + // if (error.response) { + // setError(error.response.data.message || error.response.data.en_message || 'نام کاربری یا رمز عبور اشتباه است'); + // } else if (error.request) { + // setError('خطا در برقراری ارتباط با سرور'); + // } else { + // console.error('Error setting up request'); + // setError('خطای نامشخص رخ داده است'); + // } + } finally { + setLoading(false); + } - if (response.data.success && response.data.data && response.data.data.tokens) { - const accessToken = response.data.data.tokens.accessToken; - 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('پاسخ سرور معتبر نیست'); - } - }) - .catch((error) => { - setLoading(false); - console.error('❌ Login error:', error); - - if (error.response) { - console.error('Response status:', error.response.status); - console.error('Response data:', error.response.data); - setError(error.response.data.message || error.response.data.en_message || 'نام کاربری یا رمز عبور اشتباه است'); - } else if (error.request) { - console.error('No response received from server'); - setError('خطا در برقراری ارتباط با سرور'); - } else { - console.error('Error setting up request'); - setError('خطای نامشخص رخ داده است'); - } - }); }; return ( @@ -222,18 +197,6 @@ 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 ; - } - - console.log('ProtectedRoute - Rendering protected content'); - return children; -}; export default Login; \ No newline at end of file diff --git a/src/redux/slices/authSlice.js b/src/redux/slices/authSlice.js new file mode 100644 index 0000000..b0beeb5 --- /dev/null +++ b/src/redux/slices/authSlice.js @@ -0,0 +1,26 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const token = localStorage.getItem("token"); + +const authSlice = createSlice({ + name: "auth", + initialState: { + token: token, + isAuthenticated: !!token, + }, + reducers: { + setToken(state, action) { + state.token = action.payload; + state.isAuthenticated = true; + localStorage.setItem("token", action.payload); + }, + clearToken(state) { + state.token = null; + state.isAuthenticated = false; + localStorage.removeItem("token"); + }, + }, +}); + +export const { setToken, clearToken } = authSlice.actions; +export default authSlice.reducer; diff --git a/src/redux/slices/loadingSlice.js b/src/redux/slices/loadingSlice.js new file mode 100644 index 0000000..42d5cbe --- /dev/null +++ b/src/redux/slices/loadingSlice.js @@ -0,0 +1,13 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const loadingSlice = createSlice({ + name: "loading", + initialState: false, + reducers: { + showLoading: () => true, + hideLoading: () => false, + }, +}); + +export const { showLoading, hideLoading } = loadingSlice.actions; +export default loadingSlice.reducer; diff --git a/src/redux/slices/profileSlice.js b/src/redux/slices/profileSlice.js new file mode 100644 index 0000000..0647fba --- /dev/null +++ b/src/redux/slices/profileSlice.js @@ -0,0 +1,20 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const profileSlice = createSlice({ + name: "profile", + initialState: { + profile: null, + }, + reducers: { + setProfile(state, action) { + state.profile = action.payload; + }, + clearProfile(state) { + state.profile = null; + }, + + }, +}); + +export const { setProfile, clearProfile } = profileSlice.actions; +export default profileSlice.reducer; \ No newline at end of file diff --git a/src/redux/slices/toastSlice.js b/src/redux/slices/toastSlice.js new file mode 100644 index 0000000..3d8336d --- /dev/null +++ b/src/redux/slices/toastSlice.js @@ -0,0 +1,26 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const toastSlice = createSlice({ + name: "toast", + initialState: { + message: "", + type: "success", // success | error | info + visible: false, + }, + reducers: { + showToast(state, action) { + const { message, type } = action.payload; + state.message = message; + state.type = type || "success"; + state.visible = true; + }, + hideToast(state) { + state.visible = false; + state.message = ""; + state.type = "success"; + }, + }, +}); + +export const { showToast, hideToast } = toastSlice.actions; +export default toastSlice.reducer; diff --git a/src/redux/store.js b/src/redux/store.js new file mode 100644 index 0000000..805b369 --- /dev/null +++ b/src/redux/store.js @@ -0,0 +1,14 @@ +import { configureStore } from "@reduxjs/toolkit"; +import authReducer from "./slices/authSlice"; +import loadingReducer from "./slices/loadingSlice"; +import toastReducer from "./slices/toastSlice"; +import profileReducer from "./slices/profileSlice"; + +export const store = configureStore({ + reducer: { + auth: authReducer, + loading: loadingReducer, + toast: toastReducer, + profile: profileReducer, + }, +}); diff --git a/src/routes/ProtectedRoute/ProtectedRoute.jsx b/src/routes/ProtectedRoute/ProtectedRoute.jsx new file mode 100644 index 0000000..513a6b6 --- /dev/null +++ b/src/routes/ProtectedRoute/ProtectedRoute.jsx @@ -0,0 +1,15 @@ +import { Navigate } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { useProfile } from "../../hooks/useProfile"; + +export default function ProtectedRoute({ children }) { + const isAuth = useSelector((state) => state.auth.isAuthenticated); + const token = localStorage.getItem("token"); + + useProfile(isAuth && token); + + if (!isAuth || !token) { + return ; + } + return children; +} diff --git a/src/routes/Routes.jsx b/src/routes/Routes.jsx new file mode 100644 index 0000000..6a4e07d --- /dev/null +++ b/src/routes/Routes.jsx @@ -0,0 +1,28 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; +import ProtectedRoute from './ProtectedRoute/ProtectedRoute'; +import Login from '../pages/Login/Login'; +import Layout from '../components/Layout/Layout'; +import Dashboard from '../pages/Dashboard/Dashboard'; +import CafeManagement from '../pages/CafeManagement/CafeManagement'; +import EditCafe from '../pages/CafeManagement/EditCafe'; +import Stats from '../pages/Stats/Stats'; + +export default function AppRoutes() { + return ( + + } /> + + }> + {/* }> */} + } /> + } /> + } /> + } /> + + } /> + + + } /> + + ); +} \ No newline at end of file diff --git a/src/services/api/base-api.js b/src/services/api/base-api.js new file mode 100644 index 0000000..a3af15e --- /dev/null +++ b/src/services/api/base-api.js @@ -0,0 +1,115 @@ +import axios from "axios"; +import { store } from "../../redux/store"; +import { showLoading, hideLoading } from "../../redux/slices/loadingSlice"; +import { clearToken } from "../../redux/slices/authSlice"; +import { showToast } from "../../redux/slices/toastSlice"; +import { toastIgnore } from "./toastIgnore"; + +const BASE_URL = 'https://cafeju.maksiran.ir/api'; +const TIMEOUT = 600000; + +axios.defaults.baseURL = BASE_URL; +axios.defaults.timeout = TIMEOUT; + +// Helpers +const isMutatingMethod = (method) => ["post", "put", "delete", "patch"].includes(method?.toLowerCase()); + +const extractMessage = (obj) => + obj?.response?.data?.data?.message || + obj?.data?.data?.message || + obj?.message || + obj?.data?.message || + obj?.response?.data?.message || + obj?.response?.message || + "خطایی رخ داده است!"; + +const getErrorMessageByStatus = (status, data) => { + switch (status) { + case 400: return data?.message || "درخواست نامعتبر است"; + case 401: return data?.message || "نشست شما منقضی شده، مجدد وارد شوید"; + case 403: return data?.message || "دسترسی شما محدود شده است"; + case 404: return data?.message || "صفحه یا اطلاعات درخواستی یافت نشد"; + case 422: return data?.message || "اطلاعات ارسالی نامعتبر است"; + case 429: return data?.message || "تعداد درخواست‌ها زیاد است، کمی صبر کنید"; + case 500: return data?.message || "خطای داخلی سرور، لطفاً بعداً تلاش کنید"; + case 502: + case 503: return data?.message || "سرور در حال حاضر در دسترس نیست"; + case 504: return data?.message || "سرور پاسخ نداد، دوباره تلاش کنید"; + default: return data?.message || `خطای غیرمنتظره (کد: ${status})`; + } +}; + +// Request Interceptor +axios.interceptors.request.use( + (config) => { + const token = localStorage.getItem("token"); + if (token) config.headers.Authorization = `Bearer ${token}`; + store.dispatch(showLoading()); + return config; + }, + (error) => { + store.dispatch(hideLoading()); + return Promise.reject(error); + } +); + +// Response Interceptor +axios.interceptors.response.use( + (response) => { + try { + const method = response.config.method; + const url = response.config.url; + if (!toastIgnore.some(u => url.includes(u)) && isMutatingMethod(method)) { + const message = extractMessage(response) || "عملیات با موفقیت انجام شد"; + store.dispatch(showToast({ type: "success", message })); + } + } catch (e) { + console.error("toast response error:", e); + } finally { + store.dispatch(hideLoading()); + } + return response; + }, + (error) => { + store.dispatch(hideLoading()); + + if (!error.response) { + const networkMessage = error.message.includes("Network Error") + ? "لطفا اتصال اینترنت خود را بررسی کنید" + : error.message.includes("timeout") + ? "مهلت زمانی درخواست تمام شد، دوباره تلاش کنید" + : "خطا در برقراری ارتباط با سرور"; + + store.dispatch(showToast({ message: networkMessage, type: "error" })); + return Promise.reject(error); + } + + const { status, data } = error.response; + const url = error.config?.url || ""; + + if (!toastIgnore.some(u => url.includes(u))) { + const message = getErrorMessageByStatus(status, data); + store.dispatch(showToast({ type: "error", message })); + } + + if ([401, 403].includes(status)) store.dispatch(clearToken()); + + return Promise.reject(error); + } +); + +// Request Wrappers +const requests = { + get: (url) => axios.get(url), + getByParams: (url, params) => axios.get(url, { params }), + post: (url, body) => axios.post(url, body), + put: (url, body) => axios.put(url, body), + patch: (url, body) => axios.patch(url, body), + delete: (url) => axios.delete(url), + postFormData: (url, formData) => + axios.post(url, formData, { headers: { "Content-Type": "multipart/form-data" } }), + putMedia: (url, body) => + axios.put(url, body, { headers: { "Content-Type": "application/x-www-form-urlencoded" } }), +}; + +export default requests; \ No newline at end of file diff --git a/src/services/api/toastIgnore.js b/src/services/api/toastIgnore.js new file mode 100644 index 0000000..8daf49c --- /dev/null +++ b/src/services/api/toastIgnore.js @@ -0,0 +1,3 @@ +export const toastIgnore = [ + // "/posts/like", +]; \ No newline at end of file diff --git a/src/services/auth.js b/src/services/auth.js new file mode 100644 index 0000000..0165c1a --- /dev/null +++ b/src/services/auth.js @@ -0,0 +1,15 @@ +import requests from "./api/base-api"; + +const authService = { + // register: (userData) => requests.post("/auth/register", userData), + // verifyOTP: (data) => requests.post("/auth/verify-otp", data), + + login: (data) => requests.post("/admin/v1/login", data), + + // forgotPassword: (data) => requests.post("/auth/forgot-password", data), + // resetPassword: (data) => requests.post("/auth/reset-password", data), + + logout: () => requests.post("/auth/logout"), +}; + +export default authService; \ No newline at end of file diff --git a/src/services/cafe.js b/src/services/cafe.js new file mode 100644 index 0000000..ecde669 --- /dev/null +++ b/src/services/cafe.js @@ -0,0 +1,8 @@ +import requests from "./api/base-api"; + +const cafeService = { + getCafeList: () => requests.get("/cafe/v1/get-cafe-list"), + editCafe: (id, cafeData) => requests.put(`/cafe/v1/get-cafe-profile-by-cafe/${id}`, cafeData), +}; + +export default cafeService; \ No newline at end of file diff --git a/src/services/dashboard.js b/src/services/dashboard.js new file mode 100644 index 0000000..e03ca82 --- /dev/null +++ b/src/services/dashboard.js @@ -0,0 +1,7 @@ +import requests from "./api/base-api"; + +const dashboardService = { + getCafeList: () => requests.get("/cafe/v1/get-cafe-list"), +}; + +export default dashboardService; \ No newline at end of file diff --git a/src/services/profile.js b/src/services/profile.js new file mode 100644 index 0000000..0769d4a --- /dev/null +++ b/src/services/profile.js @@ -0,0 +1,10 @@ +import requests from "./api/base-api"; + +const profileService = { + getProfile: () => requests.get("/admin/v1/get-my-profile"), + deleteProfile: () => requests.delete("/admin/v1/delete-my-profile"), + // updateProfile: (profileData) => requests.put("/admin/v1/update-profile", profileData), + // changePassword: (passwordData) => requests.post("/admin/v1/change-password", passwordData), +}; + +export default profileService; \ No newline at end of file diff --git a/src/styles/App.css b/src/styles/App.css deleted file mode 100644 index 36813f5..0000000 --- a/src/styles/App.css +++ /dev/null @@ -1,112 +0,0 @@ -@tailwind utilities; - -/* Custom Breakpoints for Mobile-First Design */ -@layer base { - /* 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; - } -} - -/* Custom Responsive Classes */ -@layer utilities { - /* Hide scrollbar globally */ - .scrollbar-hide::-webkit-scrollbar { - display: none; - } - - .scrollbar-hide { - -ms-overflow-style: none; - scrollbar-width: none; - } - - /* Prevent horizontal scroll */ - .no-scroll-x { - overflow-x: hidden; - max-width: 100vw; - } -} - -/* Responsive Container Sizes */ -@layer components { - .container-responsive { - width: 100%; - margin: 0 auto; - padding-left: 1rem; - padding-right: 1rem; - } - - /* iPhone SE and smaller (320px - 374px) */ - @media (min-width: 320px) { - .container-responsive { - max-width: 320px; - } - } - - /* iPhone 12/13 Mini (375px - 389px) */ - @media (min-width: 375px) { - .container-responsive { - max-width: 375px; - } - } - - /* iPhone 14 (390px - 429px) */ - @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; - } - } -} \ No newline at end of file