Files
ForbiddenStarsApp/frontend/src/context/ToastContext.tsx
T
NotBigGhostandClaude Opus 5 a9de68f27c Simplify: общий загрузчик файлов и единые ключи на фронте
- Четыре копии «ресайз → FormData → CSRF из cookie → fetch → разбор конверта
  ошибки» (аватар, фото партии, фото из админки, иконка ачивки) сведены в
  lib/upload.ts. Иконка по-прежнему уходит оригиналом: ресайз в JPEG убил бы
  прозрачность герба.
- Три копии readCsrfToken и вторая копия resizeImage удалены — берём readCookie
  из api/client.ts и resizeImage из lib/image.ts с параметром размера.
- Ключи, протухающие от партии, собраны в matchAffectedKeys: раньше они были
  написаны строками мимо реестра qk в двух местах, и переименование ключа
  сломало бы инвалидацию молча.
- Различение «нет сессии» и «нет связи» вынесено в authProbeRetry и применено к
  обеим пробам. Прошлый заход чинил только игроцкие гварды, и RequireAdmin
  по-прежнему выкидывал админа на страницу входа при обрыве связи.
- ToastContext больше не пересоздаёт значение контекста: провайдер обёрнут
  вокруг всего приложения, и каждый тост перерисовывал всех потребителей.
- PlaceEditor не трогает DOM, пока цель подсветки не изменилась (было
  querySelectorAll на каждый pointermove).

#8

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BoiJK9ux8peeyjLb8TYjFf
2026-09-09 18:52:06 +03:00

36 lines
1.3 KiB
TypeScript

import { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
import type { PropsWithChildren } from "react";
interface ToastCtx {
show: (message: string) => void;
}
const Ctx = createContext<ToastCtx>({ show: () => {} });
export function ToastProvider({ children }: PropsWithChildren) {
const [message, setMessage] = useState<string | null>(null);
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
const show = useCallback((msg: string) => {
setMessage(msg);
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => setMessage(null), 2800);
}, []);
// Провайдер обёрнут вокруг всего приложения и перерисовывается на каждый тост:
// без useMemo новое значение контекста заставляло бы перерисоваться и всех
// потребителей, к самому тосту отношения не имеющих.
const value = useMemo(() => ({ show }), [show]);
return (
<Ctx.Provider value={value}>
{children}
{message && <div className="toast">{message}</div>}
</Ctx.Provider>
);
}
export function useToast(): ToastCtx {
return useContext(Ctx);
}