From 8e19500f7c30657be0159aea46dd566a20bf4225 Mon Sep 17 00:00:00 2001 From: ghazall-ag Date: Tue, 23 Dec 2025 23:29:58 +0330 Subject: [PATCH] feat(TopUpAgent): add , edit, delete , toggle ,... --- README.md | 144 +- node_modules/.vite/deps/_metadata.json | 62 +- node_modules/.vite/deps/recharts.js | 6 +- src/App.jsx | 68 +- src/components/Navbar.jsx | 5 + src/components/Sidebar.jsx | 17 +- src/pages/Currency.jsx | 658 --------- src/pages/Issuer.jsx | 1788 ------------------------ src/pages/Login.jsx | 241 +++- src/pages/Roles.jsx | 392 ------ src/pages/Settings.jsx | 352 ----- src/pages/TopUpAgent.jsx | 724 ++++++++++ src/pages/Transactions.jsx | 277 ---- src/pages/Users.jsx | 332 ----- src/services/api.js | 9 +- src/services/apiClient.js | 72 +- src/services/authAPI.js | 60 +- src/services/cityAPI.js | 5 - src/services/countryAPI.js | 3 - src/services/currencyAPI.js | 112 +- src/services/issuerAPI.js | 582 -------- src/services/paymentsAPI.js | 64 +- src/services/provinceAPI.js | 5 - src/services/rolesAPI.js | 49 - src/services/topUpAgentAPI.js | 209 +++ src/services/usersAPI.js | 87 -- vite.config.js | 4 +- 27 files changed, 1462 insertions(+), 4865 deletions(-) delete mode 100644 src/pages/Currency.jsx delete mode 100644 src/pages/Issuer.jsx delete mode 100644 src/pages/Roles.jsx delete mode 100644 src/pages/Settings.jsx create mode 100644 src/pages/TopUpAgent.jsx delete mode 100644 src/pages/Transactions.jsx delete mode 100644 src/pages/Users.jsx delete mode 100644 src/services/issuerAPI.js delete mode 100644 src/services/rolesAPI.js create mode 100644 src/services/topUpAgentAPI.js delete mode 100644 src/services/usersAPI.js diff --git a/README.md b/README.md index 5089b1a5..99323e3e 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,146 @@ -For testing purposes, you can use these demo credentials: -- **Email**: admin@example.com -- **Password**: password +# Payment Admin Panel Dashboard -## Project Structure +یک داشبورد مدیریتی مدرن برای مدیریت سیستم پرداخت با رابط کاربری زیبا و کاربردی. + +## ویژگی‌ها + +- 🔐 سیستم احراز هویت کامل (ورود و بازیابی رمز عبور) +- 📊 داشبورد با نمودارها و آمار +- 📍 مدیریت موقعیت‌های جغرافیایی (کشور، استان، شهر) +- 🏢 مدیریت صادرکنندگان +- 🌙 پشتیبانی از حالت تاریک +- 📱 طراحی واکنش‌گرا (Responsive) + +## تکنولوژی‌های استفاده شده + +- **React 18** - کتابخانه UI +- **Vite** - ابزار ساخت و توسعه +- **React Router DOM** - مسیریابی +- **Zustand** - مدیریت state +- **Axios** - درخواست‌های HTTP +- **Tailwind CSS** - استایل‌دهی +- **Recharts** - نمودارها +- **Lucide React** - آیکون‌ها +- **React Toastify** - اعلان‌ها + +## نصب و راه‌اندازی + +### پیش‌نیازها + +- Node.js (نسخه 16 یا بالاتر) +- npm یا yarn + +### مراحل نصب + +1. کلون کردن پروژه: +```bash +git clone +cd dashboard-issuer +``` + +2. نصب وابستگی‌ها: +```bash +npm install +``` + +3. اجرای پروژه در حالت توسعه: +```bash +npm run dev +``` + +4. ساخت نسخه production: +```bash +npm run build +``` + +5. پیش‌نمایش نسخه production: +```bash +npm run preview +``` + +## دستورات موجود + +- `npm run dev` - اجرای سرور توسعه +- `npm run build` - ساخت نسخه production +- `npm run preview` - پیش‌نمایش نسخه production +- `npm run lint` - بررسی کد با ESLint + +## اطلاعات ورود تست + +برای تست سیستم می‌توانید از این اطلاعات استفاده کنید: +- **ایمیل**: admin@example.com +- **رمز عبور**: password + +## ساختار پروژه ``` -src// +src/ ├── components/ │ ├── Navbar.jsx │ ├── Sidebar.jsx │ └── DataTable.jsx ├── pages/ │ ├── Dashboard.jsx -│ ├── Transactions.jsx -│ ├── Settings.jsx -│ └── Login.jsx +│ ├── Login.jsx +│ ├── ForgotPassword.jsx +│ ├── Location.jsx +│ └── Issuer.jsx ├── context/ │ └── AuthContext.jsx +├── store/ +│ └── authStore.js ├── services/ -│ └── api.js +│ ├── api.js +│ ├── apiClient.js +│ ├── authAPI.js +│ ├── issuerAPI.js +│ ├── countryAPI.js +│ ├── provinceAPI.js +│ ├── cityAPI.js +│ ├── currencyAPI.js +│ ├── permissionsAPI.js +│ ├── generalAPI.js +│ └── paymentsAPI.js +├── utils/ +│ └── errorHandler.js ├── App.jsx -├── main.jsx -└── index.css +├── main.jsx +├── index.css +└── layout.css ``` -. + +## صفحات و مسیرها + +- `/` - داشبورد اصلی +- `/login` - صفحه ورود +- `/forgot-password` - بازیابی رمز عبور +- `/location` - مدیریت موقعیت‌ها (کشور، استان، شهر) +- `/issuer` - مدیریت صادرکنندگان + +## API Services + +پروژه از سرویس‌های API زیر استفاده می‌کند: + +- **authAPI.js** - احراز هویت و مدیریت کاربر +- **issuerAPI.js** - مدیریت صادرکنندگان +- **countryAPI.js** - مدیریت کشورها +- **provinceAPI.js** - مدیریت استان‌ها +- **cityAPI.js** - مدیریت شهرها +- **currencyAPI.js** - مدیریت ارزها +- **permissionsAPI.js** - مدیریت دسترسی‌ها +- **generalAPI.js** - سرویس‌های عمومی +- **paymentsAPI.js** - مدیریت پرداخت‌ها و آمار داشبورد +- **apiClient.js** - کلاینت اصلی API +- **api.js** - تنظیمات پایه API + +## توسعه + +پروژه از ESLint برای بررسی کیفیت کد استفاده می‌کند. قبل از commit کردن تغییرات، حتماً کد را بررسی کنید: + +```bash +npm run lint +``` + +## مجوز + +این پروژه خصوصی است. diff --git a/node_modules/.vite/deps/_metadata.json b/node_modules/.vite/deps/_metadata.json index 88c1cd7b..cf2a2793 100644 --- a/node_modules/.vite/deps/_metadata.json +++ b/node_modules/.vite/deps/_metadata.json @@ -1,81 +1,81 @@ { - "hash": "5dc29ded", - "browserHash": "8decdfc6", + "hash": "0b559729", + "browserHash": "99577114", "optimized": { "react/jsx-runtime": { "src": "../../react/jsx-runtime.js", "file": "react_jsx-runtime.js", - "fileHash": "071542c9", + "fileHash": "ce1a804a", "needsInterop": true }, "react/jsx-dev-runtime": { "src": "../../react/jsx-dev-runtime.js", "file": "react_jsx-dev-runtime.js", - "fileHash": "2c0ef184", + "fileHash": "e0dd02c9", "needsInterop": true }, "react": { "src": "../../react/index.js", "file": "react.js", - "fileHash": "97079daa", + "fileHash": "ebfb96cd", "needsInterop": true }, "axios": { "src": "../../axios/index.js", "file": "axios.js", - "fileHash": "89ede243", + "fileHash": "27c8a3be", "needsInterop": false }, "lucide-react": { "src": "../../lucide-react/dist/esm/lucide-react.mjs", "file": "lucide-react.js", - "fileHash": "6ec5bbdf", + "fileHash": "afb40087", "needsInterop": false }, "react-dom/client": { "src": "../../react-dom/client.js", "file": "react-dom_client.js", - "fileHash": "cca5920c", + "fileHash": "da86903f", "needsInterop": true }, "react-router-dom": { "src": "../../react-router-dom/dist/index.js", "file": "react-router-dom.js", - "fileHash": "d17c5b44", - "needsInterop": false - }, - "recharts": { - "src": "../../recharts/es6/index.js", - "file": "recharts.js", - "fileHash": "93550599", - "needsInterop": false - }, - "zustand": { - "src": "../../zustand/esm/index.mjs", - "file": "zustand.js", - "fileHash": "80080d68", - "needsInterop": false - }, - "zustand/middleware": { - "src": "../../zustand/esm/middleware.mjs", - "file": "zustand_middleware.js", - "fileHash": "1fdb7da8", + "fileHash": "20f961ff", "needsInterop": false }, "react-toastify": { "src": "../../react-toastify/dist/index.mjs", "file": "react-toastify.js", - "fileHash": "af965fd8", + "fileHash": "92e7f604", + "needsInterop": false + }, + "recharts": { + "src": "../../recharts/es6/index.js", + "file": "recharts.js", + "fileHash": "92750bad", + "needsInterop": false + }, + "zustand": { + "src": "../../zustand/esm/index.mjs", + "file": "zustand.js", + "fileHash": "815a60b7", + "needsInterop": false + }, + "zustand/middleware": { + "src": "../../zustand/esm/middleware.mjs", + "file": "zustand_middleware.js", + "fileHash": "5bd61bb2", "needsInterop": false } }, "chunks": { - "chunk-YV3COZNF": { - "file": "chunk-YV3COZNF.js" - }, "chunk-ZXN67JHZ": { "file": "chunk-ZXN67JHZ.js" }, + "chunk-YV3COZNF": { + "file": "chunk-YV3COZNF.js" + }, "chunk-WQMOH32Y": { "file": "chunk-WQMOH32Y.js" }, diff --git a/node_modules/.vite/deps/recharts.js b/node_modules/.vite/deps/recharts.js index 1e868fbe..ebe09230 100644 --- a/node_modules/.vite/deps/recharts.js +++ b/node_modules/.vite/deps/recharts.js @@ -1,9 +1,9 @@ -import { - clsx_default -} from "./chunk-YV3COZNF.js"; import { require_react_dom } from "./chunk-ZXN67JHZ.js"; +import { + clsx_default +} from "./chunk-YV3COZNF.js"; import { require_react } from "./chunk-WQMOH32Y.js"; diff --git a/src/App.jsx b/src/App.jsx index 1495186e..98f2ea6f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -5,14 +5,10 @@ import Sidebar from './components/Sidebar'; import Navbar from './components/Navbar'; import Login from './pages/Login'; import Dashboard from './pages/Dashboard'; -import Transactions from './pages/Transactions'; -import Settings from './pages/Settings'; + import ForgotPassword from './pages/ForgotPassword'; -import Roles from './pages/Roles'; -import Users from './pages/Users'; -import Currency from './pages/Currency'; import Location from './pages/Location'; -import Issuer from './pages/Issuer'; +import TopUpAgent from './pages/TopUpAgent'; import { AuthProvider } from './context/AuthContext'; // Protected Route Component @@ -57,7 +53,6 @@ const Layout = ({ children }) => { - ); }; @@ -77,57 +72,6 @@ const AppRoutes = () => { } /> - - - - - - } - /> - - - - - - } - /> - - - - - - } - /> - - - - - - - } - /> - - - - - - } - /> { } /> - + } - /> - - + /> } /> ); diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index f8a9bac7..2d254c24 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -102,6 +102,11 @@ const Navbar = ({ onSidebarToggle }) => { {userEmail}

)} + {isLoggedIn && userData.issuerName && ( +

+ Issuer: {userData.issuerName} +

+ )} {isLoggedIn && userData.role && (

نقش: {userData.role} diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index d3ebac90..9acac08c 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -2,27 +2,16 @@ import React from 'react'; import { NavLink } from 'react-router-dom'; import { LayoutDashboard, - CreditCard, - Settings, - Menu, - X, - Users, - Shield, - DollarSign, MapPin, - Building2 + Wallet, + X, } from 'lucide-react'; const Sidebar = ({ isOpen, onToggle }) => { const navigation = [ { name: 'Dashboard', href: '/', icon: LayoutDashboard }, - // { name: 'Transactions', href: '/transactions', icon: CreditCard }, - { name: 'Roles', href: '/roles', icon: Shield }, - { name: 'Users', href: '/users', icon: Users }, - { name: 'Currency', href: '/currency', icon: DollarSign }, { name: 'Location', href: '/location', icon: MapPin }, - { name: 'Issuer', href: '/issuer', icon: Building2 }, - // { name: 'Settings', href: '/settings', icon: Settings }, + { name: 'TopUp Agent', href: '/topup-agent', icon: Wallet }, ]; return ( diff --git a/src/pages/Currency.jsx b/src/pages/Currency.jsx deleted file mode 100644 index 4becde99..00000000 --- a/src/pages/Currency.jsx +++ /dev/null @@ -1,658 +0,0 @@ -import React, { useEffect, useMemo, useState, useCallback } from 'react'; -import DataTable from '../components/DataTable'; -import { currencyAPI } from '../services/api'; -import { Plus, Search, Power, Wrench, Settings, DollarSign, Shield } from 'lucide-react'; -import { ToastContainer, toast } from 'react-toastify'; -import 'react-toastify/dist/ReactToastify.css'; -import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler'; - -// debounce ساده -function debounce(func, delay) { - let timer; - return function (...args) { - clearTimeout(timer); - timer = setTimeout(() => func.apply(this, args), delay); - }; -} - -const Currency = () => { - const [currencies, setCurrencies] = useState([]); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [filter, setFilter] = useState(''); - - const [addForm, setAddForm] = useState({ currencyCode: '' }); - const [isModalOpen, setIsModalOpen] = useState(false); - - // Modals for editing - const [permissionsModal, setPermissionsModal] = useState(null); - const [limitsModal, setLimitsModal] = useState(null); - const [feesModal, setFeesModal] = useState(null); - const [maintenanceModal, setMaintenanceModal] = useState(null); - const [maintenanceMessage, setMaintenanceMessage] = useState(''); - - // Form states for modals - const [permissionsForm, setPermissionsForm] = useState({ - depositEnabled: false, - cashOutEnabled: false, - exchangeEnabled: false, - purchaseEnabled: false, - transferFromEnabled: false, - transferToEnabled: false, - voucherCreationEnabled: false, - voucherRedeemEnabled: false, - }); - const [limitsForm, setLimitsForm] = useState({ - depositLimits: { min: null, max: null }, - cashOutLimits: { min: null, max: null }, - transferLimits: { min: null, max: null }, - voucherLimits: { min: null, max: null }, - }); - const [feesForm, setFeesForm] = useState({ - depositFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, - cashOutFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, - transferFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, - exchangeFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, - generateVoucherFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, - expireVoucherSystemFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, - }); - - // دریافت ارزها - const fetchCurrencies = useCallback(async () => { - try { - setLoading(true); - const list = await currencyAPI.list(); - setCurrencies(Array.isArray(list) ? list : []); - setError(''); - } catch (err) { - console.error(err); - const errorMsg = getErrorMessage(err); - setError(errorMsg); - toast.error(errorMsg); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - fetchCurrencies(); - }, [fetchCurrencies]); - - // --- اضافه کردن ارز --- - const onAddCurrency = async (e) => { - e.preventDefault(); - const { currencyCode } = addForm; - const code = currencyCode.trim().toUpperCase(); - - if (!code) { - toast.error('Please enter a currency code'); - return; - } - - // اعتبارسنجی: کد ارز باید 3 حرف باشد - if (code.length !== 3) { - toast.error('Currency code must be exactly 3 characters (e.g., USD, EUR)'); - return; - } - - // بررسی وجود ارز در لیست (برای جلوگیری از درخواست غیرضروری) - const exists = currencies.some(c => c.currencyCode?.code?.toUpperCase() === code); - if (exists) { - toast.error(`Currency ${code} already exists`); - return; - } - - try { - const result = await currencyAPI.create(code); - await fetchCurrencies(); - setIsModalOpen(false); - setAddForm({ currencyCode: '' }); - toast.success(getSuccessMessage(result) || 'Currency added successfully'); - } catch (err) { - console.error('Error creating currency:', err); - toast.error(getErrorMessage(err)); - } - }; - - // --- تغییر وضعیت فعال بودن --- - const onToggleActivation = async (currencyCode) => { - try { - const result = await currencyAPI.toggleActivation(currencyCode); - await fetchCurrencies(); - toast.success(getSuccessMessage(result) || 'Currency activation status updated'); - } catch (err) { - toast.error(getErrorMessage(err)); - } - }; - - // --- فعال/غیرفعال کردن Maintenance --- - const onEnableMaintenance = async (currencyCode, message = '') => { - try { - const result = await currencyAPI.enableMaintenance(currencyCode, message || null); - await fetchCurrencies(); - setMaintenanceModal(null); - setMaintenanceMessage(''); - toast.success(getSuccessMessage(result) || 'Maintenance enabled'); - } catch (err) { - toast.error(getErrorMessage(err)); - } - }; - - const onDisableMaintenance = async (currencyCode) => { - try { - const result = await currencyAPI.disableMaintenance(currencyCode); - await fetchCurrencies(); - setMaintenanceModal(null); - setMaintenanceMessage(''); - toast.success(getSuccessMessage(result) || 'Maintenance disabled'); - } catch (err) { - toast.error(getErrorMessage(err)); - } - }; - - // --- به‌روزرسانی Permissions --- - const onUpdatePermissions = async (currencyCode, permissions) => { - try { - const result = await currencyAPI.updatePermissions(currencyCode, permissions); - await fetchCurrencies(); - setPermissionsModal(null); - toast.success(getSuccessMessage(result) || 'Permissions updated successfully'); - } catch (err) { - toast.error(getErrorMessage(err)); - } - }; - - // --- به‌روزرسانی Limits --- - const onUpdateLimits = async (currencyCode, limits) => { - try { - const result = await currencyAPI.updateLimits(currencyCode, limits); - await fetchCurrencies(); - setLimitsModal(null); - toast.success(getSuccessMessage(result) || 'Limits updated successfully'); - } catch (err) { - toast.error(getErrorMessage(err)); - } - }; - - // --- به‌روزرسانی Fees --- - const onUpdateFees = async (currencyCode, fees) => { - try { - const result = await currencyAPI.updateFees(currencyCode, fees); - await fetchCurrencies(); - setFeesModal(null); - toast.success(getSuccessMessage(result) || 'Fees updated successfully'); - } catch (err) { - toast.error(getErrorMessage(err)); - } - }; - - // --- ستون‌های جدول --- - const columns = useMemo(() => [ - { - key: 'currencyCode', - header: 'Currency', - render: (val) => ( -

-
{val?.code || '—'}
-
{val?.name || ''}
-
- ), - }, - { - key: 'currencyCode', - header: 'Symbol', - render: (val) => {val?.symbol || '—'}, - }, - { - key: 'isActive', - header: 'Active', - render: (val) => ( - - {val ? '✅ Yes' : '❌ No'} - - ), - }, - { - key: 'isUnderMaintenance', - header: 'Maintenance', - render: (val, row) => ( - - {val ? '⚠️ Yes' : '✓ No'} - - ), - }, - { - key: 'permissions', - header: 'Permissions', - render: (val) => { - if (!val) return '—'; - const enabled = Object.values(val).filter(Boolean).length; - return {enabled} enabled; - }, - }, - { - key: 'actions', - header: 'Actions', - render: (_val, row) => { - const code = row.currencyCode?.code; - if (!code) return '—'; - return ( -
- - - - - -
- ); - }, - }, - ], []); - - const handleFilterChange = useMemo(() => debounce(async (v) => { - // Filter is handled by DataTable component - }, 400), []); - - // --- مودال اضافه کردن ارز --- - const renderAddModal = () => { - if (!isModalOpen) return null; - return ( - <> -
setIsModalOpen(false)} /> -
-
-

Add Currency

-
-
- setAddForm({ currencyCode: e.target.value.toUpperCase() })} - className="w-full p-2 border rounded-lg" - placeholder="Currency Code (e.g., USD, EUR)" - maxLength={3} - pattern="[A-Z]{3}" - title="Enter a 3-letter currency code" - /> -

Enter a 3-letter ISO currency code

-
-
- - -
-
-
-
- - ); - }; - - // --- مودال Maintenance --- - const renderMaintenanceModal = () => { - if (!maintenanceModal) return null; - const code = maintenanceModal.currencyCode?.code; - - return ( - <> -
{ - setMaintenanceModal(null); - setMaintenanceMessage(''); - }} /> -
-
-

- Maintenance: {code} -

-
-
- -