mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
420 lines
16 KiB
React
420 lines
16 KiB
React
import { createContext, useContext, useState, useCallback } from "react";
|
|
import api from "@/utils/api.util";
|
|
import { toast } from "sonner";
|
|
|
|
const UserContext = createContext(null);
|
|
|
|
export const useUsers = () => {
|
|
const ctx = useContext(UserContext);
|
|
if (!ctx) throw new Error("useUsers must be used inside UserProvider");
|
|
return ctx;
|
|
};
|
|
|
|
const PAGINATION_INIT = {
|
|
page: 1,
|
|
limit: 10,
|
|
totalRecords: 0,
|
|
totalPages: 0,
|
|
hasPrevPage: false,
|
|
hasNextPage: false,
|
|
};
|
|
|
|
const BASE = "/admin";
|
|
|
|
export const UserProvider = ({ children }) => {
|
|
const [users, setUsers] = useState([]);
|
|
const [user, setUser] = useState(null);
|
|
const [sessions, setSessions] = useState([]);
|
|
const [achievements, setAchievements] = useState([]);
|
|
const [achievementsLoading, setAchievementsLoading] = useState(false);
|
|
const [pagination, setPagination] = useState(PAGINATION_INIT);
|
|
const [attributes, setAttributes] = useState([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState(null);
|
|
const [activity, setActivity] = useState([]);
|
|
const [activityPagination, setActivityPagination] = useState(PAGINATION_INIT);
|
|
const [activityLoading, setActivityLoading] = useState(false);
|
|
const [bans, setBans] = useState([]);
|
|
const [bansLoading, setBansLoading] = useState(false);
|
|
|
|
const request = useCallback(async (fn) => {
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
return await fn();
|
|
} catch (err) {
|
|
const message = err?.response?.data?.message || err.message || "Something went wrong.";
|
|
setError(message);
|
|
toast(message);
|
|
return null;
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// ─── GET /api/admin/users ──────────────────────────────────────────────────
|
|
const fetchUsers = useCallback(
|
|
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
|
|
request(async () => {
|
|
const { data } = await api.get(`${BASE}/users`, {
|
|
params: {
|
|
page, limit,
|
|
filters: filters.length ? JSON.stringify(filters) : undefined,
|
|
sort: sort.length ? JSON.stringify(sort) : undefined,
|
|
},
|
|
});
|
|
const final_data = data?.data;
|
|
setUsers(final_data?.data ?? []);
|
|
setPagination(final_data?.pagination ?? PAGINATION_INIT);
|
|
setAttributes(final_data?.attributes ?? []);
|
|
return data?.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── GET /api/admin/users/archived ────────────────────────────────────────
|
|
const fetchArchivedUsers = useCallback(
|
|
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
|
|
request(async () => {
|
|
const { data } = await api.get(`${BASE}/users/archived`, {
|
|
params: {
|
|
page, limit,
|
|
filters: filters.length ? JSON.stringify(filters) : undefined,
|
|
sort: sort.length ? JSON.stringify(sort) : undefined,
|
|
},
|
|
});
|
|
const final_data = data?.data;
|
|
setUsers(final_data?.data ?? []);
|
|
setPagination(final_data?.pagination ?? PAGINATION_INIT);
|
|
setAttributes(final_data?.attributes ?? []);
|
|
return data?.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── GET /api/admin/users/:id ─────────────────────────────────────────────
|
|
const fetchUser = useCallback(
|
|
(userId) =>
|
|
request(async () => {
|
|
const res = await api.get(`${BASE}/users/${userId}`);
|
|
setUser(res.data?.data ?? null);
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── POST /api/admin/users/staff ──────────────────────────────────────────
|
|
const addStaffUser = useCallback(
|
|
(payload) =>
|
|
request(async () => {
|
|
const res = await api.post(`${BASE}/users/staff`, payload);
|
|
toast("Staff user added successfully.");
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── PUT /api/admin/users/:id ─────────────────────────────────────────────
|
|
const updateUser = useCallback(
|
|
(userId, payload) =>
|
|
request(async () => {
|
|
const res = await api.put(`${BASE}/users/${userId}`, payload);
|
|
setUsers((prev) =>
|
|
prev.map((u) => (u.user_id === userId ? { ...u, ...res.data?.data } : u))
|
|
);
|
|
toast("User updated successfully.");
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── DELETE /api/admin/users/:id ──────────────────────────────────────────
|
|
const deactivateUser = useCallback(
|
|
(userId) =>
|
|
request(async () => {
|
|
const res = await api.delete(`${BASE}/users/${userId}`);
|
|
setUsers((prev) =>
|
|
prev.map((u) => (u.user_id === userId ? { ...u, is_active: false } : u))
|
|
);
|
|
toast("User deactivated.");
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── DELETE /api/admin/users/bulk ─────────────────────────────────────────
|
|
const deactivateUsers = useCallback(
|
|
({ ids }) =>
|
|
request(async () => {
|
|
const res = await api.delete(`${BASE}/users/bulk`, { data: { ids } });
|
|
const { deactivated_ids } = res.data?.data ?? {};
|
|
if (deactivated_ids?.length) {
|
|
setUsers((prev) =>
|
|
prev.map((u) =>
|
|
deactivated_ids.includes(u.user_id) ? { ...u, is_active: false } : u
|
|
)
|
|
);
|
|
toast(`${deactivated_ids.length} user(s) deactivated.`);
|
|
}
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── POST /api/admin/users/:id/restore ────────────────────────────────────
|
|
const restoreUser = useCallback(
|
|
(userId) =>
|
|
request(async () => {
|
|
const res = await api.post(`${BASE}/users/${userId}/restore`);
|
|
setUsers((prev) =>
|
|
prev.map((u) => (u.user_id === userId ? { ...u, is_active: true } : u))
|
|
);
|
|
toast("User restored.");
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── POST /api/admin/users/bulk/restore ───────────────────────────────────
|
|
const restoreUsers = useCallback(
|
|
({ ids }) =>
|
|
request(async () => {
|
|
const res = await api.post(`${BASE}/users/bulk/restore`, { ids });
|
|
const { restored_ids } = res.data?.data ?? {};
|
|
if (restored_ids?.length) {
|
|
setUsers((prev) =>
|
|
prev.map((u) =>
|
|
restored_ids.includes(u.user_id) ? { ...u, is_active: true } : u
|
|
)
|
|
);
|
|
toast(`${restored_ids.length} user(s) restored.`);
|
|
}
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── DELETE /api/admin/users/:id/permanent ────────────────────────────────
|
|
const permanentlyDeleteUser = useCallback(
|
|
(userId) =>
|
|
request(async () => {
|
|
const res = await api.delete(`${BASE}/users/${userId}/permanent`);
|
|
setUsers((prev) => prev.filter((u) => u.user_id !== userId));
|
|
toast("User permanently deleted.");
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── DELETE /api/admin/users/bulk/permanent ───────────────────────────────
|
|
const permanentlyDeleteUsers = useCallback(
|
|
({ ids }) =>
|
|
request(async () => {
|
|
const res = await api.delete(`${BASE}/users/bulk/permanent`, { data: { ids } });
|
|
const { deleted_ids } = res.data?.data ?? {};
|
|
if (deleted_ids?.length) {
|
|
setUsers((prev) => prev.filter((u) => !deleted_ids.includes(u.user_id)));
|
|
toast(`${deleted_ids.length} user(s) permanently deleted.`);
|
|
}
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── GET /api/admin/users/:id/sessions ────────────────────────────────────
|
|
const fetchUserSessions = useCallback(
|
|
(userId) =>
|
|
request(async () => {
|
|
const res = await api.get(`${BASE}/users/${userId}/sessions`);
|
|
setSessions(res.data?.data ?? []);
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── DELETE /api/admin/users/:id/sessions/:sid ────────────────────────────
|
|
const terminateSession = useCallback(
|
|
(userId, sessionId) =>
|
|
request(async () => {
|
|
const res = await api.delete(`${BASE}/users/${userId}/sessions/${sessionId}`);
|
|
setSessions((prev) =>
|
|
prev.map((s) => (s.session_id === sessionId ? { ...s, is_active: false } : s))
|
|
);
|
|
toast("Session terminated.");
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── GET /api/admin/activity ──────────────────────────────────────────────
|
|
const fetchActivity = useCallback(
|
|
({ page = 1, limit = 20, action = undefined, from = undefined, to = undefined } = {}) =>
|
|
request(async () => {
|
|
const res = await api.get(`${BASE}/activity`, { params: { page, limit, action, from, to } });
|
|
const d = res.data?.data;
|
|
setActivity(d?.activities ?? []);
|
|
setActivityPagination({
|
|
page: d?.page ?? 1,
|
|
limit,
|
|
totalRecords: d?.total ?? 0,
|
|
totalPages: d?.totalPages ?? 0,
|
|
hasPrevPage: (d?.page ?? 1) > 1,
|
|
hasNextPage: (d?.page ?? 1) < (d?.totalPages ?? 0),
|
|
});
|
|
return d;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── GET /api/admin/users/:id/activity ────────────────────────────────────
|
|
const fetchUserActivity = useCallback(
|
|
async (userId, { page = 1, limit = 20, action = undefined, from = undefined, to = undefined } = {}) => {
|
|
setActivityLoading(true);
|
|
try {
|
|
const res = await api.get(`${BASE}/users/${userId}/activity`, { params: { page, limit, action, from, to } });
|
|
const d = res.data?.data;
|
|
setActivity(d?.activities ?? []);
|
|
setActivityPagination({
|
|
page: d?.page ?? 1,
|
|
limit,
|
|
totalRecords: d?.total ?? 0,
|
|
totalPages: d?.totalPages ?? 0,
|
|
hasPrevPage: (d?.page ?? 1) > 1,
|
|
hasNextPage: (d?.page ?? 1) < (d?.totalPages ?? 0),
|
|
});
|
|
return d;
|
|
} catch (err) {
|
|
toast(err?.response?.data?.message ?? "Could not load activity.");
|
|
return null;
|
|
} finally {
|
|
setActivityLoading(false);
|
|
}
|
|
},
|
|
[]
|
|
);
|
|
|
|
// ─── GET /api/admin/users/field-values ────────────────────────────────────
|
|
const fetchUserFieldValues = useCallback(
|
|
(field) =>
|
|
request(async () => {
|
|
const res = await api.get(`${BASE}/users/field-values`, { params: { field } });
|
|
return res.data?.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── GET /api/admin/users/:id/achievements ────────────────────────────────
|
|
const fetchUserAchievements = useCallback(async (userId) => {
|
|
setAchievementsLoading(true);
|
|
try {
|
|
const res = await api.get(`${BASE}/users/${userId}/achievements`);
|
|
setAchievements(res.data?.data ?? []);
|
|
return res.data?.data;
|
|
} catch (err) {
|
|
toast(err?.response?.data?.message ?? "Could not load achievements.");
|
|
return [];
|
|
} finally {
|
|
setAchievementsLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// ─── POST /api/admin/users/:id/ban ────────────────────────────────────────
|
|
const banUser = useCallback(
|
|
(userId, payload) =>
|
|
request(async () => {
|
|
const res = await api.post(`${BASE}/users/${userId}/ban`, payload);
|
|
setUsers((prev) =>
|
|
prev.map((u) => (u.user_id === userId ? { ...u, is_banned: true } : u))
|
|
);
|
|
if (user?.user_id === userId) setUser((prev) => prev ? { ...prev, is_banned: true } : prev);
|
|
toast("User banned successfully.");
|
|
return res.data;
|
|
}),
|
|
[request, user]
|
|
);
|
|
|
|
// ─── POST /api/admin/users/:id/unban ──────────────────────────────────────
|
|
const unbanUser = useCallback(
|
|
(userId, payload) =>
|
|
request(async () => {
|
|
const res = await api.post(`${BASE}/users/${userId}/unban`, payload);
|
|
setUsers((prev) =>
|
|
prev.map((u) => (u.user_id === userId ? { ...u, is_banned: false } : u))
|
|
);
|
|
if (user?.user_id === userId) setUser((prev) => prev ? { ...prev, is_banned: false } : prev);
|
|
toast("User unbanned successfully.");
|
|
return res.data;
|
|
}),
|
|
[request, user]
|
|
);
|
|
|
|
// ─── POST /api/admin/users/bulk/ban ───────────────────────────────────────
|
|
const bulkBanUsers = useCallback(
|
|
({ ids, ...payload }) =>
|
|
request(async () => {
|
|
const res = await api.post(`${BASE}/users/bulk/ban`, { ids, ...payload });
|
|
const { banned_ids } = res.data?.data ?? {};
|
|
if (banned_ids?.length) {
|
|
setUsers((prev) =>
|
|
prev.map((u) => (banned_ids.includes(u.user_id) ? { ...u, is_banned: true } : u))
|
|
);
|
|
toast(`${banned_ids.length} user(s) banned.`);
|
|
}
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── POST /api/admin/users/bulk/unban ─────────────────────────────────────
|
|
const bulkUnbanUsers = useCallback(
|
|
({ ids, lift_reason } = {}) =>
|
|
request(async () => {
|
|
const res = await api.post(`${BASE}/users/bulk/unban`, { ids, lift_reason });
|
|
const { unbanned_ids } = res.data?.data ?? {};
|
|
if (unbanned_ids?.length) {
|
|
setUsers((prev) =>
|
|
prev.map((u) => (unbanned_ids.includes(u.user_id) ? { ...u, is_banned: false } : u))
|
|
);
|
|
toast(`${unbanned_ids.length} user(s) unbanned.`);
|
|
}
|
|
return res.data;
|
|
}),
|
|
[request]
|
|
);
|
|
|
|
// ─── GET /api/admin/users/:id/bans ────────────────────────────────────────
|
|
const fetchUserBans = useCallback(async (userId) => {
|
|
setBansLoading(true);
|
|
try {
|
|
const res = await api.get(`${BASE}/users/${userId}/bans`);
|
|
setBans(res.data?.data ?? []);
|
|
return res.data?.data;
|
|
} catch (err) {
|
|
toast(err?.response?.data?.message ?? "Could not load ban history.");
|
|
return [];
|
|
} finally {
|
|
setBansLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<UserContext.Provider value={{
|
|
users, user, sessions, achievements, achievementsLoading, pagination, attributes, loading, error,
|
|
activity, activityPagination, activityLoading,
|
|
bans, bansLoading,
|
|
setPagination,
|
|
fetchUsers, fetchArchivedUsers, fetchUser,
|
|
addStaffUser, updateUser,
|
|
deactivateUser, deactivateUsers,
|
|
restoreUser, restoreUsers,
|
|
permanentlyDeleteUser, permanentlyDeleteUsers,
|
|
fetchUserSessions, terminateSession,
|
|
fetchUserFieldValues,
|
|
fetchUserAchievements,
|
|
fetchActivity, fetchUserActivity,
|
|
banUser, unbanUser, bulkBanUsers, bulkUnbanUsers, fetchUserBans,
|
|
}}>
|
|
{children}
|
|
</UserContext.Provider>
|
|
);
|
|
}; |