Public Access
feat(ui): add follow button, follows card, and inbox page
This commit is contained in:
1 parent
3aed243e35
commit
43c4862606
23 files changed
+603
-2
No files matched your search
@@ -0,0 +1,67 @@
|
||||
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 {cn} from '@/lib/utils';
|
||||
import PageContainer from '../components/PageContainer.jsx';
|
||||
import {formatDate} from '../utils/format.js';
|
||||
import {digestParts, fetchInbox, markInboxRead} from '../utils/inbox.js';
|
||||
|
||||
/**
|
||||
* The newest notes about the people the user follows. Opening the page marks them read.
|
||||
*/
|
||||
export default function InboxPage() {
|
||||
const {t, i18n} = useTranslation();
|
||||
const [notes, setNotes] = useState([]);
|
||||
const [status, setStatus] = useState('loading');
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const {ok, status: code, data} = await fetchInbox();
|
||||
if (code === 401) return;
|
||||
if (!ok) {
|
||||
setStatus('error');
|
||||
return;
|
||||
}
|
||||
setNotes(data.results);
|
||||
setStatus('ready');
|
||||
// A failed mark is silent: the notes show as unread again on the next visit.
|
||||
if (data.unread > 0) markInboxRead();
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const text = (note) => {
|
||||
const name = note.displayName ?? t('FOLLOW.PRIVATE_USER');
|
||||
if (note.kind === 'VISIBILITY') return t('INBOX.VISIBILITY', {name});
|
||||
const parts = digestParts(note).map(({key, count}) => t(`INBOX.PART_${key}`, {count})).join(', ');
|
||||
return t('INBOX.DIGEST', {name, parts});
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer className="space-y-4 px-4 py-6 md:px-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{t('INBOX.TITLE')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{status === 'loading' && <Loader2 className="size-5 animate-spin text-muted-foreground"/>}
|
||||
{status === 'error' && <p className="text-sm text-destructive">{t('INBOX.LOAD_ERROR')}</p>}
|
||||
{status === 'ready' && notes.length === 0 && <p className="text-sm text-muted-foreground">{t('INBOX.EMPTY')}</p>}
|
||||
{status === 'ready' && notes.length > 0 && (
|
||||
<ul className="divide-y">
|
||||
{notes.map((note) => (
|
||||
<li key={note.id} className="space-y-1 py-3">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatDate(note.createdAt, i18n.language, {dateStyle: 'medium'})}
|
||||
</p>
|
||||
<p className={cn('break-words text-sm', !note.read && 'font-semibold')}>{text(note)}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user