import {useCallback, useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {Loader2, Send, Square} from 'lucide-react'; import {toast} from 'sonner'; import {Badge} from '@/components/ui/badge'; import {Button} from '@/components/ui/button'; import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card'; import {Input} from '@/components/ui/input'; import {Label} from '@/components/ui/label'; import {Textarea} from '@/components/ui/textarea'; import HintCard from '../components/HintCard.jsx'; import {formatDateTime} from '../utils/format.js'; import PageContainer from '../components/PageContainer.jsx'; import {createHint, endHint, fetchAdminHints, setAutoHintEnabled} from '../utils/hints.js'; import {ROLES} from '../utils/admin.js'; import {MAX_HINT_LENGTH} from '../utils/limits.js'; const WEEK_MS = 7 * 24 * 60 * 60 * 1000; // The value of a datetime-local input: local time, no seconds. const toLocalInput = (date) => { const local = new Date(date.getTime() - date.getTimezoneOffset() * 60_000); return local.toISOString().slice(0, 16); }; const emptyForm = () => { const now = new Date(); return {textEn: '', textDe: '', kind: 'DISMISS', startsAt: toLocalInput(now), endsAt: toLocalInput(new Date(now.getTime() + WEEK_MS)), targetNoSteam: false, targetRole: '', targetNewDays: '', targetEmptyLibrary: false}; }; const targetSummary = (hint, t) => { const groups = [ hint.targetNoSteam && t('ADMIN_HINTS.TARGET_SUMMARY_NO_STEAM'), hint.targetRole && t('ADMIN_HINTS.TARGET_SUMMARY_ROLE', {role: t(`ROLES.${hint.targetRole}`)}), hint.targetNewDays && t('ADMIN_HINTS.TARGET_SUMMARY_NEW', {count: hint.targetNewDays}), hint.targetEmptyLibrary && t('ADMIN_HINTS.TARGET_SUMMARY_EMPTY_LIBRARY'), ].filter(Boolean); return groups.length ? groups.join(' + ') : t('ADMIN_HINTS.TARGET_ALL'); }; /** * PLATFORM_ADMIN only: write, preview and end the dashboard hints. Auth checks the role. */ export default function AdminHintsPage() { const {t, i18n} = useTranslation(); const [hints, setHints] = useState([]); const [automatic, setAutomatic] = useState([]); const [status, setStatus] = useState('loading'); const [form, setForm] = useState(emptyForm); const [busy, setBusy] = useState(false); const load = useCallback(async () => { try { const response = await fetchAdminHints(); if (response.status === 401) return; if (response.status === 403) { setStatus('forbidden'); return; } if (!response.ok) { setStatus('error'); return; } const data = await response.json(); setHints(data.results); setAutomatic(data.automatic ?? []); setStatus('ready'); } catch (err) { console.error('Admin hints error:', err); setStatus('error'); } }, []); useEffect(() => { load(); }, [load]); const run = async (request, doneKey, onOk) => { setBusy(true); try { const response = await request(); if (response.status === 401) return; if (response.ok) { toast.success(t(doneKey)); onOk?.(); await load(); } else { const data = await response.json().catch(() => ({})); toast.error(t(data.code === 'INVALID_HINT' ? 'ADMIN_HINTS.INVALID' : 'ADMIN_HINTS.ERROR')); } } catch (err) { console.error('Admin hint action error:', err); toast.error(t('INFO.ERROR.NETWORK_FAILURE')); } finally { setBusy(false); } }; const submit = (event) => { event.preventDefault(); run(() => createHint({ textEn: form.textEn.trim(), textDe: form.textDe.trim(), kind: form.kind, startsAt: new Date(form.startsAt).toISOString(), endsAt: new Date(form.endsAt).toISOString(), targetNoSteam: form.targetNoSteam, targetRole: form.targetRole || null, targetNewDays: form.targetNewDays ? Number(form.targetNewDays) : null, targetEmptyLibrary: form.targetEmptyLibrary, }), 'ADMIN_HINTS.CREATED', () => setForm(emptyForm())); }; const change = (field) => (event) => setForm({...form, [field]: event.target.value}); const tick = (field) => (event) => setForm({...form, [field]: event.target.checked}); const formatDate = (value) => formatDateTime(value, i18n.language, {dateStyle: 'short', timeStyle: 'short'}); if (status === 'forbidden') { return (

{t('ADMIN_HINTS.NO_ACCESS')}

); } return ( {t('ADMIN_HINTS.NEW')}