feat: Complete admin panel implementation

- Add user management system with all CRUD operations
- Add promotion statistics dashboard with export
- Simplify Trading & Market settings UI
- Fix promotion schema (dates now optional)
- Add missing API endpoints and PATCH support
- Add comprehensive documentation
- Fix critical bugs (deletePromotion, duplicate endpoints)

All features tested and production-ready.
This commit is contained in:
2026-01-10 21:57:55 +00:00
parent b90cdd59df
commit 63c578b0ae
52 changed files with 21810 additions and 61 deletions

View File

@@ -1,16 +1,16 @@
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'
import { useToast } from 'vue-toastification'
import axios from "axios";
import { useAuthStore } from "@/stores/auth";
import { useToast } from "vue-toastification";
// Create axios instance
const axiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_URL || '/api',
baseURL: import.meta.env.VITE_API_URL || "/api",
timeout: 15000,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
"Content-Type": "application/json",
},
})
});
// Request interceptor
axiosInstance.interceptors.request.use(
@@ -20,83 +20,97 @@ axiosInstance.interceptors.request.use(
// if (token) {
// config.headers.Authorization = `Bearer ${token}`
// }
return config
return config;
},
(error) => {
return Promise.reject(error)
return Promise.reject(error);
}
)
);
// Response interceptor
axiosInstance.interceptors.response.use(
(response) => {
return response
return response;
},
async (error) => {
const toast = useToast()
const authStore = useAuthStore()
const toast = useToast();
const authStore = useAuthStore();
if (error.response) {
const { status, data } = error.response
const { status, data } = error.response;
switch (status) {
case 401:
// Unauthorized - token expired or invalid
if (data.code === 'TokenExpired') {
if (data.code === "TokenExpired") {
// Try to refresh token
try {
const refreshed = await authStore.refreshToken()
const refreshed = await authStore.refreshToken();
if (refreshed) {
// Retry the original request
return axiosInstance.request(error.config)
return axiosInstance.request(error.config);
}
} catch (refreshError) {
// Refresh failed, logout user
authStore.clearUser()
window.location.href = '/'
authStore.clearUser();
window.location.href = "/";
}
} else {
authStore.clearUser()
toast.error('Please login to continue')
authStore.clearUser();
toast.error("Please login to continue");
}
break
break;
case 403:
// Forbidden
toast.error(data.message || 'Access denied')
break
toast.error(data.message || "Access denied");
break;
case 404:
// Not found
toast.error(data.message || 'Resource not found')
break
toast.error(data.message || "Resource not found");
break;
case 429:
// Too many requests
toast.error('Too many requests. Please slow down.')
break
toast.error("Too many requests. Please slow down.");
break;
case 500:
// Server error
toast.error('Server error. Please try again later.')
break
toast.error("Server error. Please try again later.");
break;
case 503:
// Service unavailable - maintenance mode
if (data.maintenance) {
// Only redirect if user is not admin
if (!authStore.isAdmin) {
window.location.href = "/maintenance";
}
// Don't show toast for maintenance, page will handle it
return Promise.reject(error);
} else {
toast.error("Service temporarily unavailable");
}
break;
default:
// Other errors
if (data.message) {
toast.error(data.message)
toast.error(data.message);
}
}
} else if (error.request) {
// Request made but no response
toast.error('Network error. Please check your connection.')
toast.error("Network error. Please check your connection.");
} else {
// Something else happened
toast.error('An unexpected error occurred')
toast.error("An unexpected error occurred");
}
return Promise.reject(error)
return Promise.reject(error);
}
)
);
export default axiosInstance
export default axiosInstance;