363 lines
11 KiB
JavaScript
363 lines
11 KiB
JavaScript
import axios from 'axios';
|
|
|
|
import { useAuthStore } from "../store/authStore";
|
|
|
|
const BASE_URL = import.meta.env.DEV ? "/" : "https://khalijpay-core.qaserver.ir";
|
|
|
|
// ساخت instance از axios
|
|
const api = axios.create({
|
|
baseURL: BASE_URL,
|
|
withCredentials: true, // ارسال و دریافت cookie/session
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
});
|
|
|
|
// -----------------------------
|
|
// Interceptor پاسخها
|
|
// -----------------------------
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
(error) => {
|
|
const skipRedirect = error?.config?.skipAuthRedirect === true;
|
|
if (error.response?.status === 401 && !skipRedirect) {
|
|
// session منقضی شده → هدایت به login
|
|
const setLoggedIn = useAuthStore.getState().setLoggedIn;
|
|
setLoggedIn(false);
|
|
window.location.href = "/";
|
|
}
|
|
return Promise.reject(error.response?.data || error);
|
|
}
|
|
);
|
|
|
|
// -----------------------------
|
|
// توابع API
|
|
// -----------------------------
|
|
|
|
// Login
|
|
export async function login(username, password) {
|
|
try {
|
|
const res = await api.post("/api/v1/Auth/SignIn", {
|
|
// include common variants to satisfy different backends
|
|
userName: username,
|
|
username: username,
|
|
email: username,
|
|
password: password,
|
|
});
|
|
return res.data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// خروج از سیستم
|
|
export async function signOut() {
|
|
try {
|
|
const res = await api.post("/api/v1/Auth/SignOut");
|
|
// پاک کردن وضعیت login در Zustand
|
|
const setLoggedIn = useAuthStore.getState().setLoggedIn;
|
|
setLoggedIn(false);
|
|
window.location.href = "/";
|
|
return res.data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// فراموشی رمز عبور
|
|
export async function forgotPassword(email) {
|
|
try {
|
|
const res = await api.post("/api/v1/Auth/ForgotPassword", { email });
|
|
return res.data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// گرفتن دادههای محافظتشده
|
|
export async function fetchProtectedData(endpoint) {
|
|
try {
|
|
const res = await api.get(endpoint);
|
|
return res.data;
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Mock data for development
|
|
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 }
|
|
]
|
|
};
|
|
|
|
// Expose mock payment API for dashboard until real endpoints are integrated
|
|
export const paymentsAPI = {
|
|
async getStats() {
|
|
return mockData.stats;
|
|
},
|
|
async getChartData() {
|
|
return mockData.chartData;
|
|
},
|
|
};
|
|
|
|
// -----------------------------
|
|
// Roles API (localStorage mock)
|
|
// -----------------------------
|
|
const ROLES_STORAGE_KEY = 'app_roles_v1';
|
|
|
|
function readRolesFromStorage() {
|
|
try {
|
|
const raw = localStorage.getItem(ROLES_STORAGE_KEY);
|
|
if (!raw) return [];
|
|
const parsed = JSON.parse(raw);
|
|
if (!Array.isArray(parsed)) return [];
|
|
return parsed;
|
|
} catch (_) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function writeRolesToStorage(roles) {
|
|
localStorage.setItem(ROLES_STORAGE_KEY, JSON.stringify(roles));
|
|
}
|
|
|
|
function ensureSeedRoles() {
|
|
const existing = readRolesFromStorage();
|
|
if (existing.length === 0) {
|
|
const seed = [
|
|
{ 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' },
|
|
];
|
|
writeRolesToStorage(seed);
|
|
}
|
|
}
|
|
|
|
export const rolesAPI = {
|
|
async list(queryOrOptions = '') {
|
|
// Support both: list('admin') and list({ nameQuery, currentPage, pageSize })
|
|
const opts = typeof queryOrOptions === 'string'
|
|
? { nameQuery: queryOrOptions, currentPage: 1, pageSize: 100 }
|
|
: {
|
|
nameQuery: queryOrOptions?.nameQuery || '',
|
|
currentPage: queryOrOptions?.currentPage ?? 1,
|
|
pageSize: queryOrOptions?.pageSize ?? 100,
|
|
};
|
|
|
|
try {
|
|
const res = await api.get('/api/v1/Role', {
|
|
params: {
|
|
nameQuery: opts.nameQuery,
|
|
currentPage: opts.currentPage,
|
|
pageSize: opts.pageSize,
|
|
},
|
|
// prevent global 401 redirect for optional fetch
|
|
skipAuthRedirect: true,
|
|
});
|
|
const raw = res?.data;
|
|
// Backend shape example:
|
|
// { data: { filterSummary: {...}, data: Role[] }, isSuccess: true, ... }
|
|
const apiItems = Array.isArray(raw?.data?.data) ? raw.data.data : undefined;
|
|
// Fallbacks for other shapes we may encounter
|
|
const items = apiItems
|
|
|| (Array.isArray(raw) ? raw : undefined)
|
|
|| (Array.isArray(raw?.items) ? raw.items : [])
|
|
|| [];
|
|
// Sync a snapshot to local storage for offline UX
|
|
writeRolesToStorage(items.map(r => ({ id: r.id || crypto.randomUUID(), ...r })));
|
|
return items;
|
|
} catch (_) {
|
|
// Fallback to local filtering
|
|
ensureSeedRoles();
|
|
const roles = readRolesFromStorage();
|
|
const trimmed = String(opts.nameQuery || '').toLowerCase();
|
|
if (!trimmed) return roles;
|
|
return roles.filter(r => r.name?.toLowerCase().includes(trimmed));
|
|
}
|
|
},
|
|
|
|
async create(role) {
|
|
const payload = {
|
|
name: String(role?.name || '').trim(),
|
|
permissions: Array.isArray(role?.permissions) ? role.permissions : [],
|
|
// userType: String(role?.userType || '').trim(),
|
|
};
|
|
let created = null;
|
|
try {
|
|
// Try real backend
|
|
const res = await api.post('/api/v1/Role', payload, { skipAuthRedirect: true });
|
|
created = res?.data || payload;
|
|
} catch (_) {
|
|
// Fallback to local-only if backend unavailable
|
|
created = payload;
|
|
}
|
|
|
|
// Sync into local storage list so UI updates immediately
|
|
const roles = readRolesFromStorage();
|
|
const newRole = { id: crypto.randomUUID(), ...created };
|
|
roles.push(newRole);
|
|
writeRolesToStorage(roles);
|
|
return newRole;
|
|
},
|
|
|
|
async remove(id) {
|
|
try {
|
|
await api.delete(`/api/v1/Role/${encodeURIComponent(id)}`, { skipAuthRedirect: true });
|
|
} catch (_) {
|
|
// ignore backend failure, proceed with local removal for UX
|
|
}
|
|
const roles = readRolesFromStorage();
|
|
const next = roles.filter(r => r.id !== id);
|
|
writeRolesToStorage(next);
|
|
return { success: true };
|
|
},
|
|
|
|
async update(id, role) {
|
|
const existingLocal = readRolesFromStorage().find(r => r.id === id) || {};
|
|
const payload = {
|
|
name: String(role?.name || '').trim(),
|
|
permissions: Array.isArray(role?.permissions) ? role.permissions : [],
|
|
...(role?.userType !== undefined
|
|
? { userType: String(role?.userType || '').trim() }
|
|
: (existingLocal.userType ? { userType: existingLocal.userType } : {})),
|
|
};
|
|
|
|
try {
|
|
await api.put(`/api/v1/Role/${encodeURIComponent(id)}`, payload, { skipAuthRedirect: true });
|
|
} catch (err) {
|
|
const status = err?.statusCode || err?.status || err?.response?.status;
|
|
if (status === 404 || status === 405 || status === 400) {
|
|
try {
|
|
await api.put('/api/v1/Role', { id, ...payload }, { skipAuthRedirect: true });
|
|
} catch (e2) {
|
|
const s2 = e2?.statusCode || e2?.status || e2?.response?.status;
|
|
if (s2 === 401 || s2 === 403) {
|
|
throw { message: 'Unauthorized to edit roles (401/403). Check permissions/login.', status: s2 };
|
|
}
|
|
}
|
|
} else if (status === 401 || status === 403) {
|
|
throw { message: 'Unauthorized to edit roles (401/403). Check permissions/login.', status };
|
|
}
|
|
}
|
|
|
|
const roles = readRolesFromStorage();
|
|
const idx = roles.findIndex(r => r.id === id);
|
|
if (idx !== -1) {
|
|
roles[idx] = { ...roles[idx], ...payload };
|
|
writeRolesToStorage(roles);
|
|
return roles[idx];
|
|
}
|
|
|
|
// If not found locally, append
|
|
const updated = { id, ...payload };
|
|
roles.push(updated);
|
|
writeRolesToStorage(roles);
|
|
return updated;
|
|
},
|
|
};
|
|
|
|
// -----------------------------
|
|
// Permissions API
|
|
// -----------------------------
|
|
export async function listPermissions() {
|
|
try {
|
|
const res = await api.get('/api/v1/General/Permission', { skipAuthRedirect: true });
|
|
const raw = res?.data;
|
|
// Expected shape: { data: Permission[] }
|
|
const items = Array.isArray(raw?.data) ? raw.data : [];
|
|
// Prefer returning structured objects with name and description
|
|
const objs = items
|
|
.map((p) => {
|
|
const name = typeof p?.name === 'string' ? p.name : (typeof p?.displayName === 'string' ? p.displayName : null);
|
|
if (!name) return null;
|
|
const description = typeof p?.description === 'string' && p.description
|
|
? p.description
|
|
: (typeof p?.displayName === 'string' ? p.displayName : name);
|
|
return { name, description };
|
|
})
|
|
.filter(Boolean);
|
|
if (objs.length > 0) return objs;
|
|
// Fallback to simple names if server returned unexpected shape
|
|
const names = items
|
|
.map((p) => (typeof p?.name === 'string' ? p.name : (typeof p?.displayName === 'string' ? p.displayName : null)))
|
|
.filter(Boolean);
|
|
return names.map((n) => ({ name: n, description: n }));
|
|
} catch (_) {
|
|
// Fallback to a minimal static list to keep UI usable
|
|
return [
|
|
{ name: 'Administrator', description: 'Full access to administrative features' },
|
|
{ name: 'UserManagement', description: 'Manage users and their profiles' },
|
|
{ name: 'AddUser', description: 'Create new user accounts' },
|
|
{ name: 'EditUser', description: 'Edit existing user accounts' },
|
|
{ name: 'UserPasswordManagement', description: 'Reset or change user passwords' },
|
|
{ name: 'UserRoleManagement', description: 'Assign or modify user roles' },
|
|
{ name: 'RoleManagement', description: 'Manage roles and permissions' },
|
|
{ name: 'AddRole', description: 'Create new roles' },
|
|
{ name: 'EditRole', description: 'Edit existing roles' },
|
|
{ name: 'DeleteRole', description: 'Remove roles from the system' },
|
|
];
|
|
}
|
|
}
|