feat(country-province-city): add, edit functionality

This commit is contained in:
ghazall-ag
2025-11-17 00:40:53 +03:30
parent 77cc7534a0
commit 7cc442b600
20 changed files with 2008 additions and 280 deletions

View File

@@ -1,254 +1,15 @@
import axios from 'axios';
import { useAuthStore } from "../store/authStore";
// -----------------------------
// تنظیم BASE_URL
// Main API Export File
// -----------------------------
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ||
(import.meta.env.DEV ? "/" : "https://khalijpay-core.qaserver.ir");
// This file exports all API modules for backward compatibility
// Individual APIs are now in separate files for better organization
// -----------------------------
// ایجاد instance از axios
// -----------------------------
const api = axios.create({
baseURL: API_BASE_URL,
withCredentials: true,
headers: { "Content-Type": "application/json" },
});
// -----------------------------
// Global error handler برای suppress کردن 401/404
// -----------------------------
if (typeof window !== 'undefined') {
const originalConsoleError = console.error;
const originalConsoleWarn = console.warn;
console.error = function(...args) {
const errorStr = JSON.stringify(args);
const isSilentError = errorStr.includes('401') || errorStr.includes('404') ||
args.some(arg => typeof arg === 'object' && arg?.response?.status && [401,404].includes(arg.response.status));
if (isSilentError) return;
originalConsoleError.apply(console, args);
};
console.warn = function(...args) {
const warnStr = JSON.stringify(args);
if (warnStr.includes('401') || warnStr.includes('404')) return;
originalConsoleWarn.apply(console, args);
};
}
// -----------------------------
// Request interceptor
// -----------------------------
api.interceptors.request.use(config => {
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;
}
}
return config;
}, error => Promise.reject(error));
// -----------------------------
// Response interceptor
// -----------------------------
api.interceptors.response.use(
response => response,
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 || {} });
const skipRedirect = error?.config?.skipAuthRedirect === true;
const status = error?.response?.status;
if (status === 401) {
if (!skipRedirect) {
useAuthStore.getState().setLoggedIn(false);
window.location.href = "/";
return Promise.reject(error);
} else {
return Promise.reject({ isSilent: true, response: { status: 401, data: { message: 'Unauthorized' } }, config: error.config });
}
}
if (status === 404 && skipRedirect) {
return Promise.reject({ isSilent: true, response: { status: 404, data: { message: 'Not Found' } }, config: error.config });
}
return Promise.reject(error.response?.data || error);
}
);
// -----------------------------
// 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 signOut() {
try {
await api.post("/api/v1/Auth/SignOut", null, { skipAuthRedirect: true });
} catch (error) {
console.warn("SignOut API error:", error);
}
useAuthStore.getState().setLoggedIn(false);
window.location.href = "/";
}
export async function forgotPassword(email) {
const res = await api.post("/api/v1/Auth/ForgotPassword", { email });
return res.data;
}
export async function fetchProtectedData(endpoint) {
const res = await api.get(endpoint);
return res.data;
}
// -----------------------------
// Payments API (mock data)
// -----------------------------
const mockData = {
stats: { total: 1247, success: 1189, failed: 58 },
payments: [
{ id: 'TXN-001', user: 'John Doe', amount: 299.99, status: 'success', date: '2024-01-15T10:30:00Z', currency: 'USD' },
{ id: 'TXN-002', user: 'Jane Smith', amount: 150.00, status: 'pending', date: '2024-01-15T11:45:00Z', currency: 'USD' },
{ id: 'TXN-003', user: 'Bob Johnson', amount: 75.50, status: 'failed', date: '2024-01-15T12:15:00Z', currency: 'USD' },
{ id: 'TXN-004', user: 'Alice Brown', amount: 450.00, status: 'success', date: '2024-01-15T13:20:00Z', currency: 'USD' },
{ id: 'TXN-005', user: 'Charlie Wilson', amount: 89.99, status: 'success', date: '2024-01-15T14:30:00Z', currency: 'USD' }
],
chartData: [
{ date: '2024-01-09', amount: 1200 },
{ date: '2024-01-10', amount: 1900 },
{ date: '2024-01-11', amount: 3000 },
{ date: '2024-01-12', amount: 2800 },
{ date: '2024-01-13', amount: 1890 },
{ date: '2024-01-14', amount: 2390 },
{ date: '2024-01-15', amount: 3490 }
]
};
export const paymentsAPI = {
async getStats() { return mockData.stats; },
async getChartData() { return mockData.chartData; },
};
// -----------------------------
// Roles API
// -----------------------------
const ROLES_STORAGE_KEY = 'app_roles_v1';
function readRolesFromStorage() { try { const raw = localStorage.getItem(ROLES_STORAGE_KEY); return raw ? JSON.parse(raw) : []; } catch { return []; } }
function writeRolesToStorage(roles) { localStorage.setItem(ROLES_STORAGE_KEY, JSON.stringify(roles)); }
function ensureSeedRoles() { if (readRolesFromStorage().length === 0) writeRolesToStorage([
{ id: crypto.randomUUID(), name: 'Admin', permissions: ['read','write','delete'], userType: 'internal' },
{ id: crypto.randomUUID(), name: 'Editor', permissions: ['read','write'], userType: 'internal' },
{ id: crypto.randomUUID(), name: 'Viewer', permissions: ['read'], userType: 'external' },
]); }
export const rolesAPI = {
async list(queryOrOptions='') {
const opts = typeof queryOrOptions === 'string' ? { nameQuery: queryOrOptions, currentPage:1, pageSize:100 } : { nameQuery: queryOrOptions?.nameQuery||'', currentPage: queryOrOptions?.currentPage||1, pageSize: queryOrOptions?.pageSize||100 };
if (!useAuthStore.getState()?.isLoggedIn) { ensureSeedRoles(); return readRolesFromStorage().filter(r=>r.name.toLowerCase().includes((opts.nameQuery||'').toLowerCase())); }
try {
const res = await api.get('/api/v1/Role', { params: opts, skipAuthRedirect: true });
const items = Array.isArray(res?.data?.data?.data) ? res.data.data.data : [];
writeRolesToStorage(items.map(r => ({ id: r.id||crypto.randomUUID(), ...r })));
return items;
} catch { ensureSeedRoles(); return readRolesFromStorage(); }
},
async create(role) {
const payload = { name: String(role?.name||''), permissions: Array.isArray(role?.permissions)?role.permissions:[] };
let created = payload;
if (useAuthStore.getState()?.isLoggedIn) {
try { const res = await api.post('/api/v1/Role', payload, { skipAuthRedirect:true }); created = res?.data||payload; } catch {}
}
const roles = readRolesFromStorage(); const newRole = { id: crypto.randomUUID(), ...created }; roles.push(newRole); writeRolesToStorage(roles); return newRole;
},
async remove(id) {
if (useAuthStore.getState()?.isLoggedIn) { try { await api.delete(`/api/v1/Role/${encodeURIComponent(id)}`, { skipAuthRedirect:true }); } catch {} }
const roles = readRolesFromStorage(); writeRolesToStorage(roles.filter(r=>r.id!==id)); return { success:true };
},
async update(id, role) {
const payload = { name: String(role?.name||''), permissions: Array.isArray(role?.permissions)?role.permissions:[], userType: role?.userType||undefined };
if (useAuthStore.getState()?.isLoggedIn) { try { await api.put(`/api/v1/Role/${encodeURIComponent(id)}`, payload, { skipAuthRedirect:true }); } catch {} }
const roles = readRolesFromStorage(); const idx = roles.findIndex(r=>r.id===id); if(idx!==-1){ roles[idx]={...roles[idx], ...payload}; writeRolesToStorage(roles); return roles[idx]; }
const updated={id,...payload}; roles.push(updated); writeRolesToStorage(roles); return updated;
},
};
// -----------------------------
// Users API با رول‌ها
// -----------------------------
const USERS_STORAGE_KEY = 'app_users_v1';
function readUsersFromStorage(){ try{ const raw = localStorage.getItem(USERS_STORAGE_KEY); return raw?JSON.parse(raw):[]; } catch{return [];} }
function writeUsersToStorage(users){ localStorage.setItem(USERS_STORAGE_KEY, JSON.stringify(users)); }
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||[];
},
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 res = await api.post('/api/v1/User',payload,{skipAuthRedirect:true});
return res?.data;
},
async update(id,user){
const payload = { firstName:String(user?.firstName||''), lastName:String(user?.lastName||''), email:String(user?.email||''), mobile:String(user?.mobile||''), isActive:!!user?.isActive };
const res = await api.put(`/api/v1/User/${encodeURIComponent(id)}`,payload,{skipAuthRedirect:true});
return res?.data;
},
async remove(id){
const res = await api.delete(`/api/v1/User/Delete/${encodeURIComponent(id)}/Role`,{skipAuthRedirect:true});
return res?.data;
},
async toggleActivation(id){ const res = await api.patch(`/api/v1/User/${encodeURIComponent(id)}/ToggleActivation`,null,{skipAuthRedirect:true}); return res?.data; },
async resetPassword(id){ const res = await api.patch(`/api/v1/User/${encodeURIComponent(id)}/ResetPassword`,null,{skipAuthRedirect:true}); return res?.data; },
async getRoles(userId){ try{ const res = await api.get(`/api/v1/User/${encodeURIComponent(userId)}/Role`,{skipAuthRedirect:true}); return res?.data?.data?.data||[]; }catch(err){ console.error(err); return []; } },
// مدیریت رول‌ها
async getRoles(userId){
try {
const res = await api.get(`/api/v1/User/${encodeURIComponent(userId)}/Role`, { skipAuthRedirect: true });
return res?.data?.data || []; // ← اصلاح شد
} catch(err) {
console.error(err);
return [];
}
},
async removeRole(userId,roleId){ try{ const res = await api.delete(`/api/v1/User/${encodeURIComponent(userId)}/Role/${encodeURIComponent(roleId)}`,{skipAuthRedirect:true}); return res?.data; } catch(err){ console.error(err); return null; } },
async updateRoles(userId,roleIds=[]){ try{ const res = await api.post(`/api/v1/User/${encodeURIComponent(userId)}/Role`,{roleIds},{skipAuthRedirect:true}); return res?.data; } catch(err){ console.error(err); return null; } },
};
// -----------------------------
// Permissions API
// -----------------------------
export async function listPermissions(){
if(!useAuthStore.getState()?.isLoggedIn) return [
{name:'Administrator',description:'Full access'}, {name:'UserManagement',description:'Manage users'}, {name:'AddUser',description:'Create user'}, {name:'EditUser',description:'Edit user'},
{name:'UserPasswordManagement',description:'Reset/change password'}, {name:'UserRoleManagement',description:'Assign/modify roles'},
{name:'RoleManagement',description:'Manage roles'}, {name:'AddRole',description:'Add role'}, {name:'EditRole',description:'Edit role'}, {name:'DeleteRole',description:'Delete role'}
];
try{
const res = await api.get('/api/v1/General/Permissions',{skipAuthRedirect:true});
const items = Array.isArray(res?.data?.data)?res.data.data:[];
return items.map(p=>({name:p.name||p.displayName,description:p.description||p.displayName||p.name})).filter(Boolean);
}catch{return [
{name:'Administrator',description:'Full access'}, {name:'UserManagement',description:'Manage users'}, {name:'AddUser',description:'Create user'}, {name:'EditUser',description:'Edit user'},
{name:'UserPasswordManagement',description:'Reset/change password'}, {name:'UserRoleManagement',description:'Assign/modify roles'},
{name:'RoleManagement',description:'Manage roles'}, {name:'AddRole',description:'Add role'}, {name:'EditRole',description:'Edit role'}, {name:'DeleteRole',description:'Delete role'}
];}
}
export * from './authAPI';
export { paymentsAPI } from './paymentsAPI';
export { rolesAPI } from './rolesAPI';
export { usersAPI } from './usersAPI';
export { currencyAPI } from './currencyAPI';
export { countryAPI } from './countryAPI';
export { provinceAPI } from './provinceAPI';
export { cityAPI } from './cityAPI';
export { listPermissions } from './permissionsAPI';

104
src/services/apiClient.js Normal file
View File

@@ -0,0 +1,104 @@
import axios from 'axios';
import { useAuthStore } from "../store/authStore";
// -----------------------------
// تنظیم BASE_URL
// -----------------------------
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ||
(import.meta.env.DEV ? "/" : "https://khalijpay-core.qaserver.ir");
// -----------------------------
// ایجاد instance از axios
// -----------------------------
const api = axios.create({
baseURL: API_BASE_URL,
withCredentials: true,
headers: { "Content-Type": "application/json" },
});
// -----------------------------
// Global error handler برای suppress کردن 401/404
// -----------------------------
if (typeof window !== 'undefined') {
const originalConsoleError = console.error;
const originalConsoleWarn = console.warn;
console.error = function(...args) {
const errorStr = JSON.stringify(args);
const isSilentError = errorStr.includes('401') || errorStr.includes('404') ||
args.some(arg => typeof arg === 'object' && arg?.response?.status && [401,404].includes(arg.response.status));
if (isSilentError) return;
originalConsoleError.apply(console, args);
};
console.warn = function(...args) {
const warnStr = JSON.stringify(args);
if (warnStr.includes('401') || warnStr.includes('404')) return;
originalConsoleWarn.apply(console, args);
};
}
// -----------------------------
// Request interceptor
// -----------------------------
api.interceptors.request.use(config => {
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;
}
}
return config;
}, error => Promise.reject(error));
// -----------------------------
// Response interceptor
// -----------------------------
api.interceptors.response.use(
response => response,
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 || {} });
const skipRedirect = error?.config?.skipAuthRedirect === true;
const status = error?.response?.status;
if (status === 401) {
if (!skipRedirect) {
useAuthStore.getState().setLoggedIn(false);
window.location.href = "/";
return Promise.reject(error);
} else {
return Promise.reject({ isSilent: true, response: { status: 401, data: { message: 'Unauthorized' } }, config: error.config });
}
}
if (status === 404 && skipRedirect) {
return Promise.reject({ isSilent: true, response: { status: 404, data: { message: 'Not Found' } }, config: error.config });
}
// حفظ status code در خطا برای مدیریت بهتر
const errorData = error.response?.data || error;
if (error.response) {
return Promise.reject({
...errorData,
response: {
...error.response,
status: error.response.status,
data: errorData
}
});
}
return Promise.reject(errorData);
}
);
export default api;

31
src/services/authAPI.js Normal file
View File

@@ -0,0 +1,31 @@
import api from './apiClient';
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 signOut() {
try {
await api.post("/api/v1/Auth/SignOut", null, { skipAuthRedirect: true });
} catch (error) {
console.warn("SignOut API error:", error);
}
useAuthStore.getState().setLoggedIn(false);
window.location.href = "/";
}
export async function forgotPassword(email) {
const res = await api.post("/api/v1/Auth/ForgotPassword", { email });
return res.data;
}
export async function fetchProtectedData(endpoint) {
const res = await api.get(endpoint);
return res.data;
}

50
src/services/cityAPI.js Normal file
View File

@@ -0,0 +1,50 @@
import api from './apiClient';
// -----------------------------
// City API
// -----------------------------
export const cityAPI = {
// GET /api/v1/City (with pagination)
async list(params = {}) {
const { currentPage = 1, pageSize = 10, ...otherParams } = params;
const res = await api.get('/api/v1/City', {
params: { currentPage, pageSize, ...otherParams },
skipAuthRedirect: true
});
return res?.data?.data?.data || [];
},
// POST /api/v1/City
async create(city) {
const payload = {
ProvinceId: city?.provinceId || '',
CityName: String(city?.cityName || ''),
};
const res = await api.post('/api/v1/City', payload, { skipAuthRedirect: true });
return res?.data;
},
// PUT /api/v1/City/{cityId}
async update(cityId, city) {
if (!cityId) {
throw new Error('City ID is required');
}
const payload = {
ProvinceId: city?.provinceId || '',
CityName: String(city?.cityName || '').trim(),
};
console.log('City API Update:', { cityId, payload });
try {
const res = await api.put(`/api/v1/City/${encodeURIComponent(cityId)}`, payload, { skipAuthRedirect: true });
return res?.data;
} catch (error) {
console.error('City API Update Error:', {
cityId,
payload,
error: error?.response?.data || error?.message || error
});
throw error;
}
},
};

View File

@@ -0,0 +1,81 @@
import api from './apiClient';
// -----------------------------
// Country API
// -----------------------------
export const countryAPI = {
// GET /api/v1/Country (with pagination)
async list(params = {}) {
const { currentPage = 1, pageSize = 10, ...otherParams } = params;
const res = await api.get('/api/v1/Country', {
params: { currentPage, pageSize, ...otherParams },
skipAuthRedirect: true
});
return res?.data?.data?.data || [];
},
// 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 || [];
},
// POST /api/v1/Country
async create(country) {
const payload = {
Name: String(country?.name || ''),
PhoneCode: String(country?.phoneCode || ''),
CurrencyCode: String(country?.currencyCode || ''),
TimeZoneName: String(country?.timeZoneName || country?.timeZone || ''),
};
const res = await api.post('/api/v1/Country', payload, { skipAuthRedirect: true });
return res?.data;
},
// PUT /api/v1/Country/{countryId}
async update(countryId, country) {
if (!countryId) {
throw new Error('Country ID is required');
}
// ساخت payload - سرور انتظار فیلدها با حرف بزرگ دارد
const payload = {
Name: country?.name ? String(country.name).trim() : '',
PhoneCode: country?.phoneCode ? String(country.phoneCode).trim() : '',
CurrencyCode: country?.currencyCode ? String(country.currencyCode).trim() : '',
TimeZoneName: country?.timeZoneName || country?.timeZone ? String((country.timeZoneName || country.timeZone).trim()) : '',
};
const url = `/api/v1/Country/${encodeURIComponent(countryId)}`;
console.log('Country API Update:', { url, countryId, payload });
try {
const res = await api.put(url, payload, { skipAuthRedirect: true });
return res?.data;
} catch (error) {
console.error('Country API Update Error:', {
url,
countryId,
payload,
error: error?.response?.data || error?.message || error
});
throw error;
}
},
// POST /api/v1/Country/{countryId}/Province
async createProvince(countryId, province) {
const payload = {
CountryId: countryId,
ProvinceName: String(province?.provinceName || ''),
};
const res = await api.post(`/api/v1/Country/${encodeURIComponent(countryId)}/Province`, payload, { skipAuthRedirect: true });
return res?.data;
},
// GET /api/v1/Country/{countryId}/Province
async getProvinces(countryId) {
const res = await api.get(`/api/v1/Country/${encodeURIComponent(countryId)}/Province`, { skipAuthRedirect: true });
return res?.data?.data || [];
},
};

View File

@@ -0,0 +1,57 @@
import api from './apiClient';
// -----------------------------
// Currency API
// -----------------------------
export const currencyAPI = {
// GET /api/v1/Currency
async list() {
const res = await api.get('/api/v1/Currency', { skipAuthRedirect: true });
return res?.data?.data || [];
},
// POST /api/v1/Currency
async create(currencyCode) {
const payload = { currencyCode: String(currencyCode || '') };
const res = await api.post('/api/v1/Currency', payload, { skipAuthRedirect: true });
return res?.data;
},
// PATCH /api/v1/Currency/{currencyCode}/ToggleActivation
async toggleActivation(currencyCode) {
const res = await api.patch(`/api/v1/Currency/${encodeURIComponent(currencyCode)}/ToggleActivation`, null, { skipAuthRedirect: true });
return res?.data;
},
// PATCH /api/v1/Currency/{currencyCode}/Maintenance/Enable
async enableMaintenance(currencyCode, maintenanceMessage = null) {
const payload = maintenanceMessage ? { maintenanceMessage } : {};
const res = await api.patch(`/api/v1/Currency/${encodeURIComponent(currencyCode)}/Maintenance/Enable`, payload, { skipAuthRedirect: true });
return res?.data;
},
// PATCH /api/v1/Currency/{currencyCode}/Maintenance/Disable
async disableMaintenance(currencyCode) {
const res = await api.patch(`/api/v1/Currency/${encodeURIComponent(currencyCode)}/Maintenance/Disable`, null, { skipAuthRedirect: true });
return res?.data;
},
// PUT /api/v1/Currency/{currencyCode}/Permissions
async updatePermissions(currencyCode, permissions) {
const res = await api.put(`/api/v1/Currency/${encodeURIComponent(currencyCode)}/Permissions`, permissions, { skipAuthRedirect: true });
return res?.data;
},
// PUT /api/v1/Currency/{currencyCode}/Limits
async updateLimits(currencyCode, limits) {
const res = await api.put(`/api/v1/Currency/${encodeURIComponent(currencyCode)}/Limits`, limits, { skipAuthRedirect: true });
return res?.data;
},
// PUT /api/v1/Currency/{currencyCode}/Fees
async updateFees(currencyCode, fees) {
const res = await api.put(`/api/v1/Currency/${encodeURIComponent(currencyCode)}/Fees`, fees, { skipAuthRedirect: true });
return res?.data;
},
};

View File

@@ -0,0 +1,28 @@
// -----------------------------
// Payments API (mock data)
// -----------------------------
const mockData = {
stats: { total: 1247, success: 1189, failed: 58 },
payments: [
{ id: 'TXN-001', user: 'John Doe', amount: 299.99, status: 'success', date: '2024-01-15T10:30:00Z', currency: 'USD' },
{ id: 'TXN-002', user: 'Jane Smith', amount: 150.00, status: 'pending', date: '2024-01-15T11:45:00Z', currency: 'USD' },
{ id: 'TXN-003', user: 'Bob Johnson', amount: 75.50, status: 'failed', date: '2024-01-15T12:15:00Z', currency: 'USD' },
{ id: 'TXN-004', user: 'Alice Brown', amount: 450.00, status: 'success', date: '2024-01-15T13:20:00Z', currency: 'USD' },
{ id: 'TXN-005', user: 'Charlie Wilson', amount: 89.99, status: 'success', date: '2024-01-15T14:30:00Z', currency: 'USD' }
],
chartData: [
{ date: '2024-01-09', amount: 1200 },
{ date: '2024-01-10', amount: 1900 },
{ date: '2024-01-11', amount: 3000 },
{ date: '2024-01-12', amount: 2800 },
{ date: '2024-01-13', amount: 1890 },
{ date: '2024-01-14', amount: 2390 },
{ date: '2024-01-15', amount: 3490 }
]
};
export const paymentsAPI = {
async getStats() { return mockData.stats; },
async getChartData() { return mockData.chartData; },
};

View File

@@ -0,0 +1,23 @@
import api from './apiClient';
import { useAuthStore } from "../store/authStore";
// -----------------------------
// Permissions API
// -----------------------------
export async function listPermissions(){
if(!useAuthStore.getState()?.isLoggedIn) return [
{name:'Administrator',description:'Full access'}, {name:'UserManagement',description:'Manage users'}, {name:'AddUser',description:'Create user'}, {name:'EditUser',description:'Edit user'},
{name:'UserPasswordManagement',description:'Reset/change password'}, {name:'UserRoleManagement',description:'Assign/modify roles'},
{name:'RoleManagement',description:'Manage roles'}, {name:'AddRole',description:'Add role'}, {name:'EditRole',description:'Edit role'}, {name:'DeleteRole',description:'Delete role'}
];
try{
const res = await api.get('/api/v1/General/Permissions',{skipAuthRedirect:true});
const items = Array.isArray(res?.data?.data)?res.data.data:[];
return items.map(p=>({name:p.name||p.displayName,description:p.description||p.displayName||p.name})).filter(Boolean);
}catch{return [
{name:'Administrator',description:'Full access'}, {name:'UserManagement',description:'Manage users'}, {name:'AddUser',description:'Create user'}, {name:'EditUser',description:'Edit user'},
{name:'UserPasswordManagement',description:'Reset/change password'}, {name:'UserRoleManagement',description:'Assign/modify roles'},
{name:'RoleManagement',description:'Manage roles'}, {name:'AddRole',description:'Add role'}, {name:'EditRole',description:'Edit role'}, {name:'DeleteRole',description:'Delete role'}
];}
}

View File

@@ -0,0 +1,66 @@
import api from './apiClient';
// -----------------------------
// Province API
// -----------------------------
export const provinceAPI = {
// GET /api/v1/Province (with pagination)
async list(params = {}) {
const { currentPage = 1, pageSize = 10, ...otherParams } = params;
const res = await api.get('/api/v1/Province', {
params: { currentPage, pageSize, ...otherParams },
skipAuthRedirect: true
});
return res?.data?.data?.data || [];
},
// POST /api/v1/Province
async create(province) {
const payload = {
CountryId: province?.countryId || '',
ProvinceName: String(province?.provinceName || ''),
};
const res = await api.post('/api/v1/Province', payload, { skipAuthRedirect: true });
return res?.data;
},
// PUT /api/v1/Province/{provinceId} (اگر endpoint وجود دارد)
async update(provinceId, province) {
if (!provinceId) {
throw new Error('Province ID is required');
}
const payload = {
CountryId: province?.countryId || '',
ProvinceName: String(province?.provinceName || '').trim(),
};
console.log('Province API Update:', { provinceId, payload });
try {
const res = await api.put(`/api/v1/Province/${encodeURIComponent(provinceId)}`, payload, { skipAuthRedirect: true });
return res?.data;
} catch (error) {
console.error('Province API Update Error:', {
provinceId,
payload,
error: error?.response?.data || error?.message || error
});
throw error;
}
},
// POST /api/v1/Province/{provinceId}/City
async createCity(provinceId, city) {
const payload = {
ProvinceId: provinceId,
CityName: String(city?.cityName || ''),
};
const res = await api.post(`/api/v1/Province/${encodeURIComponent(provinceId)}/City`, payload, { skipAuthRedirect: true });
return res?.data;
},
// GET /api/v1/Province/{provinceId}/City
async getCities(provinceId) {
const res = await api.get(`/api/v1/Province/${encodeURIComponent(provinceId)}/City`, { skipAuthRedirect: true });
return res?.data?.data?.data || [];
},
};

49
src/services/rolesAPI.js Normal file
View File

@@ -0,0 +1,49 @@
import api from './apiClient';
import { useAuthStore } from "../store/authStore";
// -----------------------------
// Roles API
// -----------------------------
const ROLES_STORAGE_KEY = 'app_roles_v1';
function readRolesFromStorage() { try { const raw = localStorage.getItem(ROLES_STORAGE_KEY); return raw ? JSON.parse(raw) : []; } catch { return []; } }
function writeRolesToStorage(roles) { localStorage.setItem(ROLES_STORAGE_KEY, JSON.stringify(roles)); }
function ensureSeedRoles() { if (readRolesFromStorage().length === 0) writeRolesToStorage([
{ id: crypto.randomUUID(), name: 'Admin', permissions: ['read','write','delete'], userType: 'internal' },
{ id: crypto.randomUUID(), name: 'Editor', permissions: ['read','write'], userType: 'internal' },
{ id: crypto.randomUUID(), name: 'Viewer', permissions: ['read'], userType: 'external' },
]); }
export const rolesAPI = {
async list(queryOrOptions='') {
const opts = typeof queryOrOptions === 'string' ? { nameQuery: queryOrOptions, currentPage:1, pageSize:100 } : { nameQuery: queryOrOptions?.nameQuery||'', currentPage: queryOrOptions?.currentPage||1, pageSize: queryOrOptions?.pageSize||100 };
if (!useAuthStore.getState()?.isLoggedIn) { ensureSeedRoles(); return readRolesFromStorage().filter(r=>r.name.toLowerCase().includes((opts.nameQuery||'').toLowerCase())); }
try {
const res = await api.get('/api/v1/Role', { params: opts, skipAuthRedirect: true });
const items = Array.isArray(res?.data?.data?.data) ? res.data.data.data : [];
writeRolesToStorage(items.map(r => ({ id: r.id||crypto.randomUUID(), ...r })));
return items;
} catch { ensureSeedRoles(); return readRolesFromStorage(); }
},
async create(role) {
const payload = { name: String(role?.name||''), permissions: Array.isArray(role?.permissions)?role.permissions:[] };
let created = payload;
if (useAuthStore.getState()?.isLoggedIn) {
try { const res = await api.post('/api/v1/Role', payload, { skipAuthRedirect:true }); created = res?.data||payload; } catch {}
}
const roles = readRolesFromStorage(); const newRole = { id: crypto.randomUUID(), ...created }; roles.push(newRole); writeRolesToStorage(roles); return newRole;
},
async remove(id) {
if (useAuthStore.getState()?.isLoggedIn) { try { await api.delete(`/api/v1/Role/${encodeURIComponent(id)}`, { skipAuthRedirect:true }); } catch {} }
const roles = readRolesFromStorage(); writeRolesToStorage(roles.filter(r=>r.id!==id)); return { success:true };
},
async update(id, role) {
const payload = { name: String(role?.name||''), permissions: Array.isArray(role?.permissions)?role.permissions:[], userType: role?.userType||undefined };
if (useAuthStore.getState()?.isLoggedIn) { try { await api.put(`/api/v1/Role/${encodeURIComponent(id)}`, payload, { skipAuthRedirect:true }); } catch {} }
const roles = readRolesFromStorage(); const idx = roles.findIndex(r=>r.id===id); if(idx!==-1){ roles[idx]={...roles[idx], ...payload}; writeRolesToStorage(roles); return roles[idx]; }
const updated={id,...payload}; roles.push(updated); writeRolesToStorage(roles); return updated;
},
};

43
src/services/usersAPI.js Normal file
View File

@@ -0,0 +1,43 @@
import api from './apiClient';
// -----------------------------
// Users API با رول‌ها
// -----------------------------
const USERS_STORAGE_KEY = 'app_users_v1';
function readUsersFromStorage(){ try{ const raw = localStorage.getItem(USERS_STORAGE_KEY); return raw?JSON.parse(raw):[]; } catch{return [];} }
function writeUsersToStorage(users){ localStorage.setItem(USERS_STORAGE_KEY, JSON.stringify(users)); }
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||[];
},
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 res = await api.post('/api/v1/User',payload,{skipAuthRedirect:true});
return res?.data;
},
async update(id,user){
const payload = { firstName:String(user?.firstName||''), lastName:String(user?.lastName||''), email:String(user?.email||''), mobile:String(user?.mobile||''), isActive:!!user?.isActive };
const res = await api.put(`/api/v1/User/${encodeURIComponent(id)}`,payload,{skipAuthRedirect:true});
return res?.data;
},
async remove(id){
const res = await api.delete(`/api/v1/User/Delete/${encodeURIComponent(id)}/Role`,{skipAuthRedirect:true});
return res?.data;
},
async toggleActivation(id){ const res = await api.patch(`/api/v1/User/${encodeURIComponent(id)}/ToggleActivation`,null,{skipAuthRedirect:true}); return res?.data; },
async resetPassword(id){ const res = await api.patch(`/api/v1/User/${encodeURIComponent(id)}/ResetPassword`,null,{skipAuthRedirect:true}); return res?.data; },
async getRoles(userId){
try {
const res = await api.get(`/api/v1/User/${encodeURIComponent(userId)}/Role`, { skipAuthRedirect: true });
return res?.data?.data || [];
} catch(err) {
console.error(err);
return [];
}
},
async removeRole(userId,roleId){ try{ const res = await api.delete(`/api/v1/User/${encodeURIComponent(userId)}/Role/${encodeURIComponent(roleId)}`,{skipAuthRedirect:true}); return res?.data; } catch(err){ console.error(err); return null; } },
async updateRoles(userId,roleIds=[]){ try{ const res = await api.post(`/api/v1/User/${encodeURIComponent(userId)}/Role`,{roleIds},{skipAuthRedirect:true}); return res?.data; } catch(err){ console.error(err); return null; } },
};