fix(Issuer): correct AllowedCurrencies and Capabilities structure
This commit is contained in:
@@ -2,20 +2,15 @@ import React, { useState } from 'react';
|
|||||||
import { Menu, Sun, Moon, User, LogOut, ChevronDown } from 'lucide-react';
|
import { Menu, Sun, Moon, User, LogOut, ChevronDown } from 'lucide-react';
|
||||||
import { signOut } from '../services/api';
|
import { signOut } from '../services/api';
|
||||||
import { useAuthStore } from '../store/authStore';
|
import { useAuthStore } from '../store/authStore';
|
||||||
import { useAuth } from '../context/AuthContext';
|
|
||||||
|
|
||||||
const Navbar = ({ onSidebarToggle }) => {
|
const Navbar = ({ onSidebarToggle }) => {
|
||||||
let user = null;
|
|
||||||
try {
|
|
||||||
const authContext = useAuth();
|
|
||||||
user = authContext?.user || null;
|
|
||||||
} catch (e) {
|
|
||||||
// useAuth not available, will use localStorage
|
|
||||||
}
|
|
||||||
const userData = user || JSON.parse(localStorage.getItem('user') || '{}') || {};
|
|
||||||
const userName = userData.name && userData.name !== '...' ? userData.name : (userData.email || '...');
|
|
||||||
const userEmail = userData.email || '';
|
|
||||||
const isLoggedIn = useAuthStore((s) => s.isLoggedIn);
|
const isLoggedIn = useAuthStore((s) => s.isLoggedIn);
|
||||||
|
const user = useAuthStore((s) => s.user);
|
||||||
|
|
||||||
|
// Extract user information with fallbacks
|
||||||
|
const userData = user || {};
|
||||||
|
const userName = userData.name || userData.userName || userData.email?.split('@')[0] || 'کاربر';
|
||||||
|
const userEmail = userData.email || userData.userName || '';
|
||||||
const [isDarkMode, setIsDarkMode] = useState(() => {
|
const [isDarkMode, setIsDarkMode] = useState(() => {
|
||||||
return localStorage.getItem('theme') === 'dark' ||
|
return localStorage.getItem('theme') === 'dark' ||
|
||||||
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
(!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
@@ -39,6 +34,8 @@ const Navbar = ({ onSidebarToggle }) => {
|
|||||||
|
|
||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
|
const logout = useAuthStore.getState().logout;
|
||||||
|
logout(); // Clear user data from store
|
||||||
await signOut();
|
await signOut();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// no-op; interceptor handles redirect and state
|
// no-op; interceptor handles redirect and state
|
||||||
@@ -59,7 +56,7 @@ const Navbar = ({ onSidebarToggle }) => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<h1 className="text-lg font-semibold text-gray-900 dark:text-white lg:hidden">
|
<h1 className="text-lg font-semibold text-gray-900 dark:text-white lg:hidden">
|
||||||
KHalij Payment
|
KHalij Finance
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -95,21 +92,28 @@ const Navbar = ({ onSidebarToggle }) => {
|
|||||||
|
|
||||||
{/* Dropdown menu */}
|
{/* Dropdown menu */}
|
||||||
{isUserMenuOpen && (
|
{isUserMenuOpen && (
|
||||||
<div className="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50">
|
<div className="absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 rounded-lg shadow-lg border border-gray-200 dark:border-gray-700 py-1 z-50">
|
||||||
<div className="px-4 py-2 border-b border-gray-200 dark:border-gray-700">
|
<div className="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
||||||
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
<p className="text-sm font-medium text-gray-900 dark:text-white">
|
||||||
{isLoggedIn ? userName : 'Guest'}
|
{isLoggedIn ? userName : 'Guest'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
{isLoggedIn && userEmail && (
|
||||||
{isLoggedIn ? userEmail : ''}
|
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
{userEmail}
|
||||||
</p>
|
</p>
|
||||||
|
)}
|
||||||
|
{isLoggedIn && userData.role && (
|
||||||
|
<p className="text-xs text-gray-400 dark:text-gray-500 mt-1">
|
||||||
|
نقش: {userData.role}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
className="w-full flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors duration-200"
|
className="w-full flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700 transition-colors duration-200"
|
||||||
>
|
>
|
||||||
<LogOut className="mr-2 h-4 w-4" />
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
Sign out
|
خروج
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ const Sidebar = ({ isOpen, onToggle }) => {
|
|||||||
`}>
|
`}>
|
||||||
<div className="flex items-center justify-between h-16 px-6 border-b border-gray-200 dark:border-gray-700">
|
<div className="flex items-center justify-between h-16 px-6 border-b border-gray-200 dark:border-gray-700">
|
||||||
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
|
<h1 className="text-xl font-bold text-gray-900 dark:text-white">
|
||||||
KHalij Payment
|
KHalij Finance
|
||||||
</h1>
|
</h1>
|
||||||
<button
|
<button
|
||||||
onClick={onToggle}
|
onClick={onToggle}
|
||||||
|
|||||||
35
src/main.jsx
35
src/main.jsx
@@ -3,6 +3,41 @@ import ReactDOM from 'react-dom/client'
|
|||||||
import App from './App.jsx'
|
import App from './App.jsx'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
|
// Global error handler برای unhandled promise rejections
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.addEventListener('unhandledrejection', (event) => {
|
||||||
|
// فیلتر کردن خطاهای مربوط به extension های مرورگر
|
||||||
|
const errorMessage = event.reason?.message || '';
|
||||||
|
const isExtensionError = errorMessage.includes('Could not establish connection') ||
|
||||||
|
errorMessage.includes('Receiving end does not exist') ||
|
||||||
|
errorMessage.includes('Extension context invalidated');
|
||||||
|
|
||||||
|
if (isExtensionError) {
|
||||||
|
// جلوگیری از نمایش خطاهای extension
|
||||||
|
event.preventDefault();
|
||||||
|
console.warn('Extension error suppressed:', errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// برای خطاهای دیگر، لاگ کنیم اما preventDefault نکنیم
|
||||||
|
console.error('Unhandled promise rejection:', event.reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global error handler برای خطاهای عمومی
|
||||||
|
window.addEventListener('error', (event) => {
|
||||||
|
const errorMessage = event.message || '';
|
||||||
|
const isExtensionError = errorMessage.includes('Could not establish connection') ||
|
||||||
|
errorMessage.includes('Receiving end does not exist') ||
|
||||||
|
errorMessage.includes('Extension context invalidated');
|
||||||
|
|
||||||
|
if (isExtensionError) {
|
||||||
|
event.preventDefault();
|
||||||
|
console.warn('Extension error suppressed:', errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import DataTable from '../components/DataTable';
|
import DataTable from '../components/DataTable';
|
||||||
import { issuerAPI, cityAPI, currencyAPI } from '../services/api';
|
import { issuerAPI, cityAPI, currencyAPI, countryAPI, provinceAPI, generalAPI } from '../services/api';
|
||||||
import { Plus, Trash2, Search, Pencil, Power, Settings, DollarSign } from 'lucide-react';
|
import { Plus, Trash2, Search, Pencil, Power, Settings, DollarSign, Filter } from 'lucide-react';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
|
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
|
||||||
@@ -12,6 +12,20 @@ const Issuer = () => {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [filter, setFilter] = useState('');
|
const [filter, setFilter] = useState('');
|
||||||
|
|
||||||
|
// Filter states
|
||||||
|
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
|
||||||
|
const [filterState, setFilterState] = useState({
|
||||||
|
countryId: '',
|
||||||
|
provinceId: '',
|
||||||
|
cityId: '',
|
||||||
|
isActive: '',
|
||||||
|
supportCurrencyCode: '',
|
||||||
|
supportCapabilities: ''
|
||||||
|
});
|
||||||
|
const [countries, setCountries] = useState([]);
|
||||||
|
const [provinces, setProvinces] = useState([]);
|
||||||
|
const [issuerCapabilities, setIssuerCapabilities] = useState([]);
|
||||||
|
|
||||||
const [addForm, setAddForm] = useState({
|
const [addForm, setAddForm] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
supportEmail: '',
|
supportEmail: '',
|
||||||
@@ -38,15 +52,27 @@ const Issuer = () => {
|
|||||||
const [selectedCapabilities, setSelectedCapabilities] = useState([]);
|
const [selectedCapabilities, setSelectedCapabilities] = useState([]);
|
||||||
const [selectedCurrencies, setSelectedCurrencies] = useState([]);
|
const [selectedCurrencies, setSelectedCurrencies] = useState([]);
|
||||||
const [capabilitiesData, setCapabilitiesData] = useState([]); // Store full capabilities data from API
|
const [capabilitiesData, setCapabilitiesData] = useState([]); // Store full capabilities data from API
|
||||||
|
const [currenciesData, setCurrenciesData] = useState([]); // Store full currencies data from API
|
||||||
|
|
||||||
// Capabilities options
|
// Capabilities options - will be loaded from API
|
||||||
const capabilityOptions = ['TopUpAgent', 'VoucherIssuer', 'PaymentProcessor', 'WalletProvider'];
|
const capabilityOptions = issuerCapabilities.map(cap => cap.name);
|
||||||
|
|
||||||
// دریافت Issuers
|
// دریافت Issuers
|
||||||
const fetchIssuers = useCallback(async () => {
|
const fetchIssuers = useCallback(async (filters = {}) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const list = await issuerAPI.list();
|
const params = {};
|
||||||
|
if (filters.countryId) params.countryId = filters.countryId;
|
||||||
|
if (filters.provinceId) params.provinceId = filters.provinceId;
|
||||||
|
if (filters.cityId) params.cityId = filters.cityId;
|
||||||
|
if (filters.isActive !== undefined && filters.isActive !== null && filters.isActive !== '') {
|
||||||
|
params.isActive = filters.isActive === 'true' || filters.isActive === true;
|
||||||
|
}
|
||||||
|
if (filters.supportCurrencyCode) params.supportCurrencyCode = filters.supportCurrencyCode;
|
||||||
|
if (filters.supportCapabilities) params.supportCapabilities = filters.supportCapabilities;
|
||||||
|
|
||||||
|
console.log('🔵 Fetching issuers with filters:', params);
|
||||||
|
const list = await issuerAPI.list(params);
|
||||||
setIssuers(Array.isArray(list) ? list : []);
|
setIssuers(Array.isArray(list) ? list : []);
|
||||||
setError('');
|
setError('');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -65,8 +91,42 @@ const Issuer = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Load countries, provinces, and capabilities for filter modal
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchIssuers();
|
const loadCountries = async () => {
|
||||||
|
try {
|
||||||
|
const countriesList = await countryAPI.listAll();
|
||||||
|
setCountries(Array.isArray(countriesList) ? countriesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load countries:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadProvinces = async () => {
|
||||||
|
try {
|
||||||
|
const provincesList = await provinceAPI.list({ currentPage: 1, pageSize: 1000 });
|
||||||
|
setProvinces(Array.isArray(provincesList) ? provincesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load provinces:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadCapabilities = async () => {
|
||||||
|
try {
|
||||||
|
const capabilitiesList = await generalAPI.getIssuerCapabilities();
|
||||||
|
setIssuerCapabilities(Array.isArray(capabilitiesList) ? capabilitiesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load issuer capabilities:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadCountries();
|
||||||
|
loadProvinces();
|
||||||
|
loadCapabilities();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchIssuers(filterState);
|
||||||
// Load cities from /api/v1/City
|
// Load cities from /api/v1/City
|
||||||
const loadCities = async () => {
|
const loadCities = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -87,20 +147,22 @@ const Issuer = () => {
|
|||||||
setCities([]);
|
setCities([]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// Load currencies
|
// Load currencies for filter (use generalAPI for better format)
|
||||||
const loadCurrencies = async () => {
|
const loadCurrencies = async () => {
|
||||||
|
try {
|
||||||
|
const currenciesList = await generalAPI.getCurrencies();
|
||||||
|
setCurrencies(Array.isArray(currenciesList) ? currenciesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load currencies from generalAPI:', err);
|
||||||
|
// Fallback to currencyAPI if generalAPI fails
|
||||||
try {
|
try {
|
||||||
const currenciesList = await currencyAPI.list();
|
const currenciesList = await currencyAPI.list();
|
||||||
setCurrencies(Array.isArray(currenciesList) ? currenciesList : []);
|
setCurrencies(Array.isArray(currenciesList) ? currenciesList : []);
|
||||||
} catch (err) {
|
} catch (err2) {
|
||||||
console.error('Failed to load currencies:', err);
|
console.error('Failed to load currencies from currencyAPI:', err2);
|
||||||
const status = err?.response?.status;
|
|
||||||
// Don't show toast for server errors (500) - it's a server issue
|
|
||||||
if (status !== 500) {
|
|
||||||
console.warn('Currency loading failed but continuing...');
|
|
||||||
}
|
|
||||||
setCurrencies([]);
|
setCurrencies([]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
loadCities();
|
loadCities();
|
||||||
loadCurrencies();
|
loadCurrencies();
|
||||||
@@ -115,7 +177,7 @@ const Issuer = () => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const newIssuer = await issuerAPI.create(addForm);
|
const newIssuer = await issuerAPI.create(addForm);
|
||||||
await fetchIssuers();
|
await fetchIssuers(filterState);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
setAddForm({
|
setAddForm({
|
||||||
name: '',
|
name: '',
|
||||||
@@ -178,7 +240,7 @@ const Issuer = () => {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const updatedIssuer = await issuerAPI.update(editingIssuer.id, editForm);
|
const updatedIssuer = await issuerAPI.update(editingIssuer.id, editForm);
|
||||||
await fetchIssuers();
|
await fetchIssuers(filterState);
|
||||||
setIsEditModalOpen(false);
|
setIsEditModalOpen(false);
|
||||||
setEditingIssuer(null);
|
setEditingIssuer(null);
|
||||||
toast.success(getSuccessMessage(updatedIssuer) || 'Issuer updated successfully');
|
toast.success(getSuccessMessage(updatedIssuer) || 'Issuer updated successfully');
|
||||||
@@ -192,7 +254,7 @@ const Issuer = () => {
|
|||||||
if (!window.confirm('Are you sure you want to delete this issuer?')) return;
|
if (!window.confirm('Are you sure you want to delete this issuer?')) return;
|
||||||
try {
|
try {
|
||||||
const result = await issuerAPI.remove(id);
|
const result = await issuerAPI.remove(id);
|
||||||
await fetchIssuers();
|
await fetchIssuers(filterState);
|
||||||
toast.success(getSuccessMessage(result) || 'Issuer deleted successfully');
|
toast.success(getSuccessMessage(result) || 'Issuer deleted successfully');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(getErrorMessage(err));
|
toast.error(getErrorMessage(err));
|
||||||
@@ -203,7 +265,7 @@ const Issuer = () => {
|
|||||||
const onToggleActivation = async (id) => {
|
const onToggleActivation = async (id) => {
|
||||||
try {
|
try {
|
||||||
const updated = await issuerAPI.toggleActivation(id);
|
const updated = await issuerAPI.toggleActivation(id);
|
||||||
await fetchIssuers();
|
await fetchIssuers(filterState);
|
||||||
toast.success(getSuccessMessage(updated) || 'Issuer status updated');
|
toast.success(getSuccessMessage(updated) || 'Issuer status updated');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(getErrorMessage(err));
|
toast.error(getErrorMessage(err));
|
||||||
@@ -264,7 +326,7 @@ const Issuer = () => {
|
|||||||
await issuerAPI.updateCapabilities(capabilitiesModal.id, capabilities);
|
await issuerAPI.updateCapabilities(capabilitiesModal.id, capabilities);
|
||||||
toast.success('Capabilities updated successfully');
|
toast.success('Capabilities updated successfully');
|
||||||
setCapabilitiesModal(null);
|
setCapabilitiesModal(null);
|
||||||
await fetchIssuers();
|
await fetchIssuers(filterState);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error updating capabilities:', err);
|
console.error('Error updating capabilities:', err);
|
||||||
console.error('Error response:', err?.response?.data);
|
console.error('Error response:', err?.response?.data);
|
||||||
@@ -277,44 +339,232 @@ const Issuer = () => {
|
|||||||
const openCurrenciesModal = async (issuer) => {
|
const openCurrenciesModal = async (issuer) => {
|
||||||
setCurrenciesModal(issuer);
|
setCurrenciesModal(issuer);
|
||||||
try {
|
try {
|
||||||
|
// Get allowed currencies from /api/v1/Issuer/{issuerId}/AllowedCurrencies
|
||||||
const allowed = await issuerAPI.getAllowedCurrencies(issuer.id);
|
const allowed = await issuerAPI.getAllowedCurrencies(issuer.id);
|
||||||
console.log('Allowed currencies from API:', allowed);
|
console.log('🔵 Allowed currencies received from API:', allowed);
|
||||||
|
|
||||||
if (Array.isArray(allowed) && allowed.length > 0) {
|
if (Array.isArray(allowed) && allowed.length > 0) {
|
||||||
// Extract currency codes from the response
|
// Use currencies from API
|
||||||
const codes = allowed.map(c => c.code).filter(code => code);
|
setCurrenciesData(allowed);
|
||||||
console.log('Extracted currency codes:', codes);
|
// Extract selected currencies (those with allowed = true)
|
||||||
setSelectedCurrencies(codes);
|
const selected = allowed
|
||||||
|
.filter(c => c.allowed === true || c.allowed === 'true')
|
||||||
|
.map(c => c.code || c.currencyCode);
|
||||||
|
setSelectedCurrencies(selected);
|
||||||
|
console.log('🔵 Selected currencies:', selected);
|
||||||
} else {
|
} else {
|
||||||
|
// If no currencies from API, create default structure from available currencies
|
||||||
|
const defaultCurrencies = currencies.map(curr => {
|
||||||
|
const code = curr.currencyCode?.code || curr.code || '';
|
||||||
|
const name = curr.currencyCode?.name || curr.name || '';
|
||||||
|
return {
|
||||||
|
code: code,
|
||||||
|
currencyCode: code,
|
||||||
|
name: name,
|
||||||
|
currencyEnglishName: name,
|
||||||
|
allowed: false
|
||||||
|
};
|
||||||
|
}).filter(c => c.code);
|
||||||
|
setCurrenciesData(defaultCurrencies);
|
||||||
setSelectedCurrencies([]);
|
setSelectedCurrencies([]);
|
||||||
|
console.log('🔵 Using default currencies structure');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error loading allowed currencies:', err);
|
console.error('🔴 Error loading allowed currencies:', err);
|
||||||
|
// Fallback: create default structure
|
||||||
|
const defaultCurrencies = currencies.map(curr => {
|
||||||
|
const code = curr.currencyCode?.code || curr.code || '';
|
||||||
|
const name = curr.currencyCode?.name || curr.name || '';
|
||||||
|
return {
|
||||||
|
code: code,
|
||||||
|
currencyCode: code,
|
||||||
|
name: name,
|
||||||
|
currencyEnglishName: name,
|
||||||
|
allowed: false
|
||||||
|
};
|
||||||
|
}).filter(c => c.code);
|
||||||
|
setCurrenciesData(defaultCurrencies);
|
||||||
setSelectedCurrencies([]);
|
setSelectedCurrencies([]);
|
||||||
|
toast.error('Failed to load allowed currencies. Using default structure.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUpdateCurrencies = async () => {
|
const onUpdateCurrencies = async () => {
|
||||||
if (!currenciesModal) return;
|
if (!currenciesModal) return;
|
||||||
try {
|
try {
|
||||||
const allowedCurrencies = currencies.map(curr => ({
|
// Get current allowed currencies from API
|
||||||
currencyCode: curr.currencyCode?.code || curr.code || '',
|
let currentAllowedCurrencies = [];
|
||||||
currencyEnglishName: curr.currencyCode?.name || curr.name || '',
|
try {
|
||||||
allowed: selectedCurrencies.includes(curr.currencyCode?.code || curr.code || '')
|
currentAllowedCurrencies = await issuerAPI.getAllowedCurrencies(currenciesModal.id);
|
||||||
}));
|
} catch (err) {
|
||||||
|
console.warn('⚠️ Could not fetch current allowed currencies:', err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build map of current currencies (those already configured)
|
||||||
|
const currentCurrenciesMap = new Map();
|
||||||
|
if (Array.isArray(currentAllowedCurrencies)) {
|
||||||
|
currentAllowedCurrencies.forEach(curr => {
|
||||||
|
const code = curr.code || curr.currencyCode || '';
|
||||||
|
if (code) {
|
||||||
|
currentCurrenciesMap.set(String(code).trim(), curr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build IssuerAllowedCurrencyDto array from SELECTED currencies only
|
||||||
|
// Only send currencies that are selected (to avoid conflict with already configured ones)
|
||||||
|
const allCurrencies = currenciesData.length > 0 ? currenciesData : currencies;
|
||||||
|
|
||||||
|
const allowedCurrencies = selectedCurrencies
|
||||||
|
.filter(selectedCode => {
|
||||||
|
// Filter out invalid currency codes
|
||||||
|
return selectedCode && String(selectedCode).trim() !== '';
|
||||||
|
})
|
||||||
|
.map(selectedCode => {
|
||||||
|
const code = String(selectedCode).trim();
|
||||||
|
|
||||||
|
// Find the currency data
|
||||||
|
const currencyData = allCurrencies.find(curr => {
|
||||||
|
const currCode = String(curr.code || curr.currencyCode || curr.currencyCode?.code || '').trim();
|
||||||
|
return currCode === code ||
|
||||||
|
currCode === String(curr.currencyCode || '').trim() ||
|
||||||
|
currCode === String(curr.currencyCode?.code || '').trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
const name = currencyData
|
||||||
|
? String(currencyData.currencyEnglishName || currencyData.name || currencyData.currencyCode?.name || '').trim()
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// Use existing currency data if available
|
||||||
|
const existingCurrency = currentCurrenciesMap.get(code);
|
||||||
|
|
||||||
|
return {
|
||||||
|
currencyCode: code,
|
||||||
|
currencyEnglishName: name || existingCurrency?.currencyEnglishName || existingCurrency?.name || code,
|
||||||
|
allowed: true // All selected currencies have allowed=true
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(curr => curr.currencyCode && curr.currencyCode.trim() !== '');
|
||||||
|
|
||||||
|
console.log('🔵 Sending ALL currencies to update:', JSON.stringify(allowedCurrencies, null, 2));
|
||||||
|
console.log('🔵 Issuer ID:', currenciesModal.id);
|
||||||
|
console.log('🔵 Total currencies count:', allowedCurrencies.length);
|
||||||
|
console.log('🔵 Selected currencies:', selectedCurrencies);
|
||||||
|
console.log('🔵 Currencies with allowed=true:', allowedCurrencies.filter(c => c.allowed).length);
|
||||||
|
console.log('🔵 Currencies with allowed=false:', allowedCurrencies.filter(c => !c.allowed).length);
|
||||||
|
console.log('🔵 Current currencies from API:', currentAllowedCurrencies.length);
|
||||||
|
|
||||||
|
// Call PUT /api/v1/Issuer/{issuerId}/AllowedCurrencies
|
||||||
await issuerAPI.updateAllowedCurrencies(currenciesModal.id, allowedCurrencies);
|
await issuerAPI.updateAllowedCurrencies(currenciesModal.id, allowedCurrencies);
|
||||||
toast.success('Allowed currencies updated successfully');
|
toast.success('Allowed currencies updated successfully');
|
||||||
setCurrenciesModal(null);
|
setCurrenciesModal(null);
|
||||||
await fetchIssuers();
|
await fetchIssuers(filterState);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('🔴 Error updating allowed currencies:', err);
|
||||||
|
console.error('🔴 Error response:', err?.response?.data);
|
||||||
|
console.error('🔴 Error details:', err?.response?.data?.errors);
|
||||||
|
|
||||||
|
// Check if it's a conflict error
|
||||||
|
if (err?.response?.status === 409) {
|
||||||
|
const errorMsg = err?.response?.data?.message || 'Currency conflict. Please refresh and try again.';
|
||||||
|
toast.error(errorMsg);
|
||||||
|
} else {
|
||||||
toast.error(getErrorMessage(err));
|
toast.error(getErrorMessage(err));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- مدیریت فیلترها ---
|
||||||
|
const handleApplyFilter = () => {
|
||||||
|
fetchIssuers(filterState);
|
||||||
|
setIsFilterModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetFilter = () => {
|
||||||
|
setFilterState({
|
||||||
|
countryId: '',
|
||||||
|
provinceId: '',
|
||||||
|
cityId: '',
|
||||||
|
isActive: '',
|
||||||
|
supportCurrencyCode: '',
|
||||||
|
supportCapabilities: ''
|
||||||
|
});
|
||||||
|
fetchIssuers({});
|
||||||
|
setIsFilterModalOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// وقتی country در فیلتر تغییر میکند، provinces را بهروزرسانی کن
|
||||||
|
useEffect(() => {
|
||||||
|
if (filterState.countryId) {
|
||||||
|
const loadProvinces = async () => {
|
||||||
|
try {
|
||||||
|
const provincesList = await provinceAPI.list({
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 1000,
|
||||||
|
countryId: filterState.countryId
|
||||||
|
});
|
||||||
|
setProvinces(Array.isArray(provincesList) ? provincesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load provinces:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadProvinces();
|
||||||
|
// Reset province and city when country changes
|
||||||
|
setFilterState(prev => ({ ...prev, provinceId: '', cityId: '' }));
|
||||||
|
} else {
|
||||||
|
// Load all provinces if no country selected
|
||||||
|
const loadProvinces = async () => {
|
||||||
|
try {
|
||||||
|
const provincesList = await provinceAPI.list({ currentPage: 1, pageSize: 1000 });
|
||||||
|
setProvinces(Array.isArray(provincesList) ? provincesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load provinces:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadProvinces();
|
||||||
|
}
|
||||||
|
}, [filterState.countryId]);
|
||||||
|
|
||||||
|
// وقتی province در فیلتر تغییر میکند، cities را بهروزرسانی کن
|
||||||
|
useEffect(() => {
|
||||||
|
if (filterState.provinceId) {
|
||||||
|
const loadCities = async () => {
|
||||||
|
try {
|
||||||
|
const citiesList = await cityAPI.list({
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 1000,
|
||||||
|
provinceId: filterState.provinceId
|
||||||
|
});
|
||||||
|
setCities(Array.isArray(citiesList) ? citiesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load cities:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadCities();
|
||||||
|
// Reset city when province changes
|
||||||
|
setFilterState(prev => ({ ...prev, cityId: '' }));
|
||||||
|
} else if (filterState.countryId) {
|
||||||
|
// Load cities for selected country if no province selected
|
||||||
|
const loadCities = async () => {
|
||||||
|
try {
|
||||||
|
const citiesList = await cityAPI.list({
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 1000,
|
||||||
|
countryId: filterState.countryId
|
||||||
|
});
|
||||||
|
setCities(Array.isArray(citiesList) ? citiesList : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load cities:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
loadCities();
|
||||||
|
}
|
||||||
|
}, [filterState.provinceId, filterState.countryId]);
|
||||||
|
|
||||||
// --- ستونهای جدول ---
|
// --- ستونهای جدول ---
|
||||||
const columns = useMemo(() => [
|
const columns = useMemo(() => [
|
||||||
{ key: 'name', header: 'Name' },
|
{ key: 'name', header: 'Name' },
|
||||||
{ key: 'supportEmail', header: 'Support Email' },
|
{ key: 'supportEmail', header: 'Support Email' },
|
||||||
{ key: 'address', header: 'Address' },
|
|
||||||
{ key: 'supportCurrencies', header: 'Support Currencies' },
|
{ key: 'supportCurrencies', header: 'Support Currencies' },
|
||||||
{
|
{
|
||||||
key: 'capabilities',
|
key: 'capabilities',
|
||||||
@@ -513,14 +763,153 @@ const Issuer = () => {
|
|||||||
Manage issuers: add, edit, activate/deactivate, and delete
|
Manage issuers: add, edit, activate/deactivate, and delete
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setIsFilterModalOpen(true)}
|
||||||
|
className="btn-secondary inline-flex items-center"
|
||||||
|
>
|
||||||
|
<Filter className="h-4 w-4 mr-2" /> Filter
|
||||||
|
</button>
|
||||||
<button onClick={() => setIsModalOpen(true)} className="btn-primary inline-flex items-center">
|
<button onClick={() => setIsModalOpen(true)} className="btn-primary inline-flex items-center">
|
||||||
<Plus className="h-4 w-4 mr-2" /> Add Issuer
|
<Plus className="h-4 w-4 mr-2" /> Add Issuer
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{renderModal(isModalOpen, 'Add Issuer', onAddIssuer, addForm, setAddForm, () => setIsModalOpen(false))}
|
{renderModal(isModalOpen, 'Add Issuer', onAddIssuer, addForm, setAddForm, () => setIsModalOpen(false))}
|
||||||
{renderModal(isEditModalOpen, 'Edit Issuer', onUpdateIssuer, editForm, setEditForm, () => setIsEditModalOpen(false))}
|
{renderModal(isEditModalOpen, 'Edit Issuer', onUpdateIssuer, editForm, setEditForm, () => setIsEditModalOpen(false))}
|
||||||
|
|
||||||
|
{/* Filter Modal */}
|
||||||
|
{isFilterModalOpen && (
|
||||||
|
<>
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={() => setIsFilterModalOpen(false)} />
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||||
|
<div className="w-full max-w-2xl card p-6 relative bg-white rounded-2xl shadow-lg">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-4">Filter Issuers</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Country</label>
|
||||||
|
<select
|
||||||
|
value={filterState.countryId}
|
||||||
|
onChange={(e) => setFilterState({ ...filterState, countryId: e.target.value, provinceId: '', cityId: '' })}
|
||||||
|
className="w-full p-2 border rounded-lg"
|
||||||
|
>
|
||||||
|
<option value="">All Countries</option>
|
||||||
|
{countries.map((country) => (
|
||||||
|
<option key={country.id} value={country.id}>
|
||||||
|
{country.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Province</label>
|
||||||
|
<select
|
||||||
|
value={filterState.provinceId}
|
||||||
|
onChange={(e) => setFilterState({ ...filterState, provinceId: e.target.value, cityId: '' })}
|
||||||
|
className="w-full p-2 border rounded-lg"
|
||||||
|
disabled={!filterState.countryId}
|
||||||
|
>
|
||||||
|
<option value="">All Provinces</option>
|
||||||
|
{provinces
|
||||||
|
.filter(p => !filterState.countryId || p.countryId === filterState.countryId)
|
||||||
|
.map((province) => (
|
||||||
|
<option key={province.id} value={province.id}>
|
||||||
|
{province.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">City</label>
|
||||||
|
<select
|
||||||
|
value={filterState.cityId}
|
||||||
|
onChange={(e) => setFilterState({ ...filterState, cityId: e.target.value })}
|
||||||
|
className="w-full p-2 border rounded-lg"
|
||||||
|
disabled={!filterState.provinceId && !filterState.countryId}
|
||||||
|
>
|
||||||
|
<option value="">All Cities</option>
|
||||||
|
{cities
|
||||||
|
.filter(c => {
|
||||||
|
if (filterState.provinceId) {
|
||||||
|
return c.provinceId === filterState.provinceId;
|
||||||
|
}
|
||||||
|
if (filterState.countryId) {
|
||||||
|
return c.countryId === filterState.countryId;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map((city) => (
|
||||||
|
<option key={city.id} value={city.id}>
|
||||||
|
{city.name || city.cityName}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Active Status</label>
|
||||||
|
<select
|
||||||
|
value={filterState.isActive}
|
||||||
|
onChange={(e) => setFilterState({ ...filterState, isActive: e.target.value })}
|
||||||
|
className="w-full p-2 border rounded-lg"
|
||||||
|
>
|
||||||
|
<option value="">All</option>
|
||||||
|
<option value="true">Active</option>
|
||||||
|
<option value="false">Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Support Currency</label>
|
||||||
|
<select
|
||||||
|
value={filterState.supportCurrencyCode}
|
||||||
|
onChange={(e) => setFilterState({ ...filterState, supportCurrencyCode: e.target.value })}
|
||||||
|
className="w-full p-2 border rounded-lg"
|
||||||
|
>
|
||||||
|
<option value="">All Currencies</option>
|
||||||
|
{currencies.map((curr) => (
|
||||||
|
<option key={curr.code} value={curr.code}>
|
||||||
|
{curr.code} - {curr.name || curr.nativeName || curr.symbol}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1 text-gray-700 dark:text-gray-300">Support Capabilities</label>
|
||||||
|
<select
|
||||||
|
value={filterState.supportCapabilities}
|
||||||
|
onChange={(e) => setFilterState({ ...filterState, supportCapabilities: e.target.value })}
|
||||||
|
className="w-full p-2 border rounded-lg"
|
||||||
|
>
|
||||||
|
<option value="">All Capabilities</option>
|
||||||
|
{issuerCapabilities.map((cap) => (
|
||||||
|
<option key={cap.name} value={cap.name}>
|
||||||
|
{cap.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end gap-2 pt-4">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleResetFilter}
|
||||||
|
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleApplyFilter}
|
||||||
|
className="btn-primary px-4 py-2 rounded-lg"
|
||||||
|
>
|
||||||
|
Apply Filter
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Capabilities Modal */}
|
{/* Capabilities Modal */}
|
||||||
{capabilitiesModal && (
|
{capabilitiesModal && (
|
||||||
<>
|
<>
|
||||||
@@ -581,7 +970,42 @@ const Issuer = () => {
|
|||||||
Manage Allowed Currencies for {currenciesModal.name}
|
Manage Allowed Currencies for {currenciesModal.name}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{currencies.map((curr) => {
|
{currenciesData.length > 0 ? (
|
||||||
|
currenciesData.map((curr) => {
|
||||||
|
const code = curr.code || curr.currencyCode || '';
|
||||||
|
const name = curr.currencyEnglishName || curr.name || '';
|
||||||
|
const checked = selectedCurrencies.some(selected => {
|
||||||
|
const selectedStr = String(selected).trim();
|
||||||
|
return selectedStr === String(code).trim() || selectedStr === String(curr.currencyCode || '').trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label key={code || curr.currencyCode} className="flex items-center gap-2 p-2 border rounded hover:bg-gray-50">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={checked}
|
||||||
|
onChange={(e) => {
|
||||||
|
const currencyToToggle = code || curr.currencyCode;
|
||||||
|
if (e.target.checked) {
|
||||||
|
// Add to selected if not already there
|
||||||
|
if (!selectedCurrencies.includes(currencyToToggle)) {
|
||||||
|
setSelectedCurrencies([...selectedCurrencies, currencyToToggle]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Remove from selected
|
||||||
|
setSelectedCurrencies(selectedCurrencies.filter(c => {
|
||||||
|
const cStr = String(c).trim();
|
||||||
|
return cStr !== String(currencyToToggle).trim() && cStr !== String(curr.currencyCode || '').trim();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<span className="text-sm">{code} - {name}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
currencies.map((curr) => {
|
||||||
const code = curr.currencyCode?.code || curr.code || '';
|
const code = curr.currencyCode?.code || curr.code || '';
|
||||||
const name = curr.currencyCode?.name || curr.name || '';
|
const name = curr.currencyCode?.name || curr.name || '';
|
||||||
const checked = selectedCurrencies.includes(code);
|
const checked = selectedCurrencies.includes(code);
|
||||||
@@ -601,7 +1025,8 @@ const Issuer = () => {
|
|||||||
<span className="text-sm">{code} - {name}</span>
|
<span className="text-sm">{code} - {name}</span>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
})}
|
})
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-x-2 mt-4">
|
<div className="flex justify-end gap-x-2 mt-4">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import DataTable from '../components/DataTable';
|
import DataTable from '../components/DataTable';
|
||||||
import { countryAPI, provinceAPI, cityAPI, currencyAPI } from '../services/api';
|
import { countryAPI, provinceAPI, cityAPI, generalAPI } from '../services/api';
|
||||||
import { Plus, Search, Pencil, Trash2, MapPin, Globe, Building2 } from 'lucide-react';
|
import { Plus, Search, Pencil, Trash2, MapPin, Globe, Building2 } from 'lucide-react';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
@@ -17,11 +17,13 @@ const Location = () => {
|
|||||||
const [editingCountry, setEditingCountry] = useState(null);
|
const [editingCountry, setEditingCountry] = useState(null);
|
||||||
const [countryForm, setCountryForm] = useState({ name: '', phoneCode: '', currencyCode: '', timeZoneName: '' });
|
const [countryForm, setCountryForm] = useState({ name: '', phoneCode: '', currencyCode: '', timeZoneName: '' });
|
||||||
const [currencies, setCurrencies] = useState([]);
|
const [currencies, setCurrencies] = useState([]);
|
||||||
|
const [timeZones, setTimeZones] = useState([]);
|
||||||
|
|
||||||
// Province states
|
// Province states
|
||||||
const [provinces, setProvinces] = useState([]);
|
const [provinces, setProvinces] = useState([]);
|
||||||
const [provincesLoading, setProvincesLoading] = useState(true);
|
const [provincesLoading, setProvincesLoading] = useState(true);
|
||||||
const [provinceFilter, setProvinceFilter] = useState('');
|
const [provinceFilter, setProvinceFilter] = useState('');
|
||||||
|
const [selectedCountryFilter, setSelectedCountryFilter] = useState('');
|
||||||
const [isProvinceModalOpen, setIsProvinceModalOpen] = useState(false);
|
const [isProvinceModalOpen, setIsProvinceModalOpen] = useState(false);
|
||||||
const [editingProvince, setEditingProvince] = useState(null);
|
const [editingProvince, setEditingProvince] = useState(null);
|
||||||
const [provinceForm, setProvinceForm] = useState({ countryId: '', provinceName: '' });
|
const [provinceForm, setProvinceForm] = useState({ countryId: '', provinceName: '' });
|
||||||
@@ -31,23 +33,38 @@ const Location = () => {
|
|||||||
const [cities, setCities] = useState([]);
|
const [cities, setCities] = useState([]);
|
||||||
const [citiesLoading, setCitiesLoading] = useState(true);
|
const [citiesLoading, setCitiesLoading] = useState(true);
|
||||||
const [cityFilter, setCityFilter] = useState('');
|
const [cityFilter, setCityFilter] = useState('');
|
||||||
|
const [selectedCountryFilterForCity, setSelectedCountryFilterForCity] = useState('');
|
||||||
|
const [selectedProvinceFilterForCity, setSelectedProvinceFilterForCity] = useState('');
|
||||||
const [isCityModalOpen, setIsCityModalOpen] = useState(false);
|
const [isCityModalOpen, setIsCityModalOpen] = useState(false);
|
||||||
const [editingCity, setEditingCity] = useState(null);
|
const [editingCity, setEditingCity] = useState(null);
|
||||||
const [cityForm, setCityForm] = useState({ provinceId: '', cityName: '' });
|
const [cityForm, setCityForm] = useState({ provinceId: '', cityName: '' });
|
||||||
const [selectedProvinceForCity, setSelectedProvinceForCity] = useState(null);
|
const [selectedProvinceForCity, setSelectedProvinceForCity] = useState(null);
|
||||||
const [provincesForCity, setProvincesForCity] = useState([]);
|
const [provincesForCity, setProvincesForCity] = useState([]);
|
||||||
|
|
||||||
// دریافت ارزها برای dropdown
|
// دریافت ارزها و timezone ها برای dropdown
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCurrencies = async () => {
|
const fetchCurrencies = async () => {
|
||||||
try {
|
try {
|
||||||
const list = await currencyAPI.list();
|
const list = await generalAPI.getCurrencies();
|
||||||
setCurrencies(Array.isArray(list) ? list : []);
|
setCurrencies(Array.isArray(list) ? list : []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load currencies:', err);
|
console.error('Failed to load currencies:', err);
|
||||||
|
toast.error('Failed to load currencies');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchTimeZones = async () => {
|
||||||
|
try {
|
||||||
|
const list = await generalAPI.getTimeZones();
|
||||||
|
setTimeZones(Array.isArray(list) ? list : []);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load timezones:', err);
|
||||||
|
toast.error('Failed to load timezones');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
fetchCurrencies();
|
fetchCurrencies();
|
||||||
|
fetchTimeZones();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// دریافت کشورها
|
// دریافت کشورها
|
||||||
@@ -64,10 +81,14 @@ const Location = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// دریافت استانها
|
// دریافت استانها
|
||||||
const fetchProvinces = useCallback(async () => {
|
const fetchProvinces = useCallback(async (countryId = null) => {
|
||||||
try {
|
try {
|
||||||
setProvincesLoading(true);
|
setProvincesLoading(true);
|
||||||
const list = await provinceAPI.list();
|
const params = {};
|
||||||
|
if (countryId) {
|
||||||
|
params.countryId = countryId;
|
||||||
|
}
|
||||||
|
const list = await provinceAPI.list(params);
|
||||||
setProvinces(Array.isArray(list) ? list : []);
|
setProvinces(Array.isArray(list) ? list : []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(getErrorMessage(err));
|
toast.error(getErrorMessage(err));
|
||||||
@@ -77,10 +98,17 @@ const Location = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// دریافت شهرها
|
// دریافت شهرها
|
||||||
const fetchCities = useCallback(async () => {
|
const fetchCities = useCallback(async (countryId = null, provinceId = null) => {
|
||||||
try {
|
try {
|
||||||
setCitiesLoading(true);
|
setCitiesLoading(true);
|
||||||
const list = await cityAPI.list();
|
const params = {};
|
||||||
|
if (countryId) {
|
||||||
|
params.countryId = countryId;
|
||||||
|
}
|
||||||
|
if (provinceId) {
|
||||||
|
params.provinceId = provinceId;
|
||||||
|
}
|
||||||
|
const list = await cityAPI.list(params);
|
||||||
setCities(Array.isArray(list) ? list : []);
|
setCities(Array.isArray(list) ? list : []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(getErrorMessage(err));
|
toast.error(getErrorMessage(err));
|
||||||
@@ -89,17 +117,62 @@ const Location = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// دریافت کشورها برای dropdown ها
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeTab === 'country') {
|
if (activeTab === 'province' || activeTab === 'city') {
|
||||||
fetchCountries();
|
fetchCountries();
|
||||||
} else if (activeTab === 'province') {
|
|
||||||
fetchProvinces();
|
|
||||||
fetchCountries(); // برای dropdown
|
|
||||||
} else if (activeTab === 'city') {
|
|
||||||
fetchCities();
|
|
||||||
fetchProvinces(); // برای dropdown
|
|
||||||
}
|
}
|
||||||
}, [activeTab, fetchCountries, fetchProvinces, fetchCities]);
|
}, [activeTab, fetchCountries]);
|
||||||
|
|
||||||
|
// دریافت provinces برای province tab - وقتی tab تغییر میکند یا فیلتر country تغییر میکند
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'province') {
|
||||||
|
const countryId = selectedCountryFilter || null;
|
||||||
|
console.log('🔵 Fetching provinces for province tab with countryId:', countryId);
|
||||||
|
fetchProvinces(countryId);
|
||||||
|
}
|
||||||
|
}, [activeTab, selectedCountryFilter, fetchProvinces]);
|
||||||
|
|
||||||
|
// دریافت provinces برای city tab (برای dropdown)
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'city') {
|
||||||
|
if (selectedCountryFilterForCity) {
|
||||||
|
// دریافت provinces برای country انتخاب شده
|
||||||
|
fetchProvinces(selectedCountryFilterForCity);
|
||||||
|
} else {
|
||||||
|
// دریافت همه provinces
|
||||||
|
fetchProvinces();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [activeTab, selectedCountryFilterForCity, fetchProvinces]);
|
||||||
|
|
||||||
|
// دریافت cities - وقتی tab تغییر میکند یا فیلترها تغییر میکنند
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'city') {
|
||||||
|
const countryId = selectedCountryFilterForCity || null;
|
||||||
|
const provinceId = selectedProvinceFilterForCity || null;
|
||||||
|
console.log('🔵 Fetching cities with filters:', { countryId, provinceId });
|
||||||
|
fetchCities(countryId, provinceId);
|
||||||
|
}
|
||||||
|
}, [activeTab, selectedCountryFilterForCity, selectedProvinceFilterForCity, fetchCities]);
|
||||||
|
|
||||||
|
// Reset filters when switching tabs
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab !== 'province') {
|
||||||
|
setSelectedCountryFilter('');
|
||||||
|
}
|
||||||
|
if (activeTab !== 'city') {
|
||||||
|
setSelectedCountryFilterForCity('');
|
||||||
|
setSelectedProvinceFilterForCity('');
|
||||||
|
}
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
|
// وقتی country برای city تغییر میکند، province filter را reset کن
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === 'city' && selectedCountryFilterForCity) {
|
||||||
|
setSelectedProvinceFilterForCity('');
|
||||||
|
}
|
||||||
|
}, [selectedCountryFilterForCity, activeTab]);
|
||||||
|
|
||||||
// ========== Country Functions ==========
|
// ========== Country Functions ==========
|
||||||
const openCountryModal = (country = null) => {
|
const openCountryModal = (country = null) => {
|
||||||
@@ -120,6 +193,25 @@ const Location = () => {
|
|||||||
|
|
||||||
const onSaveCountry = async (e) => {
|
const onSaveCountry = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!countryForm.name?.trim()) {
|
||||||
|
toast.error('Name is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!countryForm.phoneCode?.trim()) {
|
||||||
|
toast.error('Phone Code is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!countryForm.currencyCode?.trim()) {
|
||||||
|
toast.error('Currency Code is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!countryForm.timeZoneName?.trim()) {
|
||||||
|
toast.error('Time Zone is required');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (editingCountry) {
|
if (editingCountry) {
|
||||||
const countryId = editingCountry.id || editingCountry.countryId;
|
const countryId = editingCountry.id || editingCountry.countryId;
|
||||||
@@ -132,6 +224,7 @@ const Location = () => {
|
|||||||
await countryAPI.update(countryId, countryForm);
|
await countryAPI.update(countryId, countryForm);
|
||||||
toast.success(getSuccessMessage({ data: { message: 'Country updated successfully' } }) || 'Country updated successfully');
|
toast.success(getSuccessMessage({ data: { message: 'Country updated successfully' } }) || 'Country updated successfully');
|
||||||
} else {
|
} else {
|
||||||
|
console.log('Creating country with payload:', countryForm);
|
||||||
await countryAPI.create(countryForm);
|
await countryAPI.create(countryForm);
|
||||||
toast.success(getSuccessMessage({ data: { message: 'Country added successfully' } }) || 'Country added successfully');
|
toast.success(getSuccessMessage({ data: { message: 'Country added successfully' } }) || 'Country added successfully');
|
||||||
}
|
}
|
||||||
@@ -248,7 +341,7 @@ const Location = () => {
|
|||||||
{ key: 'name', header: 'Name' },
|
{ key: 'name', header: 'Name' },
|
||||||
{ key: 'phoneCode', header: 'Phone Code' },
|
{ key: 'phoneCode', header: 'Phone Code' },
|
||||||
{ key: 'currencyCode', header: 'Currency' },
|
{ key: 'currencyCode', header: 'Currency' },
|
||||||
{ key: 'timeZoneName', header: 'Time Zone', render: (val, row) => val || row.timeZone || '—' },
|
{ key: 'timeZone', header: 'Time Zone', render: (val, row) => val || row.timeZoneName || '—' },
|
||||||
{
|
{
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
header: 'Actions',
|
header: 'Actions',
|
||||||
@@ -413,7 +506,8 @@ const Location = () => {
|
|||||||
{/* Province Tab */}
|
{/* Province Tab */}
|
||||||
{activeTab === 'province' && (
|
{activeTab === 'province' && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4 flex justify-between items-center">
|
<div className="mb-4 flex justify-between items-center gap-4">
|
||||||
|
<div className="flex gap-4 flex-1">
|
||||||
<div className="relative max-w-md flex-1">
|
<div className="relative max-w-md flex-1">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
@@ -423,9 +517,27 @@ const Location = () => {
|
|||||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="min-w-[200px]">
|
||||||
|
<select
|
||||||
|
value={selectedCountryFilter}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedCountryFilter(e.target.value);
|
||||||
|
setProvinceFilter(''); // Reset text filter when country changes
|
||||||
|
}}
|
||||||
|
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
<option value="">All Countries</option>
|
||||||
|
{countries.map((country) => (
|
||||||
|
<option key={country.id} value={country.id}>
|
||||||
|
{country.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => openProvinceModal()}
|
onClick={() => openProvinceModal()}
|
||||||
className="btn-primary inline-flex items-center ml-4"
|
className="btn-primary inline-flex items-center"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4 mr-2" /> Add Province
|
<Plus className="h-4 w-4 mr-2" /> Add Province
|
||||||
</button>
|
</button>
|
||||||
@@ -443,7 +555,8 @@ const Location = () => {
|
|||||||
{/* City Tab */}
|
{/* City Tab */}
|
||||||
{activeTab === 'city' && (
|
{activeTab === 'city' && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-4 flex justify-between items-center">
|
<div className="mb-4 flex justify-between items-center gap-4">
|
||||||
|
<div className="flex gap-4 flex-1">
|
||||||
<div className="relative max-w-md flex-1">
|
<div className="relative max-w-md flex-1">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||||
<input
|
<input
|
||||||
@@ -453,9 +566,48 @@ const Location = () => {
|
|||||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="min-w-[200px]">
|
||||||
|
<select
|
||||||
|
value={selectedCountryFilterForCity}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedCountryFilterForCity(e.target.value);
|
||||||
|
setSelectedProvinceFilterForCity(''); // Reset province filter when country changes
|
||||||
|
setCityFilter(''); // Reset text filter
|
||||||
|
}}
|
||||||
|
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||||
|
>
|
||||||
|
<option value="">All Countries</option>
|
||||||
|
{countries.map((country) => (
|
||||||
|
<option key={country.id} value={country.id}>
|
||||||
|
{country.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="min-w-[200px]">
|
||||||
|
<select
|
||||||
|
value={selectedProvinceFilterForCity}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSelectedProvinceFilterForCity(e.target.value);
|
||||||
|
setCityFilter(''); // Reset text filter
|
||||||
|
}}
|
||||||
|
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||||
|
disabled={!selectedCountryFilterForCity}
|
||||||
|
>
|
||||||
|
<option value="">All Provinces</option>
|
||||||
|
{provinces
|
||||||
|
.filter(p => !selectedCountryFilterForCity || p.countryId === selectedCountryFilterForCity)
|
||||||
|
.map((province) => (
|
||||||
|
<option key={province.id} value={province.id}>
|
||||||
|
{province.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => openCityModal()}
|
onClick={() => openCityModal()}
|
||||||
className="btn-primary inline-flex items-center ml-4"
|
className="btn-primary inline-flex items-center"
|
||||||
>
|
>
|
||||||
<Plus className="h-4 w-4 mr-2" /> Add City
|
<Plus className="h-4 w-4 mr-2" /> Add City
|
||||||
</button>
|
</button>
|
||||||
@@ -508,21 +660,27 @@ const Location = () => {
|
|||||||
>
|
>
|
||||||
<option value="">Select Currency</option>
|
<option value="">Select Currency</option>
|
||||||
{currencies.map((curr) => (
|
{currencies.map((curr) => (
|
||||||
<option key={curr.currencyCode?.code} value={curr.currencyCode?.code}>
|
<option key={curr.code} value={curr.code}>
|
||||||
{curr.currencyCode?.code} - {curr.currencyCode?.name}
|
{curr.code} - {curr.name || curr.nativeName || curr.symbol}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-medium mb-1">Time Zone *</label>
|
<label className="block text-sm font-medium mb-1">Time Zone *</label>
|
||||||
<input
|
<select
|
||||||
value={countryForm.timeZoneName}
|
value={countryForm.timeZoneName}
|
||||||
onChange={(e) => setCountryForm({ ...countryForm, timeZoneName: e.target.value })}
|
onChange={(e) => setCountryForm({ ...countryForm, timeZoneName: e.target.value })}
|
||||||
className="w-full p-2 border rounded-lg"
|
className="w-full p-2 border rounded-lg"
|
||||||
placeholder="e.g., UTC+3:30"
|
|
||||||
required
|
required
|
||||||
/>
|
>
|
||||||
|
<option value="">Select Time Zone</option>
|
||||||
|
{timeZones.map((tz) => (
|
||||||
|
<option key={tz.name} value={tz.name}>
|
||||||
|
{tz.name}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end gap-x-2">
|
<div className="flex justify-end gap-x-2">
|
||||||
<button type="button" onClick={() => setIsCountryModalOpen(false)} className="px-4 py-2 border rounded-lg">
|
<button type="button" onClick={() => setIsCountryModalOpen(false)} className="px-4 py-2 border rounded-lg">
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const Login = () => {
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
const setLoggedIn = useAuthStore((s) => s.setLoggedIn);
|
const setLoggedIn = useAuthStore((s) => s.setLoggedIn);
|
||||||
|
const setUser = useAuthStore((s) => s.setUser);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
@@ -22,11 +23,26 @@ const Login = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await apiLogin(email, password);
|
const result = await apiLogin(email, password);
|
||||||
// If server responds successfully, consider logged in (session cookie via withCredentials)
|
// بررسی اینکه آیا پاسخ موفقیتآمیز است (پشتیبانی از isSuccess و IsSuccess)
|
||||||
if (result) {
|
const isSuccess = result?.isSuccess ?? result?.IsSuccess;
|
||||||
|
|
||||||
|
if (result && (isSuccess === true || isSuccess === undefined)) {
|
||||||
|
// استخراج اطلاعات کاربر از response
|
||||||
|
// فرمت جدید: { data: { email: "..." }, isSuccess: true, ... }
|
||||||
|
const userEmail = result.data?.email || result.email || email;
|
||||||
|
const userData = {
|
||||||
|
email: userEmail,
|
||||||
|
userName: userEmail,
|
||||||
|
name: userEmail.split('@')[0],
|
||||||
|
...result.data,
|
||||||
|
...result
|
||||||
|
};
|
||||||
|
|
||||||
|
setUser(userData);
|
||||||
setLoggedIn(true);
|
setLoggedIn(true);
|
||||||
navigate('/');
|
navigate('/');
|
||||||
} else {
|
} else {
|
||||||
|
// اگر isSuccess false باشد، خطا نمایش داده میشود (در catch block)
|
||||||
setError('Invalid email or password');
|
setError('Invalid email or password');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -45,7 +61,7 @@ const Login = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900 dark:text-white">
|
<h2 className="mt-6 text-center text-3xl font-bold text-gray-900 dark:text-white">
|
||||||
Khalij pay Admin Panel
|
Khalij Finance Admin
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">
|
<p className="mt-2 text-center text-sm text-gray-600 dark:text-gray-400">
|
||||||
Sign in to your account
|
Sign in to your account
|
||||||
|
|||||||
@@ -14,3 +14,4 @@ export { provinceAPI } from './provinceAPI';
|
|||||||
export { cityAPI } from './cityAPI';
|
export { cityAPI } from './cityAPI';
|
||||||
export { issuerAPI } from './issuerAPI';
|
export { issuerAPI } from './issuerAPI';
|
||||||
export { listPermissions } from './permissionsAPI';
|
export { listPermissions } from './permissionsAPI';
|
||||||
|
export { generalAPI } from './generalAPI';
|
||||||
|
|||||||
@@ -43,16 +43,39 @@ if (typeof window !== 'undefined') {
|
|||||||
// Request interceptor
|
// Request interceptor
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
api.interceptors.request.use(config => {
|
api.interceptors.request.use(config => {
|
||||||
|
console.log("🔵 Request interceptor:", {
|
||||||
|
url: config.url,
|
||||||
|
fullUrl: config.baseURL + config.url,
|
||||||
|
skipAuthRedirect: config?.skipAuthRedirect,
|
||||||
|
method: config.method,
|
||||||
|
data: config.data,
|
||||||
|
headers: config.headers
|
||||||
|
});
|
||||||
|
|
||||||
const skipAuthRedirect = config?.skipAuthRedirect === true;
|
const skipAuthRedirect = config?.skipAuthRedirect === true;
|
||||||
if (skipAuthRedirect) {
|
if (skipAuthRedirect) {
|
||||||
|
// بررسی اینکه آیا درخواست به endpoint احراز هویت است یا نه
|
||||||
|
const isAuthEndpoint = config.url?.includes('/Auth/SignIn') ||
|
||||||
|
config.url?.includes('/Auth/SignOut') ||
|
||||||
|
config.url?.includes('/Auth/ForgotPassword');
|
||||||
|
|
||||||
|
console.log("🔵 Auth endpoint check:", { isAuthEndpoint, url: config.url });
|
||||||
|
|
||||||
|
// اگر endpoint احراز هویت است، اجازه ارسال درخواست را بدهیم (حتی اگر کاربر لاگین نباشد)
|
||||||
|
if (!isAuthEndpoint) {
|
||||||
const authState = useAuthStore.getState();
|
const authState = useAuthStore.getState();
|
||||||
|
console.log("🔵 Auth state:", { isLoggedIn: authState?.isLoggedIn });
|
||||||
if (!authState?.isLoggedIn) {
|
if (!authState?.isLoggedIn) {
|
||||||
|
console.warn("⚠️ Canceling request - user not logged in");
|
||||||
const CancelToken = axios.CancelToken;
|
const CancelToken = axios.CancelToken;
|
||||||
const source = CancelToken.source();
|
const source = CancelToken.source();
|
||||||
source.cancel('User not logged in');
|
source.cancel('User not logged in');
|
||||||
config.cancelToken = source.token;
|
config.cancelToken = source.token;
|
||||||
config._skipRequest = true;
|
config._skipRequest = true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
console.log("✅ Allowing auth endpoint request");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
}, error => Promise.reject(error));
|
}, error => Promise.reject(error));
|
||||||
@@ -61,10 +84,59 @@ api.interceptors.request.use(config => {
|
|||||||
// Response interceptor
|
// Response interceptor
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
api.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
response => response,
|
response => {
|
||||||
|
console.log("🟢 Response interceptor - Success:", {
|
||||||
|
url: response.config?.url,
|
||||||
|
status: response.status,
|
||||||
|
data: response.data
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
},
|
||||||
error => {
|
error => {
|
||||||
|
console.log("🔴 Response interceptor - Error:", {
|
||||||
|
url: error.config?.url,
|
||||||
|
isSilent: error?.isSilent,
|
||||||
|
isCancel: axios.isCancel(error),
|
||||||
|
status: error?.response?.status,
|
||||||
|
message: error?.message
|
||||||
|
});
|
||||||
|
|
||||||
|
// لاگ کامل error.response
|
||||||
|
console.log("🔴 Full error.response:", error.response);
|
||||||
|
console.log("🔴 error.response exists:", !!error.response);
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
console.log("🔴 error.response.status:", error.response.status);
|
||||||
|
console.log("🔴 error.response.statusText:", error.response.statusText);
|
||||||
|
console.log("🔴 error.response.headers:", error.response.headers);
|
||||||
|
console.log("🔴 error.response.data exists:", !!error.response.data);
|
||||||
|
console.log("🔴 error.response.data:", error.response.data);
|
||||||
|
console.log("🔴 error.response.data type:", typeof error.response.data);
|
||||||
|
|
||||||
|
// بررسی اینکه آیا data یک string است
|
||||||
|
if (typeof error.response.data === 'string') {
|
||||||
|
console.log("🔴 Response data is string:", error.response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// بررسی اینکه آیا data یک object است
|
||||||
|
if (error.response.data && typeof error.response.data === 'object') {
|
||||||
|
console.log("🔴 Response data keys:", Object.keys(error.response.data));
|
||||||
|
try {
|
||||||
|
console.log("🔴 Response data (JSON):", JSON.stringify(error.response.data, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
console.log("🔴 Cannot stringify response.data:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("🔴 No error.response object");
|
||||||
|
console.log("🔴 Full error object:", error);
|
||||||
|
}
|
||||||
|
|
||||||
if (error?.isSilent) return Promise.reject(error);
|
if (error?.isSilent) return Promise.reject(error);
|
||||||
if (axios.isCancel(error)) return Promise.reject({ isSilent: true, isCancel: true, response: { status: 401, data: { message: 'Unauthorized' } }, config: error.config || {} });
|
if (axios.isCancel(error)) {
|
||||||
|
console.warn("⚠️ Request was canceled");
|
||||||
|
return Promise.reject({ isSilent: true, isCancel: true, response: { status: 401, data: { message: 'Unauthorized' } }, config: error.config || {} });
|
||||||
|
}
|
||||||
|
|
||||||
const skipRedirect = error?.config?.skipAuthRedirect === true;
|
const skipRedirect = error?.config?.skipAuthRedirect === true;
|
||||||
const status = error?.response?.status;
|
const status = error?.response?.status;
|
||||||
@@ -84,10 +156,45 @@ api.interceptors.response.use(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// حفظ status code در خطا برای مدیریت بهتر
|
// حفظ status code در خطا برای مدیریت بهتر
|
||||||
const errorData = error.response?.data || error;
|
let errorData = error.response?.data;
|
||||||
|
|
||||||
|
// اگر errorData خودش یک AxiosError باشد، پیام خطا را از آن استخراج کنیم
|
||||||
|
if (errorData && typeof errorData === 'object' && errorData.name === 'AxiosError') {
|
||||||
|
errorData = {
|
||||||
|
message: errorData.message || error.message || 'خطا در ارتباط با سرور',
|
||||||
|
...(errorData.response?.data || {})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر errorData وجود نداشت یا خالی است، از status code برای ایجاد پیام مناسب استفاده کنیم
|
||||||
|
if (!errorData || (typeof errorData === 'object' && Object.keys(errorData).length === 0)) {
|
||||||
|
const status = error.response?.status;
|
||||||
|
let message = error.message || 'خطا در ارتباط با سرور';
|
||||||
|
|
||||||
|
// پیامهای مناسب بر اساس status code
|
||||||
|
if (status === 500) {
|
||||||
|
message = 'خطای سرور. لطفاً با پشتیبانی تماس بگیرید یا دوباره تلاش کنید.';
|
||||||
|
} else if (status === 400) {
|
||||||
|
message = 'درخواست نامعتبر. لطفاً اطلاعات را بررسی کنید.';
|
||||||
|
} else if (status === 401) {
|
||||||
|
message = 'نام کاربری یا رمز عبور اشتباه است.';
|
||||||
|
} else if (status === 403) {
|
||||||
|
message = 'شما دسترسی به این بخش را ندارید.';
|
||||||
|
} else if (status === 404) {
|
||||||
|
message = 'منبع مورد نظر یافت نشد.';
|
||||||
|
} else if (status === 502 || status === 503) {
|
||||||
|
message = 'سرور در دسترس نیست. لطفاً بعداً تلاش کنید.';
|
||||||
|
}
|
||||||
|
|
||||||
|
errorData = {
|
||||||
|
message: message,
|
||||||
|
statusCode: status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
return Promise.reject({
|
return Promise.reject({
|
||||||
...errorData,
|
...error,
|
||||||
response: {
|
response: {
|
||||||
...error.response,
|
...error.response,
|
||||||
status: error.response.status,
|
status: error.response.status,
|
||||||
@@ -96,7 +203,7 @@ api.interceptors.response.use(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(errorData);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -4,9 +4,42 @@ import { useAuthStore } from "../store/authStore";
|
|||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Auth API
|
// Auth API
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
export async function login(username, password) {
|
export async function login(email, password) {
|
||||||
const res = await api.post("/api/v1/Auth/SignIn", { userName: username, username, email: username, password });
|
try {
|
||||||
return res.data;
|
const res = await api.post("/api/v1/Auth/SignIn", { email: email, password }, { skipAuthRedirect: true });
|
||||||
|
const data = res.data;
|
||||||
|
|
||||||
|
console.log("data login", data);
|
||||||
|
|
||||||
|
// بررسی اینکه آیا پاسخ موفقیتآمیز است یا نه (پشتیبانی از isSuccess و IsSuccess)
|
||||||
|
if (data && (data.isSuccess === false || data.IsSuccess === false)) {
|
||||||
|
// ایجاد یک خطا با پیام دریافتی از API (پشتیبانی از message و Message)
|
||||||
|
const errorMessage = data.message || data.Message || 'Login failed';
|
||||||
|
const error = new Error(errorMessage);
|
||||||
|
error.response = {
|
||||||
|
...res,
|
||||||
|
data: data,
|
||||||
|
status: data.statusCode || data.StatusCode || 409
|
||||||
|
};
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
|
||||||
|
// اگر خطا از سمت سرور است (500, 400, etc.)، آن را throw کنیم
|
||||||
|
if (error.response) {
|
||||||
|
const errorData = error.response.data || {};
|
||||||
|
const errorMessage = errorData.message || errorData.Message || error.message || 'خطا در ارتباط با سرور';
|
||||||
|
const customError = new Error(errorMessage);
|
||||||
|
customError.response = error.response;
|
||||||
|
throw customError;
|
||||||
|
}
|
||||||
|
|
||||||
|
// برای خطاهای دیگر (network error, etc.)
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signOut() {
|
export async function signOut() {
|
||||||
@@ -15,7 +48,8 @@ export async function signOut() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("SignOut API error:", error);
|
console.warn("SignOut API error:", error);
|
||||||
}
|
}
|
||||||
useAuthStore.getState().setLoggedIn(false);
|
const { setLoggedIn, logout } = useAuthStore.getState();
|
||||||
|
logout(); // This will clear both isLoggedIn and user
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,29 @@ import api from './apiClient';
|
|||||||
// City API
|
// City API
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
export const cityAPI = {
|
export const cityAPI = {
|
||||||
// GET /api/v1/City (with pagination)
|
// GET /api/v1/City (with pagination and filters)
|
||||||
async list(params = {}) {
|
async list(params = {}) {
|
||||||
const { currentPage = 1, pageSize = 10, ...otherParams } = params;
|
const { currentPage = 1, pageSize = 10, countryId, provinceId, ...otherParams } = params;
|
||||||
|
const requestParams = { currentPage, pageSize, ...otherParams };
|
||||||
|
|
||||||
|
// اگر countryId یا provinceId وجود دارد، آنها را به params اضافه کن
|
||||||
|
if (countryId) {
|
||||||
|
requestParams.countryId = countryId;
|
||||||
|
}
|
||||||
|
if (provinceId) {
|
||||||
|
requestParams.provinceId = provinceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔵 City API - list request:', { params: requestParams });
|
||||||
|
|
||||||
const res = await api.get('/api/v1/City', {
|
const res = await api.get('/api/v1/City', {
|
||||||
params: { currentPage, pageSize, ...otherParams },
|
params: requestParams,
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('🟢 City API - list response:', res?.data);
|
||||||
|
|
||||||
|
// پاسخ به صورت { data: { data: [...], filterSummary: {...} } } است
|
||||||
return res?.data?.data?.data || [];
|
return res?.data?.data?.data || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,15 @@ export const countryAPI = {
|
|||||||
|
|
||||||
// GET /api/v1/Country/All (all countries without pagination)
|
// GET /api/v1/Country/All (all countries without pagination)
|
||||||
async listAll() {
|
async listAll() {
|
||||||
|
try {
|
||||||
const res = await api.get('/api/v1/Country/All', { skipAuthRedirect: true });
|
const res = await api.get('/api/v1/Country/All', { skipAuthRedirect: true });
|
||||||
|
// پاسخ به صورت { data: [...], statusCode: 200, isSuccess: true, ... } است
|
||||||
|
console.log('🔵 Country API - listAll response:', res?.data);
|
||||||
return res?.data?.data || [];
|
return res?.data?.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('🔴 Country API - listAll error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// POST /api/v1/Country
|
// POST /api/v1/Country
|
||||||
@@ -26,8 +33,9 @@ export const countryAPI = {
|
|||||||
Name: String(country?.name || ''),
|
Name: String(country?.name || ''),
|
||||||
PhoneCode: String(country?.phoneCode || ''),
|
PhoneCode: String(country?.phoneCode || ''),
|
||||||
CurrencyCode: String(country?.currencyCode || ''),
|
CurrencyCode: String(country?.currencyCode || ''),
|
||||||
TimeZoneName: String(country?.timeZoneName || country?.timeZone || ''),
|
TimeZone: String(country?.timeZoneName || country?.timeZone || ''),
|
||||||
};
|
};
|
||||||
|
console.log('🔵 Country API - create payload:', payload);
|
||||||
const res = await api.post('/api/v1/Country', payload, { skipAuthRedirect: true });
|
const res = await api.post('/api/v1/Country', payload, { skipAuthRedirect: true });
|
||||||
return res?.data;
|
return res?.data;
|
||||||
},
|
},
|
||||||
@@ -37,7 +45,7 @@ export const countryAPI = {
|
|||||||
if (!countryId) {
|
if (!countryId) {
|
||||||
throw new Error('Country ID is required');
|
throw new Error('Country ID is required');
|
||||||
}
|
}
|
||||||
// ساخت payload - سرور انتظار فیلدها با حرف بزرگ دارد
|
// ساخت payload - سرور در PUT انتظار TimeZoneName دارد (نه TimeZone)
|
||||||
const payload = {
|
const payload = {
|
||||||
Name: country?.name ? String(country.name).trim() : '',
|
Name: country?.name ? String(country.name).trim() : '',
|
||||||
PhoneCode: country?.phoneCode ? String(country.phoneCode).trim() : '',
|
PhoneCode: country?.phoneCode ? String(country.phoneCode).trim() : '',
|
||||||
@@ -46,13 +54,13 @@ export const countryAPI = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const url = `/api/v1/Country/${encodeURIComponent(countryId)}`;
|
const url = `/api/v1/Country/${encodeURIComponent(countryId)}`;
|
||||||
console.log('Country API Update:', { url, countryId, payload });
|
console.log('🔵 Country API - update payload:', { url, countryId, payload });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.put(url, payload, { skipAuthRedirect: true });
|
const res = await api.put(url, payload, { skipAuthRedirect: true });
|
||||||
return res?.data;
|
return res?.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Country API Update Error:', {
|
console.error('🔴 Country API - update error:', {
|
||||||
url,
|
url,
|
||||||
countryId,
|
countryId,
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
43
src/services/generalAPI.js
Normal file
43
src/services/generalAPI.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import api from './apiClient';
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// General API
|
||||||
|
// -----------------------------
|
||||||
|
export const generalAPI = {
|
||||||
|
// GET /api/v1/General/Currencies
|
||||||
|
async getCurrencies() {
|
||||||
|
try {
|
||||||
|
const res = await api.get('/api/v1/General/Currencies', { skipAuthRedirect: true });
|
||||||
|
// پاسخ به صورت array مستقیم است
|
||||||
|
return Array.isArray(res?.data) ? res?.data : [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('🔴 General API - getCurrencies error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// GET /api/v1/General/TimeZones
|
||||||
|
async getTimeZones() {
|
||||||
|
try {
|
||||||
|
const res = await api.get('/api/v1/General/TimeZones', { skipAuthRedirect: true });
|
||||||
|
// پاسخ به صورت { data: [...], statusCode: 200, ... } است
|
||||||
|
return res?.data?.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('🔴 General API - getTimeZones error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// GET /api/v1/General/IssuerCapabilities
|
||||||
|
async getIssuerCapabilities() {
|
||||||
|
try {
|
||||||
|
const res = await api.get('/api/v1/General/IssuerCapabilities', { skipAuthRedirect: true });
|
||||||
|
// پاسخ به صورت { data: [...], statusCode: 200, ... } است
|
||||||
|
return res?.data?.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('🔴 General API - getIssuerCapabilities error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
@@ -4,13 +4,32 @@ import api from './apiClient';
|
|||||||
// Issuer API
|
// Issuer API
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
export const issuerAPI = {
|
export const issuerAPI = {
|
||||||
// GET /api/v1/Issuer (with pagination)
|
// GET /api/v1/Issuer (with pagination and filters)
|
||||||
async list(params = {}) {
|
async list(params = {}) {
|
||||||
const { currentPage = 1, pageSize = 100, ...otherParams } = params;
|
const { currentPage = 1, pageSize = 100, isActive, supportCurrencyCode, supportCapabilities, ...otherParams } = params;
|
||||||
|
const requestParams = { currentPage, pageSize, ...otherParams };
|
||||||
|
|
||||||
|
// اگر فیلترها وجود دارند، آنها را به params اضافه کن
|
||||||
|
if (isActive !== undefined && isActive !== null && isActive !== '') {
|
||||||
|
requestParams.isActive = isActive;
|
||||||
|
}
|
||||||
|
if (supportCurrencyCode) {
|
||||||
|
requestParams.supportCurrencyCode = supportCurrencyCode;
|
||||||
|
}
|
||||||
|
if (supportCapabilities) {
|
||||||
|
requestParams.supportCapabilities = supportCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔵 Issuer API - list request:', { params: requestParams });
|
||||||
|
|
||||||
const res = await api.get('/api/v1/Issuer', {
|
const res = await api.get('/api/v1/Issuer', {
|
||||||
params: { currentPage, pageSize, ...otherParams },
|
params: requestParams,
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('🟢 Issuer API - list response:', res?.data);
|
||||||
|
|
||||||
|
// پاسخ به صورت { data: { data: [...], filterSummary: {...} } } است
|
||||||
return res?.data?.data?.data || [];
|
return res?.data?.data?.data || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -26,46 +45,117 @@ export const issuerAPI = {
|
|||||||
|
|
||||||
// POST /api/v1/Issuer
|
// POST /api/v1/Issuer
|
||||||
async create(issuer) {
|
async create(issuer) {
|
||||||
|
// Build payload according to AddIssuerCommand structure:
|
||||||
|
// name (required), supportEmail (required), title (nullable), cityId (Guid nullable), postalCode, addressDetails (nullable)
|
||||||
const payload = {
|
const payload = {
|
||||||
name: String(issuer?.name || ''),
|
name: String(issuer?.name || '').trim(),
|
||||||
supportEmail: String(issuer?.supportEmail || ''),
|
supportEmail: String(issuer?.supportEmail || '').trim(),
|
||||||
postalCode: String(issuer?.postalCode || ''),
|
postalCode: String(issuer?.postalCode || '').trim()
|
||||||
addressDetails: String(issuer?.addressDetails || '')
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Include cityId - use null if empty or invalid
|
// Add title only if provided (nullable field - don't send if empty)
|
||||||
if (issuer?.cityId && issuer.cityId !== '' && issuer.cityId !== 'null' && issuer.cityId !== null) {
|
if (issuer?.title && String(issuer.title).trim()) {
|
||||||
payload.cityId = issuer.cityId;
|
payload.title = String(issuer.title).trim();
|
||||||
} else {
|
|
||||||
payload.cityId = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Issuer Create Payload:', payload);
|
// Add addressDetails only if provided (nullable field - don't send if empty)
|
||||||
|
const addressValue = String(issuer?.addressDetails || issuer?.address || '').trim();
|
||||||
|
if (addressValue) {
|
||||||
|
payload.addressDetails = addressValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include cityId only if valid GUID (nullable Guid - don't send if null/empty)
|
||||||
|
if (issuer?.cityId && issuer.cityId !== '' && issuer.cityId !== 'null' && issuer.cityId !== null) {
|
||||||
|
// Validate GUID format (basic check)
|
||||||
|
const guidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
|
if (guidPattern.test(issuer.cityId)) {
|
||||||
|
payload.cityId = issuer.cityId;
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ Invalid cityId GUID format:', issuer.cityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔵 Issuer Create Payload:', JSON.stringify(payload, null, 2));
|
||||||
|
console.log('🔵 Issuer Create Payload (original):', issuer);
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await api.post('/api/v1/Issuer', payload, { skipAuthRedirect: true });
|
const res = await api.post('/api/v1/Issuer', payload, { skipAuthRedirect: true });
|
||||||
|
console.log('🟢 Issuer Create Response:', res?.data);
|
||||||
return res?.data;
|
return res?.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('🔴 Issuer Create Error:', {
|
||||||
|
status: err?.response?.status,
|
||||||
|
statusText: err?.response?.statusText,
|
||||||
|
data: err?.response?.data,
|
||||||
|
errors: err?.response?.data?.errors,
|
||||||
|
message: err?.response?.data?.message,
|
||||||
|
payload: payload
|
||||||
|
});
|
||||||
|
console.error('🔴 Full error.response:', err?.response);
|
||||||
|
console.error('🔴 Full error.response.data:', err?.response?.data);
|
||||||
|
console.error('🔴 Error response data type:', typeof err?.response?.data);
|
||||||
|
if (err?.response?.data) {
|
||||||
|
try {
|
||||||
|
console.error('🔴 Error response data stringified:', JSON.stringify(err?.response?.data, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('🔴 Error response data (could not stringify):', err?.response?.data);
|
||||||
|
console.error('🔴 Error response data toString:', String(err?.response?.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// PUT /api/v1/Issuer/{id}
|
// PUT /api/v1/Issuer/{id}
|
||||||
async update(id, issuer) {
|
async update(id, issuer) {
|
||||||
|
// Build payload according to UpdateIssuerCommand structure (similar to AddIssuerCommand)
|
||||||
const payload = {
|
const payload = {
|
||||||
name: String(issuer?.name || ''),
|
name: String(issuer?.name || '').trim(),
|
||||||
supportEmail: String(issuer?.supportEmail || ''),
|
supportEmail: String(issuer?.supportEmail || '').trim(),
|
||||||
postalCode: String(issuer?.postalCode || ''),
|
postalCode: String(issuer?.postalCode || '').trim()
|
||||||
addressDetails: String(issuer?.addressDetails || '')
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Include cityId - use null if empty or invalid
|
// Add title if provided (nullable field)
|
||||||
if (issuer?.cityId && issuer.cityId !== '' && issuer.cityId !== 'null' && issuer.cityId !== null) {
|
if (issuer?.title && String(issuer.title).trim()) {
|
||||||
payload.cityId = issuer.cityId;
|
payload.title = String(issuer.title).trim();
|
||||||
} else {
|
|
||||||
payload.cityId = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Issuer Update Payload:', payload);
|
// Add addressDetails if provided (nullable field)
|
||||||
|
const addressValue = String(issuer?.addressDetails || issuer?.address || '').trim();
|
||||||
|
if (addressValue) {
|
||||||
|
payload.addressDetails = addressValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Include cityId - validate GUID format
|
||||||
|
if (issuer?.cityId && issuer.cityId !== '' && issuer.cityId !== 'null' && issuer.cityId !== null) {
|
||||||
|
// Validate GUID format (basic check)
|
||||||
|
const guidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
|
if (guidPattern.test(issuer.cityId)) {
|
||||||
|
payload.cityId = issuer.cityId;
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ Invalid cityId GUID format:', issuer.cityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔵 Issuer Update Payload:', JSON.stringify(payload, null, 2));
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(id)}`, payload, {
|
const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(id)}`, payload, {
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
|
console.log('🟢 Issuer Update Response:', res?.data);
|
||||||
return res?.data;
|
return res?.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('🔴 Issuer Update Error:', {
|
||||||
|
status: err?.response?.status,
|
||||||
|
statusText: err?.response?.statusText,
|
||||||
|
data: err?.response?.data,
|
||||||
|
errors: err?.response?.data?.errors,
|
||||||
|
message: err?.response?.data?.message,
|
||||||
|
payload: payload
|
||||||
|
});
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// DELETE /api/v1/Issuer/{id}
|
// DELETE /api/v1/Issuer/{id}
|
||||||
@@ -84,111 +174,201 @@ export const issuerAPI = {
|
|||||||
return res?.data;
|
return res?.data;
|
||||||
},
|
},
|
||||||
|
|
||||||
// GET /api/v1/Issuer/{id}/Capabilities
|
// GET /api/v1/Issuer/{issuerId}/Capabilities
|
||||||
async getCapabilities(id) {
|
async getCapabilities(issuerId) {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(`/api/v1/Issuer/${encodeURIComponent(id)}/Capabilities`, {
|
console.log('🔵 Getting capabilities for issuer:', issuerId);
|
||||||
|
const res = await api.get(`/api/v1/Issuer/${encodeURIComponent(issuerId)}/Capabilities`, {
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
// Response structure: { data: [...], statusCode, isSuccess, ... }
|
// Response structure: { data: [...], statusCode, isSuccess, ... }
|
||||||
const capabilities = res?.data?.data || [];
|
// Or direct array: [...]
|
||||||
console.log('Capabilities from API:', capabilities);
|
const capabilities = Array.isArray(res?.data) ? res.data : (res?.data?.data || []);
|
||||||
|
console.log('🟢 Capabilities from API:', capabilities);
|
||||||
return capabilities;
|
return capabilities;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('🔴 Error getting capabilities:', {
|
||||||
|
status: err?.response?.status,
|
||||||
|
data: err?.response?.data
|
||||||
|
});
|
||||||
// Handle 404 gracefully - endpoint might not exist for some issuers
|
// Handle 404 gracefully - endpoint might not exist for some issuers
|
||||||
if (err?.response?.status === 404) {
|
if (err?.response?.status === 404) {
|
||||||
console.warn('Capabilities endpoint not found (404), returning empty array');
|
console.warn('⚠️ Capabilities endpoint not found (404), returning empty array');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// PUT /api/v1/Issuer/{id}/Capabilities
|
// PUT /api/v1/Issuer/{issuerId}/Capabilities
|
||||||
async updateCapabilities(id, capabilities) {
|
// Payload: IssuerManageCapabilitiesCommand { capabilities: List<IssuerCapabilityDto> }
|
||||||
// Filter out invalid capabilities and ensure all required fields are present
|
async updateCapabilities(issuerId, capabilities) {
|
||||||
|
// Build IssuerCapabilityDto array from input
|
||||||
|
// Each IssuerCapabilityDto should have: capability, capabilityName, hasCapability (or similar fields)
|
||||||
const validCapabilities = Array.isArray(capabilities) ? capabilities
|
const validCapabilities = Array.isArray(capabilities) ? capabilities
|
||||||
.filter(cap => {
|
.filter(cap => {
|
||||||
const capabilityValue = typeof cap === 'string' ? cap : (cap.capability || '');
|
// Filter out invalid capabilities
|
||||||
|
if (typeof cap === 'string') {
|
||||||
|
return cap && String(cap).trim() !== '';
|
||||||
|
}
|
||||||
|
if (typeof cap === 'object' && cap !== null) {
|
||||||
|
const capabilityValue = cap.capability || cap.capabilityName || '';
|
||||||
return capabilityValue && String(capabilityValue).trim() !== '';
|
return capabilityValue && String(capabilityValue).trim() !== '';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
})
|
})
|
||||||
.map(cap => {
|
.map(cap => {
|
||||||
const capabilityValue = typeof cap === 'string' ? cap : (cap.capability || '');
|
// Convert to IssuerCapabilityDto format
|
||||||
const capabilityName = typeof cap === 'object' && cap.capabilityName ? cap.capabilityName : capabilityValue;
|
if (typeof cap === 'string') {
|
||||||
const hasCapability = typeof cap === 'object' ? (cap.hasCapability !== undefined ? cap.hasCapability : true) : true;
|
return {
|
||||||
|
capability: String(cap).trim(),
|
||||||
// Ensure capability is a valid string (not empty, not null, not undefined)
|
capabilityName: String(cap).trim(),
|
||||||
const capValue = String(capabilityValue).trim();
|
hasCapability: true
|
||||||
if (!capValue) {
|
};
|
||||||
return null; // Will be filtered out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it's an object, ensure it has the required fields
|
||||||
|
const capabilityValue = cap.capability || cap.capabilityName || '';
|
||||||
|
const capabilityName = cap.capabilityName || cap.capability || String(capabilityValue).trim();
|
||||||
|
const hasCapability = cap.hasCapability !== undefined ? Boolean(cap.hasCapability) : true;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
capability: capValue,
|
capability: String(capabilityValue).trim(),
|
||||||
capabilityName: String(capabilityName).trim() || capValue,
|
capabilityName: String(capabilityName).trim() || String(capabilityValue).trim(),
|
||||||
hasCapability: Boolean(hasCapability)
|
hasCapability: hasCapability
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(cap => cap !== null) : [];
|
.filter(cap => cap.capability && cap.capability.trim() !== '') : [];
|
||||||
|
|
||||||
// Wrap in IssuerManageCapabilitiesCommand structure
|
// Wrap in IssuerManageCapabilitiesCommand structure
|
||||||
const payload = {
|
const payload = {
|
||||||
capabilities: validCapabilities
|
capabilities: validCapabilities
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Update Capabilities Payload:', JSON.stringify(payload, null, 2));
|
console.log('🔵 Update Capabilities Payload:', JSON.stringify(payload, null, 2));
|
||||||
console.log('Valid capabilities count:', validCapabilities.length);
|
console.log('🔵 Issuer ID:', issuerId);
|
||||||
console.log('Capabilities details:', validCapabilities);
|
console.log('🔵 Valid capabilities count:', validCapabilities.length);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(id)}/Capabilities`, payload, {
|
const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(issuerId)}/Capabilities`, payload, {
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
|
console.log('🟢 Capabilities Update Response:', res?.data);
|
||||||
return res?.data;
|
return res?.data;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Capabilities update error details:', {
|
console.error('🔴 Capabilities update error:', {
|
||||||
status: err?.response?.status,
|
status: err?.response?.status,
|
||||||
|
statusText: err?.response?.statusText,
|
||||||
data: err?.response?.data,
|
data: err?.response?.data,
|
||||||
errors: err?.response?.data?.errors,
|
errors: err?.response?.data?.errors,
|
||||||
|
message: err?.response?.data?.message,
|
||||||
payload: payload
|
payload: payload
|
||||||
});
|
});
|
||||||
|
if (err?.response?.data) {
|
||||||
|
try {
|
||||||
|
console.error('🔴 Error response data stringified:', JSON.stringify(err?.response?.data, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('🔴 Error response data (could not stringify):', err?.response?.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// GET /api/v1/Issuer/{id}/AllowedCurrencies
|
// GET /api/v1/Issuer/{issuerId}/AllowedCurrencies
|
||||||
async getAllowedCurrencies(id) {
|
async getAllowedCurrencies(issuerId) {
|
||||||
try {
|
try {
|
||||||
const res = await api.get(`/api/v1/Issuer/${encodeURIComponent(id)}/AllowedCurrencies`, {
|
console.log('🔵 Getting allowed currencies for issuer:', issuerId);
|
||||||
|
const res = await api.get(`/api/v1/Issuer/${encodeURIComponent(issuerId)}/AllowedCurrencies`, {
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
// Response is a direct array: [{code, name, nativeName, symbol, numericCode, decimalPlaces}, ...]
|
// Response is a direct array: [{code, name, nativeName, symbol, numericCode, decimalPlaces, allowed}, ...]
|
||||||
|
// Or wrapped: { data: [...] }
|
||||||
const currencies = Array.isArray(res?.data) ? res.data : (res?.data?.data || []);
|
const currencies = Array.isArray(res?.data) ? res.data : (res?.data?.data || []);
|
||||||
console.log('Allowed Currencies from API:', currencies);
|
console.log('🟢 Allowed Currencies from API:', currencies);
|
||||||
return currencies;
|
return currencies;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('🔴 Error getting allowed currencies:', {
|
||||||
|
status: err?.response?.status,
|
||||||
|
data: err?.response?.data
|
||||||
|
});
|
||||||
// Handle 404 gracefully - endpoint might not exist for some issuers
|
// Handle 404 gracefully - endpoint might not exist for some issuers
|
||||||
if (err?.response?.status === 404) {
|
if (err?.response?.status === 404) {
|
||||||
console.warn('AllowedCurrencies endpoint not found (404), returning empty array');
|
console.warn('⚠️ AllowedCurrencies endpoint not found (404), returning empty array');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// PUT /api/v1/Issuer/{id}/AllowedCurrencies
|
// PUT /api/v1/Issuer/{issuerId}/AllowedCurrencies
|
||||||
async updateAllowedCurrencies(id, allowedCurrencies) {
|
// Payload: { allowedCurrencies: List<IssuerAllowedCurrencyDto> }
|
||||||
|
async updateAllowedCurrencies(issuerId, allowedCurrencies) {
|
||||||
|
// Build IssuerAllowedCurrencyDto array from input
|
||||||
|
// Each should have: currencyCode, currencyEnglishName, allowed
|
||||||
|
const currencyMap = new Map(); // Use Map to avoid duplicates
|
||||||
|
|
||||||
|
if (Array.isArray(allowedCurrencies)) {
|
||||||
|
allowedCurrencies.forEach(curr => {
|
||||||
|
const currencyCode = String(curr.currencyCode || curr.code || '').trim();
|
||||||
|
if (!currencyCode) return; // Skip invalid currencies
|
||||||
|
|
||||||
|
// Use currencyCode as key to avoid duplicates
|
||||||
|
if (!currencyMap.has(currencyCode)) {
|
||||||
|
const currencyEnglishName = String(curr.currencyEnglishName || curr.name || '').trim();
|
||||||
|
const allowed = curr.allowed !== undefined ? Boolean(curr.allowed) : true;
|
||||||
|
|
||||||
|
currencyMap.set(currencyCode, {
|
||||||
|
currencyCode: currencyCode,
|
||||||
|
currencyEnglishName: currencyEnglishName || currencyCode,
|
||||||
|
allowed: allowed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert Map to array
|
||||||
|
const validCurrencies = Array.from(currencyMap.values());
|
||||||
|
|
||||||
|
// Wrap in command structure
|
||||||
const payload = {
|
const payload = {
|
||||||
allowedCurrencies: Array.isArray(allowedCurrencies) ? allowedCurrencies.map(curr => ({
|
allowedCurrencies: validCurrencies
|
||||||
currencyCode: curr.currencyCode || curr.code || '',
|
|
||||||
currencyEnglishName: curr.currencyEnglishName || curr.name || '',
|
|
||||||
allowed: curr.allowed !== undefined ? curr.allowed : true
|
|
||||||
})) : []
|
|
||||||
};
|
};
|
||||||
const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(id)}/AllowedCurrencies`, payload, {
|
|
||||||
|
console.log('🔵 Update Allowed Currencies Payload:', JSON.stringify(payload, null, 2));
|
||||||
|
console.log('🔵 Issuer ID:', issuerId);
|
||||||
|
console.log('🔵 Valid currencies count:', validCurrencies.length);
|
||||||
|
console.log('🔵 Currencies with allowed=true:', validCurrencies.filter(c => c.allowed).length);
|
||||||
|
console.log('🔵 Currencies with allowed=false:', validCurrencies.filter(c => !c.allowed).length);
|
||||||
|
console.log('🔵 Duplicate check - unique currency codes:', [...currencyMap.keys()]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(issuerId)}/AllowedCurrencies`, payload, {
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
|
console.log('🟢 Allowed Currencies Update Response:', res?.data);
|
||||||
return res?.data;
|
return res?.data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('🔴 Allowed Currencies update error:', {
|
||||||
|
status: err?.response?.status,
|
||||||
|
statusText: err?.response?.statusText,
|
||||||
|
data: err?.response?.data,
|
||||||
|
errors: err?.response?.data?.errors,
|
||||||
|
message: err?.response?.data?.message,
|
||||||
|
code: err?.response?.data?.code,
|
||||||
|
payload: payload
|
||||||
|
});
|
||||||
|
console.error('🔴 Full error.response:', err?.response);
|
||||||
|
if (err?.response?.data) {
|
||||||
|
try {
|
||||||
|
console.error('🔴 Error response data stringified:', JSON.stringify(err?.response?.data, null, 2));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('🔴 Error response data (could not stringify):', err?.response?.data);
|
||||||
|
console.error('🔴 Error response data toString:', String(err?.response?.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,26 @@ import api from './apiClient';
|
|||||||
// Province API
|
// Province API
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
export const provinceAPI = {
|
export const provinceAPI = {
|
||||||
// GET /api/v1/Province (with pagination)
|
// GET /api/v1/Province (with pagination and filters)
|
||||||
async list(params = {}) {
|
async list(params = {}) {
|
||||||
const { currentPage = 1, pageSize = 10, ...otherParams } = params;
|
const { currentPage = 1, pageSize = 10, countryId, ...otherParams } = params;
|
||||||
|
const requestParams = { currentPage, pageSize, ...otherParams };
|
||||||
|
|
||||||
|
// اگر countryId وجود دارد، آن را به params اضافه کن
|
||||||
|
if (countryId) {
|
||||||
|
requestParams.countryId = countryId;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('🔵 Province API - list request:', { params: requestParams });
|
||||||
|
|
||||||
const res = await api.get('/api/v1/Province', {
|
const res = await api.get('/api/v1/Province', {
|
||||||
params: { currentPage, pageSize, ...otherParams },
|
params: requestParams,
|
||||||
skipAuthRedirect: true
|
skipAuthRedirect: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('🟢 Province API - list response:', res?.data);
|
||||||
|
|
||||||
|
// پاسخ به صورت { data: { data: [...], filterSummary: {...} } } است
|
||||||
return res?.data?.data?.data || [];
|
return res?.data?.data?.data || [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,44 @@ function writeUsersToStorage(users){ localStorage.setItem(USERS_STORAGE_KEY, JSO
|
|||||||
|
|
||||||
export const usersAPI = {
|
export const usersAPI = {
|
||||||
async list({searchQuery='',currentPage=1,pageSize=100}={}) {
|
async list({searchQuery='',currentPage=1,pageSize=100}={}) {
|
||||||
const res = await api.get('/api/v1/User',{ params:{searchQuery,currentPage,pageSize}, skipAuthRedirect:true });
|
try {
|
||||||
|
// اطمینان از اینکه params همیشه object است
|
||||||
|
const params = {
|
||||||
|
searchQuery: searchQuery || '',
|
||||||
|
currentPage: currentPage || 1,
|
||||||
|
pageSize: pageSize || 100
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('🔵 Users API - list request:', {
|
||||||
|
url: '/api/v1/User',
|
||||||
|
method: 'GET',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await api.get('/api/v1/User',{
|
||||||
|
params,
|
||||||
|
skipAuthRedirect:true
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🟢 Users API - list response:', res?.data);
|
||||||
|
|
||||||
return res?.data?.data?.data||[];
|
return res?.data?.data?.data||[];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('🔴 Users API - list error:', error);
|
||||||
|
console.error('🔴 Error details:', {
|
||||||
|
url: error.config?.url,
|
||||||
|
method: error.config?.method,
|
||||||
|
params: error.config?.params,
|
||||||
|
data: error.config?.data,
|
||||||
|
response: error.response?.data
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async get(id){
|
||||||
|
if (!id) throw new Error('User ID is required');
|
||||||
|
const res = await api.get(`/api/v1/User/${encodeURIComponent(id)}`, { skipAuthRedirect: true });
|
||||||
|
return res?.data?.data || res?.data;
|
||||||
},
|
},
|
||||||
async create(user){
|
async create(user){
|
||||||
const payload = { firstName:String(user?.firstName||''), lastName:String(user?.lastName||''), email:String(user?.email||''), mobile:String(user?.mobile||''), isActive:!!user?.isActive };
|
const payload = { firstName:String(user?.firstName||''), lastName:String(user?.lastName||''), email:String(user?.email||''), mobile:String(user?.mobile||''), isActive:!!user?.isActive };
|
||||||
|
|||||||
@@ -6,12 +6,15 @@ export const useAuthStore = create(
|
|||||||
(set) => ({
|
(set) => ({
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
|
user: null,
|
||||||
setLoggedIn: (status) => set({ isLoggedIn: status }),
|
setLoggedIn: (status) => set({ isLoggedIn: status }),
|
||||||
setLoading: (status) => set({ loading: status }),
|
setLoading: (status) => set({ loading: status }),
|
||||||
|
setUser: (userData) => set({ user: userData }),
|
||||||
|
logout: () => set({ isLoggedIn: false, user: null }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: "auth_store_v1",
|
name: "auth_store_v1",
|
||||||
partialize: (state) => ({ isLoggedIn: state.isLoggedIn }),
|
partialize: (state) => ({ isLoggedIn: state.isLoggedIn, user: state.user }),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ export default defineConfig({
|
|||||||
// اجازه ارسال کوکیها
|
// اجازه ارسال کوکیها
|
||||||
configure: (proxy) => {
|
configure: (proxy) => {
|
||||||
proxy.on("proxyReq", (proxyReq, req) => {
|
proxy.on("proxyReq", (proxyReq, req) => {
|
||||||
|
console.log("🔵 Proxy Request:", {
|
||||||
|
method: req.method,
|
||||||
|
url: req.url,
|
||||||
|
headers: req.headers
|
||||||
|
});
|
||||||
const origin = req.headers.origin;
|
const origin = req.headers.origin;
|
||||||
if (origin) {
|
if (origin) {
|
||||||
proxyReq.setHeader("origin", origin);
|
proxyReq.setHeader("origin", origin);
|
||||||
@@ -20,14 +25,24 @@ export default defineConfig({
|
|||||||
proxyReq.setHeader("Access-Control-Allow-Credentials", "true");
|
proxyReq.setHeader("Access-Control-Allow-Credentials", "true");
|
||||||
});
|
});
|
||||||
|
|
||||||
proxy.on("proxyRes", (proxyRes) => {
|
proxy.on("proxyRes", (proxyRes, req) => {
|
||||||
|
console.log("🟢 Proxy Response:", {
|
||||||
|
statusCode: proxyRes.statusCode,
|
||||||
|
statusMessage: proxyRes.statusMessage,
|
||||||
|
headers: proxyRes.headers,
|
||||||
|
url: req.url
|
||||||
|
});
|
||||||
// اطمینان از دریافت کوکی از سرور
|
// اطمینان از دریافت کوکی از سرور
|
||||||
proxyRes.headers["Access-Control-Allow-Origin"] = "http://localhost:5173";
|
proxyRes.headers["Access-Control-Allow-Origin"] = "http://localhost:5173";
|
||||||
proxyRes.headers["Access-Control-Allow-Credentials"] = "true";
|
proxyRes.headers["Access-Control-Allow-Credentials"] = "true";
|
||||||
});
|
});
|
||||||
|
|
||||||
proxy.on("error", (err) => {
|
proxy.on("error", (err, req) => {
|
||||||
console.log("Proxy Error:", err);
|
console.error("🔴 Proxy Error:", {
|
||||||
|
error: err,
|
||||||
|
url: req?.url,
|
||||||
|
method: req?.method
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user