From fd2b6537cd9c291e0dd1552c4785c61098bcc98d Mon Sep 17 00:00:00 2001 From: ghazall-ag Date: Thu, 4 Dec 2025 22:00:53 +0330 Subject: [PATCH] fix(Issuer): correct AllowedCurrencies and Capabilities structure --- src/components/Navbar.jsx | 40 +-- src/components/Sidebar.jsx | 2 +- src/main.jsx | 35 +++ src/pages/Issuer.jsx | 495 +++++++++++++++++++++++++++++++++--- src/pages/Location.jsx | 240 ++++++++++++++--- src/pages/Login.jsx | 22 +- src/services/api.js | 1 + src/services/apiClient.js | 131 +++++++++- src/services/authAPI.js | 42 ++- src/services/cityAPI.js | 22 +- src/services/countryAPI.js | 20 +- src/services/generalAPI.js | 43 ++++ src/services/issuerAPI.js | 328 ++++++++++++++++++------ src/services/provinceAPI.js | 19 +- src/services/usersAPI.js | 40 ++- src/store/authStore.js | 5 +- vite.config.js | 21 +- 17 files changed, 1300 insertions(+), 206 deletions(-) create mode 100644 src/services/generalAPI.js diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index 99dabbd..f8a9bac 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -2,20 +2,15 @@ import React, { useState } from 'react'; import { Menu, Sun, Moon, User, LogOut, ChevronDown } from 'lucide-react'; import { signOut } from '../services/api'; import { useAuthStore } from '../store/authStore'; -import { useAuth } from '../context/AuthContext'; 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 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(() => { return localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches); @@ -39,6 +34,8 @@ const Navbar = ({ onSidebarToggle }) => { const handleLogout = async () => { try { + const logout = useAuthStore.getState().logout; + logout(); // Clear user data from store await signOut(); } catch (e) { // no-op; interceptor handles redirect and state @@ -59,7 +56,7 @@ const Navbar = ({ onSidebarToggle }) => {

- KHalij Payment + KHalij Finance

@@ -95,21 +92,28 @@ const Navbar = ({ onSidebarToggle }) => { {/* Dropdown menu */} {isUserMenuOpen && ( -
-
+
+

{isLoggedIn ? userName : 'Guest'}

-

- {isLoggedIn ? userEmail : ''} -

+ {isLoggedIn && userEmail && ( +

+ {userEmail} +

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

+ نقش: {userData.role} +

+ )}
)} diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 3a4ab2b..8821d57 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -43,7 +43,7 @@ const Sidebar = ({ isOpen, onToggle }) => { `}>

- KHalij Payment + KHalij Finance

+
+ +
{renderModal(isModalOpen, 'Add Issuer', onAddIssuer, addForm, setAddForm, () => setIsModalOpen(false))} {renderModal(isEditModalOpen, 'Edit Issuer', onUpdateIssuer, editForm, setEditForm, () => setIsEditModalOpen(false))} + {/* Filter Modal */} + {isFilterModalOpen && ( + <> +
setIsFilterModalOpen(false)} /> +
+
+

Filter Issuers

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + )} + {/* Capabilities Modal */} {capabilitiesModal && ( <> @@ -581,7 +970,42 @@ const Issuer = () => { Manage Allowed Currencies for {currenciesModal.name}
- {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 ( + + ); + }) + ) : ( + currencies.map((curr) => { const code = curr.currencyCode?.code || curr.code || ''; const name = curr.currencyCode?.name || curr.name || ''; const checked = selectedCurrencies.includes(code); @@ -601,7 +1025,8 @@ const Issuer = () => { {code} - {name} ); - })} + }) + )}
@@ -443,19 +555,59 @@ const Location = () => { {/* City Tab */} {activeTab === 'city' && ( <> -
-
- - setCityFilter(e.target.value)} - placeholder="Filter cities..." - className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500" - /> +
+
+
+ + setCityFilter(e.target.value)} + placeholder="Filter cities..." + className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500" + /> +
+
+ +
+
+ +
@@ -508,21 +660,27 @@ const Location = () => { > {currencies.map((curr) => ( - ))}
- setCountryForm({ ...countryForm, timeZoneName: e.target.value })} className="w-full p-2 border rounded-lg" - placeholder="e.g., UTC+3:30" required - /> + > + + {timeZones.map((tz) => ( + + ))} +

- Khalij pay Admin Panel + Khalij Finance Admin

Sign in to your account diff --git a/src/services/api.js b/src/services/api.js index ab19331..8c257bb 100644 --- a/src/services/api.js +++ b/src/services/api.js @@ -14,3 +14,4 @@ export { provinceAPI } from './provinceAPI'; export { cityAPI } from './cityAPI'; export { issuerAPI } from './issuerAPI'; export { listPermissions } from './permissionsAPI'; +export { generalAPI } from './generalAPI'; diff --git a/src/services/apiClient.js b/src/services/apiClient.js index 67b9081..ddd9408 100644 --- a/src/services/apiClient.js +++ b/src/services/apiClient.js @@ -43,15 +43,38 @@ if (typeof window !== 'undefined') { // Request interceptor // ----------------------------- 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; if (skipAuthRedirect) { - const authState = useAuthStore.getState(); - if (!authState?.isLoggedIn) { - const CancelToken = axios.CancelToken; - const source = CancelToken.source(); - source.cancel('User not logged in'); - config.cancelToken = source.token; - config._skipRequest = true; + // بررسی اینکه آیا درخواست به 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(); + console.log("🔵 Auth state:", { isLoggedIn: authState?.isLoggedIn }); + if (!authState?.isLoggedIn) { + console.warn("⚠️ Canceling request - user not logged in"); + const CancelToken = axios.CancelToken; + const source = CancelToken.source(); + source.cancel('User not logged in'); + config.cancelToken = source.token; + config._skipRequest = true; + } + } else { + console.log("✅ Allowing auth endpoint request"); } } return config; @@ -61,10 +84,59 @@ api.interceptors.request.use(config => { // Response interceptor // ----------------------------- 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 => { + 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 (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 status = error?.response?.status; @@ -84,10 +156,45 @@ api.interceptors.response.use( } // حفظ 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) { return Promise.reject({ - ...errorData, + ...error, response: { ...error.response, status: error.response.status, @@ -96,7 +203,7 @@ api.interceptors.response.use( }); } - return Promise.reject(errorData); + return Promise.reject(error); } ); diff --git a/src/services/authAPI.js b/src/services/authAPI.js index b411e73..b7dbe51 100644 --- a/src/services/authAPI.js +++ b/src/services/authAPI.js @@ -4,9 +4,42 @@ import { useAuthStore } from "../store/authStore"; // ----------------------------- // Auth API // ----------------------------- -export async function login(username, password) { - const res = await api.post("/api/v1/Auth/SignIn", { userName: username, username, email: username, password }); - return res.data; +export async function login(email, password) { + try { + 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() { @@ -15,7 +48,8 @@ export async function signOut() { } catch (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 = "/"; } diff --git a/src/services/cityAPI.js b/src/services/cityAPI.js index 54f96ed..bff8ede 100644 --- a/src/services/cityAPI.js +++ b/src/services/cityAPI.js @@ -4,13 +4,29 @@ import api from './apiClient'; // City API // ----------------------------- export const cityAPI = { - // GET /api/v1/City (with pagination) + // GET /api/v1/City (with pagination and filters) 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', { - params: { currentPage, pageSize, ...otherParams }, + params: requestParams, skipAuthRedirect: true }); + + console.log('🟢 City API - list response:', res?.data); + + // پاسخ به صورت { data: { data: [...], filterSummary: {...} } } است return res?.data?.data?.data || []; }, diff --git a/src/services/countryAPI.js b/src/services/countryAPI.js index 60f1b2c..1902192 100644 --- a/src/services/countryAPI.js +++ b/src/services/countryAPI.js @@ -16,8 +16,15 @@ export const countryAPI = { // GET /api/v1/Country/All (all countries without pagination) async listAll() { - const res = await api.get('/api/v1/Country/All', { skipAuthRedirect: true }); - return res?.data?.data || []; + try { + 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 || []; + } catch (error) { + console.error('🔴 Country API - listAll error:', error); + throw error; + } }, // POST /api/v1/Country @@ -26,8 +33,9 @@ export const countryAPI = { Name: String(country?.name || ''), PhoneCode: String(country?.phoneCode || ''), 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 }); return res?.data; }, @@ -37,7 +45,7 @@ export const countryAPI = { if (!countryId) { throw new Error('Country ID is required'); } - // ساخت payload - سرور انتظار فیلدها با حرف بزرگ دارد + // ساخت payload - سرور در PUT انتظار TimeZoneName دارد (نه TimeZone) const payload = { Name: country?.name ? String(country.name).trim() : '', PhoneCode: country?.phoneCode ? String(country.phoneCode).trim() : '', @@ -46,13 +54,13 @@ export const countryAPI = { }; 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 { const res = await api.put(url, payload, { skipAuthRedirect: true }); return res?.data; } catch (error) { - console.error('Country API Update Error:', { + console.error('🔴 Country API - update error:', { url, countryId, payload, diff --git a/src/services/generalAPI.js b/src/services/generalAPI.js new file mode 100644 index 0000000..ecf60a3 --- /dev/null +++ b/src/services/generalAPI.js @@ -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; + } + }, +}; + diff --git a/src/services/issuerAPI.js b/src/services/issuerAPI.js index 19ca9cd..cf1f279 100644 --- a/src/services/issuerAPI.js +++ b/src/services/issuerAPI.js @@ -4,13 +4,32 @@ import api from './apiClient'; // Issuer API // ----------------------------- export const issuerAPI = { - // GET /api/v1/Issuer (with pagination) + // GET /api/v1/Issuer (with pagination and filters) 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', { - params: { currentPage, pageSize, ...otherParams }, + params: requestParams, skipAuthRedirect: true }); + + console.log('🟢 Issuer API - list response:', res?.data); + + // پاسخ به صورت { data: { data: [...], filterSummary: {...} } } است return res?.data?.data?.data || []; }, @@ -26,46 +45,117 @@ export const issuerAPI = { // POST /api/v1/Issuer async create(issuer) { + // Build payload according to AddIssuerCommand structure: + // name (required), supportEmail (required), title (nullable), cityId (Guid nullable), postalCode, addressDetails (nullable) const payload = { - name: String(issuer?.name || ''), - supportEmail: String(issuer?.supportEmail || ''), - postalCode: String(issuer?.postalCode || ''), - addressDetails: String(issuer?.addressDetails || '') + name: String(issuer?.name || '').trim(), + supportEmail: String(issuer?.supportEmail || '').trim(), + postalCode: String(issuer?.postalCode || '').trim() }; - // Include cityId - use null if empty or invalid - if (issuer?.cityId && issuer.cityId !== '' && issuer.cityId !== 'null' && issuer.cityId !== null) { - payload.cityId = issuer.cityId; - } else { - payload.cityId = null; + // Add title only if provided (nullable field - don't send if empty) + if (issuer?.title && String(issuer.title).trim()) { + payload.title = String(issuer.title).trim(); } - console.log('Issuer Create Payload:', payload); - const res = await api.post('/api/v1/Issuer', payload, { skipAuthRedirect: true }); - return res?.data; + // 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 }); + console.log('🟢 Issuer Create Response:', 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} async update(id, issuer) { + // Build payload according to UpdateIssuerCommand structure (similar to AddIssuerCommand) const payload = { - name: String(issuer?.name || ''), - supportEmail: String(issuer?.supportEmail || ''), - postalCode: String(issuer?.postalCode || ''), - addressDetails: String(issuer?.addressDetails || '') + name: String(issuer?.name || '').trim(), + supportEmail: String(issuer?.supportEmail || '').trim(), + postalCode: String(issuer?.postalCode || '').trim() }; - // Include cityId - use null if empty or invalid - if (issuer?.cityId && issuer.cityId !== '' && issuer.cityId !== 'null' && issuer.cityId !== null) { - payload.cityId = issuer.cityId; - } else { - payload.cityId = null; + // Add title if provided (nullable field) + if (issuer?.title && String(issuer.title).trim()) { + payload.title = String(issuer.title).trim(); } - console.log('Issuer Update Payload:', payload); - const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(id)}`, payload, { - skipAuthRedirect: true - }); - return res?.data; + // 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, { + skipAuthRedirect: true + }); + console.log('🟢 Issuer Update Response:', 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} @@ -84,111 +174,201 @@ export const issuerAPI = { return res?.data; }, - // GET /api/v1/Issuer/{id}/Capabilities - async getCapabilities(id) { + // GET /api/v1/Issuer/{issuerId}/Capabilities + async getCapabilities(issuerId) { 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 }); // Response structure: { data: [...], statusCode, isSuccess, ... } - const capabilities = res?.data?.data || []; - console.log('Capabilities from API:', capabilities); + // Or direct array: [...] + const capabilities = Array.isArray(res?.data) ? res.data : (res?.data?.data || []); + console.log('🟢 Capabilities from API:', capabilities); return capabilities; } 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 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 []; } throw err; } }, - // PUT /api/v1/Issuer/{id}/Capabilities - async updateCapabilities(id, capabilities) { - // Filter out invalid capabilities and ensure all required fields are present + // PUT /api/v1/Issuer/{issuerId}/Capabilities + // Payload: IssuerManageCapabilitiesCommand { capabilities: List } + 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 .filter(cap => { - const capabilityValue = typeof cap === 'string' ? cap : (cap.capability || ''); - return capabilityValue && String(capabilityValue).trim() !== ''; + // 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 false; }) .map(cap => { - const capabilityValue = typeof cap === 'string' ? cap : (cap.capability || ''); - const capabilityName = typeof cap === 'object' && cap.capabilityName ? cap.capabilityName : capabilityValue; - const hasCapability = typeof cap === 'object' ? (cap.hasCapability !== undefined ? cap.hasCapability : true) : true; - - // Ensure capability is a valid string (not empty, not null, not undefined) - const capValue = String(capabilityValue).trim(); - if (!capValue) { - return null; // Will be filtered out + // Convert to IssuerCapabilityDto format + if (typeof cap === 'string') { + return { + capability: String(cap).trim(), + capabilityName: String(cap).trim(), + hasCapability: true + }; } + // 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 { - capability: capValue, - capabilityName: String(capabilityName).trim() || capValue, - hasCapability: Boolean(hasCapability) + capability: String(capabilityValue).trim(), + capabilityName: String(capabilityName).trim() || String(capabilityValue).trim(), + hasCapability: hasCapability }; }) - .filter(cap => cap !== null) : []; + .filter(cap => cap.capability && cap.capability.trim() !== '') : []; // Wrap in IssuerManageCapabilitiesCommand structure const payload = { capabilities: validCapabilities }; - console.log('Update Capabilities Payload:', JSON.stringify(payload, null, 2)); - console.log('Valid capabilities count:', validCapabilities.length); - console.log('Capabilities details:', validCapabilities); + console.log('🔵 Update Capabilities Payload:', JSON.stringify(payload, null, 2)); + console.log('🔵 Issuer ID:', issuerId); + console.log('🔵 Valid capabilities count:', validCapabilities.length); 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 }); + console.log('🟢 Capabilities Update Response:', res?.data); return res?.data; } catch (err) { - console.error('Capabilities update error details:', { + console.error('🔴 Capabilities 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 }); + 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; } }, - // GET /api/v1/Issuer/{id}/AllowedCurrencies - async getAllowedCurrencies(id) { + // GET /api/v1/Issuer/{issuerId}/AllowedCurrencies + async getAllowedCurrencies(issuerId) { 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 }); - // 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 || []); - console.log('Allowed Currencies from API:', currencies); + console.log('🟢 Allowed Currencies from API:', currencies); return currencies; } 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 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 []; } throw err; } }, - // PUT /api/v1/Issuer/{id}/AllowedCurrencies - async updateAllowedCurrencies(id, allowedCurrencies) { + // PUT /api/v1/Issuer/{issuerId}/AllowedCurrencies + // Payload: { allowedCurrencies: List } + 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 = { - allowedCurrencies: Array.isArray(allowedCurrencies) ? allowedCurrencies.map(curr => ({ - currencyCode: curr.currencyCode || curr.code || '', - currencyEnglishName: curr.currencyEnglishName || curr.name || '', - allowed: curr.allowed !== undefined ? curr.allowed : true - })) : [] + allowedCurrencies: validCurrencies }; - const res = await api.put(`/api/v1/Issuer/${encodeURIComponent(id)}/AllowedCurrencies`, payload, { - skipAuthRedirect: true - }); - return res?.data; + + 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 + }); + console.log('🟢 Allowed Currencies Update Response:', 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; + } } }; diff --git a/src/services/provinceAPI.js b/src/services/provinceAPI.js index 6ba4513..a0b48f7 100644 --- a/src/services/provinceAPI.js +++ b/src/services/provinceAPI.js @@ -4,13 +4,26 @@ import api from './apiClient'; // Province API // ----------------------------- export const provinceAPI = { - // GET /api/v1/Province (with pagination) + // GET /api/v1/Province (with pagination and filters) 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', { - params: { currentPage, pageSize, ...otherParams }, + params: requestParams, skipAuthRedirect: true }); + + console.log('🟢 Province API - list response:', res?.data); + + // پاسخ به صورت { data: { data: [...], filterSummary: {...} } } است return res?.data?.data?.data || []; }, diff --git a/src/services/usersAPI.js b/src/services/usersAPI.js index dd3bc1a..6e4abb0 100644 --- a/src/services/usersAPI.js +++ b/src/services/usersAPI.js @@ -9,8 +9,44 @@ function writeUsersToStorage(users){ localStorage.setItem(USERS_STORAGE_KEY, JSO export const usersAPI = { async list({searchQuery='',currentPage=1,pageSize=100}={}) { - const res = await api.get('/api/v1/User',{ params:{searchQuery,currentPage,pageSize}, skipAuthRedirect:true }); - return res?.data?.data?.data||[]; + 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||[]; + } 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){ const payload = { firstName:String(user?.firstName||''), lastName:String(user?.lastName||''), email:String(user?.email||''), mobile:String(user?.mobile||''), isActive:!!user?.isActive }; diff --git a/src/store/authStore.js b/src/store/authStore.js index f5d4a1d..0d60c2c 100644 --- a/src/store/authStore.js +++ b/src/store/authStore.js @@ -6,12 +6,15 @@ export const useAuthStore = create( (set) => ({ isLoggedIn: false, loading: false, + user: null, setLoggedIn: (status) => set({ isLoggedIn: status }), setLoading: (status) => set({ loading: status }), + setUser: (userData) => set({ user: userData }), + logout: () => set({ isLoggedIn: false, user: null }), }), { name: "auth_store_v1", - partialize: (state) => ({ isLoggedIn: state.isLoggedIn }), + partialize: (state) => ({ isLoggedIn: state.isLoggedIn, user: state.user }), } ) ); diff --git a/vite.config.js b/vite.config.js index 23d7469..7694968 100644 --- a/vite.config.js +++ b/vite.config.js @@ -12,6 +12,11 @@ export default defineConfig({ // اجازه ارسال کوکی‌ها configure: (proxy) => { proxy.on("proxyReq", (proxyReq, req) => { + console.log("🔵 Proxy Request:", { + method: req.method, + url: req.url, + headers: req.headers + }); const origin = req.headers.origin; if (origin) { proxyReq.setHeader("origin", origin); @@ -20,14 +25,24 @@ export default defineConfig({ 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-Credentials"] = "true"; }); - proxy.on("error", (err) => { - console.log("Proxy Error:", err); + proxy.on("error", (err, req) => { + console.error("🔴 Proxy Error:", { + error: err, + url: req?.url, + method: req?.method + }); }); }, },