This commit is contained in:
2026-04-19 13:22:48 +03:00
parent 1c1d87176e
commit 538612c8ea
14 changed files with 661 additions and 42 deletions
+11 -4
View File
@@ -66,10 +66,17 @@ export interface UpdateUserSubscriptionDto {
// ========================
export interface AnalyticsOverviewDto {
totalUsers: number;
activeUsers: number;
totalPredictions: number;
totalCoupons: number;
totalUsers?: number;
activeUsers?: number;
totalPredictions?: number;
totalCoupons?: number;
users?: {
total: number;
active: number;
premium: number;
};
matches?: number;
predictions?: number;
aiHealth?: Record<string, unknown>;
[key: string]: unknown;
}
+7 -2
View File
@@ -19,10 +19,11 @@ export const AdminQueryKeys = {
};
// Analytics
export const useAdminAnalytics = () => {
export const useAdminAnalytics = (enabled = true) => {
return useQuery({
queryKey: AdminQueryKeys.analytics(),
queryFn: () => adminService.getAnalyticsOverview(),
enabled,
});
};
@@ -66,10 +67,14 @@ export const useResetAllUsageLimits = () => {
};
// Users
export const useAdminUsers = (params?: AdminPaginationParams) => {
export const useAdminUsers = (
params?: AdminPaginationParams,
enabled = true,
) => {
return useQuery({
queryKey: AdminQueryKeys.users(params),
queryFn: () => adminService.getAllUsers(params),
enabled,
});
};
+50
View File
@@ -0,0 +1,50 @@
import { apiRequest } from "@/lib/api/api-service";
import { ApiResponse } from "@/types/api-response";
import {
LoginDto,
AuthResponse,
RegisterDto,
RefreshTokenDto,
} from "./types";
const login = (data: LoginDto) => {
return apiRequest<ApiResponse<AuthResponse>>({
url: "/auth/login",
client: "auth",
method: "post",
data,
});
};
const register = (data: RegisterDto) => {
return apiRequest<ApiResponse<AuthResponse>>({
url: "/auth/register",
client: "auth",
method: "post",
data,
});
};
const refreshToken = (data: RefreshTokenDto) => {
return apiRequest<ApiResponse<AuthResponse>>({
url: "/auth/refresh",
client: "auth",
method: "post",
data,
});
};
const logout = () => {
return apiRequest<ApiResponse<null>>({
url: "/auth/logout",
client: "auth",
method: "post",
});
};
export const authService = {
login,
register,
refreshToken,
logout,
};
+30
View File
@@ -0,0 +1,30 @@
export interface LoginDto {
email: string;
password: string;
}
export interface RegisterDto {
email: string;
password: string;
firstName: string;
lastName: string;
username?: string;
}
export interface RefreshTokenDto {
refreshToken: string;
}
export interface AuthResponse {
accessToken: string;
refreshToken: string;
expiresIn: number;
user: {
id: string;
email: string;
firstName: string;
lastName: string;
isActive?: boolean;
roles: string[];
};
}
+64
View File
@@ -0,0 +1,64 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { ApiResponse } from "@/types/api-response";
import { authService } from "./service";
import { LoginDto, RegisterDto, RefreshTokenDto, AuthResponse } from "./types";
export const AuthQueryKeys = {
all: ["auth"] as const,
session: () => [...AuthQueryKeys.all, "session"] as const,
};
export function useLogin() {
const queryClient = useQueryClient();
const { data, ...rest } = useMutation<
ApiResponse<AuthResponse>,
Error,
LoginDto
>({
mutationFn: (credentials: LoginDto) => authService.login(credentials),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: AuthQueryKeys.session() });
},
});
return { data: data?.data, ...rest };
}
export function useRegister() {
const { data, ...rest } = useMutation<
ApiResponse<AuthResponse>,
Error,
RegisterDto
>({
mutationFn: (userData: RegisterDto) => authService.register(userData),
});
return { data: data?.data, ...rest };
}
export function useRefreshToken() {
const { data, ...rest } = useMutation<
ApiResponse<AuthResponse>,
Error,
RefreshTokenDto
>({
mutationFn: (tokenData: RefreshTokenDto) =>
authService.refreshToken(tokenData),
});
return { data: data?.data, ...rest };
}
export function useLogout() {
const queryClient = useQueryClient();
const { data, ...rest } = useMutation<ApiResponse<null>, Error, void>({
mutationFn: () => authService.logout(),
onSuccess: () => {
queryClient.clear();
},
});
return { data: data?.data, ...rest };
}
+16 -3
View File
@@ -99,13 +99,26 @@ export function createApiClient(baseURL: string): AxiosInstance {
) {
const errorMessage = extractApiErrorMessage(data, "Bir hata oluştu");
const errorStatus =
"status" in data && typeof data.status === "number"
? data.status
: response.status;
// Handle 429 in success: false body
if (data.status === 429) {
if (errorStatus === 429) {
show429Toast(errorMessage);
}
// Use API-level status (data.status) if available, otherwise fall back to HTTP status
const errorStatus = data.status || response.status;
if (errorStatus === 401 && typeof window !== "undefined") {
const isAuthPath =
window.location.pathname.includes("/api/auth") ||
window.location.pathname === "/";
if (!isAuthPath) {
void signOut({ redirect: true, callbackUrl: "/" });
}
}
const apiError = new ApiError(errorMessage, errorStatus, data);
return Promise.reject(apiError);
}
+43
View File
@@ -0,0 +1,43 @@
const ADMIN_ROLES = new Set(["superadmin"]);
export function normalizeRole(role: string | null | undefined): string {
return role?.trim().toLowerCase() ?? "";
}
export function normalizeRoles(
roles: Array<string | null | undefined> | null | undefined,
): string[] {
if (!roles?.length) {
return [];
}
return Array.from(
new Set(roles.map((role) => normalizeRole(role)).filter(Boolean)),
);
}
export function hasRole(
roles: Array<string | null | undefined> | null | undefined,
expectedRole: string,
): boolean {
return normalizeRoles(roles).includes(normalizeRole(expectedRole));
}
export function isAdminRole(
roles: Array<string | null | undefined> | null | undefined,
): boolean {
return normalizeRoles(roles).some((role) => ADMIN_ROLES.has(role));
}
export function formatRoleLabel(role: string | null | undefined): string {
const normalizedRole = normalizeRole(role);
switch (normalizedRole) {
case "superadmin":
return "Superadmin";
case "user":
return "User";
default:
return role?.trim() || "User";
}
}