Public Access
feat: add What's new feature
Dashboard dialog shows unseen news entries. Users mark them seen with "Got it" or revisit via the /whats-new page. Admins publish, edit, and delete entries on /admin/news. Full i18n support and test coverage included.
This commit is contained in:
1 parent
79b086c525
commit
b811255c16
17 files changed
+690
-1
No files matched your search
@@ -0,0 +1,57 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {Loader2} from 'lucide-react';
|
||||
|
||||
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
|
||||
import NewsEntry from '../components/NewsEntry.jsx';
|
||||
import PageContainer from '../components/PageContainer.jsx';
|
||||
import {fetchNews, markNewsSeen} from '../utils/news.js';
|
||||
|
||||
/**
|
||||
* Every news entry, newest first. Opening the page marks them seen.
|
||||
*/
|
||||
export default function WhatsNewPage() {
|
||||
const {t} = useTranslation();
|
||||
const [entries, setEntries] = useState([]);
|
||||
const [status, setStatus] = useState('loading');
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
const response = await fetchNews();
|
||||
if (response.status === 401) return;
|
||||
if (!response.ok) {
|
||||
setStatus('error');
|
||||
return;
|
||||
}
|
||||
setEntries((await response.json()).results);
|
||||
setStatus('ready');
|
||||
// A failed mark is silent: the dashboard dialog shows the entries again.
|
||||
markNewsSeen().catch(() => undefined);
|
||||
} catch (err) {
|
||||
console.error('News error:', err);
|
||||
setStatus('error');
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<PageContainer className="space-y-4 px-4 py-6 md:px-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('NEWS.TITLE')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{status === 'loading' && <Loader2 className="size-5 animate-spin text-muted-foreground"/>}
|
||||
{status === 'error' && <p className="text-sm text-destructive">{t('NEWS.LOAD_ERROR')}</p>}
|
||||
{status === 'ready' && entries.length === 0 && <p className="text-sm text-muted-foreground">{t('NEWS.EMPTY')}</p>}
|
||||
{status === 'ready' && entries.length > 0 && (
|
||||
<ul className="divide-y">
|
||||
{entries.map((entry) => <li key={entry.id} className="py-3"><NewsEntry entry={entry}/></li>)}
|
||||
</ul>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user