Большой консолидирующий коммит после поднятия studio.rublox.pro (28 мая 2026). Содержит изменения которые делались в процессе подготовки прод-окружения: Фиксы импортов после выноса из minecraftia: - Массовая замена путей ../../components → ../components (40+ файлов в src/community/, src/admin-preview/) - Замена ../KubikonEditor/ → ../editor/, ../KubikonStudio/ → ../community/, ../AdminPreview/ → ../admin-preview/ - API.js скопирован из минки целиком (было 8 экспортов, стало 312) - Добавлены PLAYER_URL, MyButton_1, недостающие компоненты - Заменены require() на статические ES-imports в BabylonScene, PrimitiveManager, GameRuntime (Vite не поддерживает CJS require) Структура ассетов: - public/kubikon-templates/ → public/assets/kubikon-templates/ - public/kubikon-learn/ → public/assets/kubikon-learn/ - (код искал в /assets/, файлы лежали без /assets/) Навигация роутов внутри студии: - /kubikon-studio/docs → /docs (90+ навигационных вызовов sed-replaced) - /kubikon-editor/X → /edit/X, /kubikon/play/X → /play/X, /kubikon/gd/X → /gd/X UI: - Новый компонент StudioHeader (61px, как в минке) + копия favicon - WithHeader wrapper в App.jsx для всех страниц кроме fullscreen-редактора/плеера - SSO ticket-flow в AuthContext (auto-redeem #ticket= при загрузке) - Тёмная тема карточек игр в ВИКИ (фон #1c2231 вместо #fff, картинка впритык) Документация: - docs/ONBOARDING.md — путь нового контрибьютора от нуля до PR - docs/TUTORIAL_ADD_SCRIPT_API.md — как добавить game.* API - API_USAGE.md — список эндпоинтов backend - README в подпапках engine/, engine/terrain/, engine/voxel/, engine/robloxterrain/, engine/types/ .gitignore: - public/wiki/ исключён (73МБ PNG, будут на CDN отдельной задачей) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
85 lines
3.3 KiB
JavaScript
85 lines
3.3 KiB
JavaScript
/**
|
|
* SanctionsContext — глобальный контекст активных санкций юзера.
|
|
*
|
|
* Дёргает /api-storys/api/v1/storys/me/sanctions ОДИН раз при монтировании
|
|
* и при изменении localStorage Authorization (логин/логаут). Возвращает:
|
|
*
|
|
* sanctions — массив { type, banned_until, reason, ... }
|
|
* isMuted — true если есть активный user_timed_ban или chat_ban
|
|
* или comment_ban (то что блокирует ввод текста)
|
|
* isCantPublish — true если есть user_timed_ban или publish_ban
|
|
* muteInfo — { type, banned_until, reason } первой найденной мут-санкции
|
|
* reload() — перезагрузить (например после успешной подачи жалобы)
|
|
*
|
|
* Подключи провайдер один раз в корне приложения (Menu.jsx). Дальше
|
|
* через useSanctions() в любом компоненте, без отдельных запросов.
|
|
*/
|
|
import React, { createContext, useContext, useEffect, useState, useCallback } from 'react';
|
|
import SanctionsService from '../api/SanctionsService';
|
|
|
|
const SanctionsContext = createContext({
|
|
sanctions: [], isMuted: false, isCantPublish: false,
|
|
muteInfo: null, loading: true, reload: () => {},
|
|
});
|
|
|
|
export function SanctionsProvider({ children }) {
|
|
const [sanctions, setSanctions] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const reload = useCallback(async () => {
|
|
// Без JWT не дёргаем (вернёт пустоту, но всё равно лишний запрос).
|
|
if (!localStorage.getItem('Authorization')) {
|
|
setSanctions([]);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
try {
|
|
const r = await SanctionsService.getMySanctions();
|
|
setSanctions(Array.isArray(r?.sanctions) ? r.sanctions : []);
|
|
} catch (_) {
|
|
setSanctions([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
reload();
|
|
}, [reload]);
|
|
|
|
// Перезагрузка при изменении токена (login/logout)
|
|
useEffect(() => {
|
|
const onStorage = (e) => {
|
|
if (e.key === 'Authorization') reload();
|
|
};
|
|
window.addEventListener('storage', onStorage);
|
|
return () => window.removeEventListener('storage', onStorage);
|
|
}, [reload]);
|
|
|
|
// Мут на текст: user_timed_ban или chat_ban или comment_ban
|
|
const muteInfo = sanctions.find((s) =>
|
|
['user_timed_ban', 'chat_ban', 'comment_ban'].includes(s.type)
|
|
) || null;
|
|
const isMuted = !!muteInfo;
|
|
|
|
// Запрет публикации: user_timed_ban или publish_ban
|
|
const cantPublishInfo = sanctions.find((s) =>
|
|
['user_timed_ban', 'publish_ban'].includes(s.type)
|
|
) || null;
|
|
const isCantPublish = !!cantPublishInfo;
|
|
|
|
return (
|
|
<SanctionsContext.Provider value={{
|
|
sanctions, isMuted, isCantPublish,
|
|
muteInfo, cantPublishInfo,
|
|
loading, reload,
|
|
}}>
|
|
{children}
|
|
</SanctionsContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useSanctions() {
|
|
return useContext(SanctionsContext);
|
|
}
|