feat: tune visual of web app admin panel, tune limit messages and etc
This commit is contained in:
@@ -23,6 +23,12 @@ export function createUsersStore({ api, onToast, at }) {
|
||||
userBanConfirmOpen: false,
|
||||
userMessageConfirmOpen: false,
|
||||
userDetailTab: "profile",
|
||||
premiumUnlimitedDraft: false,
|
||||
premiumBonusGbDraft: "",
|
||||
regularUnlimitedDraft: false,
|
||||
regularBonusGbDraft: "",
|
||||
grantTrafficGbDraft: "",
|
||||
grantTrafficKindDraft: "regular",
|
||||
});
|
||||
|
||||
let _activeRef = "stats"; // fallback if active isn't tracked
|
||||
@@ -86,10 +92,19 @@ export function createUsersStore({ api, onToast, at }) {
|
||||
try {
|
||||
const res = await api(`/admin/users/${userId}`);
|
||||
if (res?.ok) {
|
||||
const sub = res.active_subscription || null;
|
||||
const bonusBytes = Number(sub?.premium_bonus_bytes || 0);
|
||||
const regularBonusBytes = Number(sub?.regular_bonus_bytes || 0);
|
||||
state.update(s => ({
|
||||
...s,
|
||||
openedUserDetail: res,
|
||||
openedUser: res.user ? { ...res.user, ...s.openedUser, ...res.user } : s.openedUser
|
||||
openedUser: res.user ? { ...res.user, ...s.openedUser, ...res.user } : s.openedUser,
|
||||
premiumUnlimitedDraft: Boolean(sub?.premium_unlimited_override),
|
||||
premiumBonusGbDraft: bonusBytes > 0 ? +(bonusBytes / (1024 ** 3)).toFixed(2) : "",
|
||||
regularUnlimitedDraft: Boolean(sub?.regular_unlimited_override),
|
||||
regularBonusGbDraft: regularBonusBytes > 0 ? +(regularBonusBytes / (1024 ** 3)).toFixed(2) : "",
|
||||
grantTrafficGbDraft: "",
|
||||
grantTrafficKindDraft: "regular",
|
||||
}));
|
||||
} else {
|
||||
onToast(res?.error || "load_failed");
|
||||
@@ -249,6 +264,103 @@ export function createUsersStore({ api, onToast, at }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function savePremiumTrafficOverride() {
|
||||
let s;
|
||||
state.update(st => { s = st; return st; });
|
||||
if (!s.openedUser) return;
|
||||
state.update(st => ({ ...st, userActionBusy: true }));
|
||||
try {
|
||||
const bonusGbRaw = s.premiumBonusGbDraft;
|
||||
const bonusGb = bonusGbRaw === "" || bonusGbRaw === null || bonusGbRaw === undefined
|
||||
? 0
|
||||
: Number(bonusGbRaw);
|
||||
if (Number.isNaN(bonusGb) || bonusGb < 0) {
|
||||
onToast(at("premium_override_invalid_bonus", {}, "Некорректное значение GB"));
|
||||
return;
|
||||
}
|
||||
const res = await api(`/admin/users/${s.openedUser.user_id}/premium-override`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
unlimited: Boolean(s.premiumUnlimitedDraft),
|
||||
bonus_gb: bonusGb,
|
||||
}),
|
||||
});
|
||||
if (res?.ok) {
|
||||
onToast(at("premium_override_saved", {}, "Премиум-оверрайд сохранён"));
|
||||
await openUser(s.openedUser, { skipPush: true });
|
||||
} else {
|
||||
onToast(res?.error || at("error", {}, "Ошибка"));
|
||||
}
|
||||
} finally {
|
||||
state.update(st => ({ ...st, userActionBusy: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRegularTrafficOverride() {
|
||||
let s;
|
||||
state.update(st => { s = st; return st; });
|
||||
if (!s.openedUser) return;
|
||||
state.update(st => ({ ...st, userActionBusy: true }));
|
||||
try {
|
||||
const regGbRaw = s.regularBonusGbDraft;
|
||||
const regularGb = regGbRaw === "" || regGbRaw === null || regGbRaw === undefined
|
||||
? 0
|
||||
: Number(regGbRaw);
|
||||
if (Number.isNaN(regularGb) || regularGb < 0) {
|
||||
onToast(at("regular_override_invalid_bonus", {}, "Некорректное значение GB для основного трафика"));
|
||||
return;
|
||||
}
|
||||
const res = await api(`/admin/users/${s.openedUser.user_id}/regular-traffic-override`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
unlimited: Boolean(s.regularUnlimitedDraft),
|
||||
regular_bonus_gb: regularGb,
|
||||
}),
|
||||
});
|
||||
if (res?.ok) {
|
||||
onToast(at("regular_override_saved", {}, "Оверрайд основного трафика сохранён"));
|
||||
await openUser(s.openedUser, { skipPush: true });
|
||||
} else {
|
||||
onToast(res?.error || at("error", {}, "Ошибка"));
|
||||
}
|
||||
} finally {
|
||||
state.update(st => ({ ...st, userActionBusy: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function grantTraffic() {
|
||||
let s;
|
||||
state.update(st => { s = st; return st; });
|
||||
if (!s.openedUser) return;
|
||||
const gbRaw = s.grantTrafficGbDraft;
|
||||
const gb = Number(gbRaw);
|
||||
if (!gbRaw || Number.isNaN(gb) || gb <= 0) {
|
||||
onToast(at("traffic_grant_invalid_gb", {}, "Введите положительное число GB"));
|
||||
return;
|
||||
}
|
||||
const kind = s.grantTrafficKindDraft === "premium" ? "premium" : "regular";
|
||||
state.update(st => ({ ...st, userActionBusy: true }));
|
||||
try {
|
||||
const res = await api(`/admin/users/${s.openedUser.user_id}/traffic-grant`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ kind, gb }),
|
||||
});
|
||||
if (res?.ok) {
|
||||
onToast(
|
||||
kind === "premium"
|
||||
? at("traffic_grant_premium_done", { gb }, `+${gb} ГБ премиум-трафика`)
|
||||
: at("traffic_grant_regular_done", { gb }, `+${gb} ГБ трафика`)
|
||||
);
|
||||
state.update(st => ({ ...st, grantTrafficGbDraft: "" }));
|
||||
await openUser(s.openedUser, { skipPush: true });
|
||||
} else {
|
||||
onToast(res?.error || at("error", {}, "Ошибка"));
|
||||
}
|
||||
} finally {
|
||||
state.update(st => ({ ...st, userActionBusy: false }));
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUser() {
|
||||
let s;
|
||||
state.update(st => { s = st; return st; });
|
||||
@@ -291,5 +403,8 @@ export function createUsersStore({ api, onToast, at }) {
|
||||
extendUser,
|
||||
resetTrialUser,
|
||||
deleteUser,
|
||||
savePremiumTrafficOverride,
|
||||
saveRegularTrafficOverride,
|
||||
grantTraffic,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user