Public Access
78 lines
3.6 KiB
React
78 lines
3.6 KiB
React
import {useCallback, useEffect, useState} from 'react';
|
|
import {Link} from 'react-router-dom';
|
|
import {useTranslation} from 'react-i18next';
|
|
import {Users} from 'lucide-react';
|
|
import {toast} from 'sonner';
|
|
|
|
import {Button} from '@/components/ui/button';
|
|
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
|
|
import {fetchFollows, removeFollow} from '../utils/follows.js';
|
|
import {profileHref} from '../utils/people.js';
|
|
|
|
export default function FollowsCard() {
|
|
const {t} = useTranslation();
|
|
const [follows, setFollows] = useState(null);
|
|
const [failed, setFailed] = useState(false);
|
|
|
|
const load = useCallback(async () => {
|
|
const {ok, data} = await fetchFollows();
|
|
setFailed(!ok);
|
|
if (ok) setFollows(data);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
load();
|
|
}, [load]);
|
|
|
|
const end = async (id) => {
|
|
const {ok, status} = await removeFollow(id);
|
|
if (!ok && status !== 401) toast.error(t('FOLLOW.ERROR'));
|
|
await load();
|
|
};
|
|
|
|
const link = (displayName) => (
|
|
<Link to={profileHref(displayName)} className="min-w-0 truncate hover:text-primary hover:underline">{displayName}</Link>
|
|
);
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2"><Users className="size-4 text-primary"/>{t('FOLLOW.TITLE')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{failed && <p className="text-sm text-destructive">{t('FOLLOW.LOAD_ERROR')}</p>}
|
|
{follows && (
|
|
<>
|
|
<section className="space-y-2">
|
|
<h3 className="text-sm font-medium">{t('FOLLOW.FOLLOWING')}</h3>
|
|
{follows.following.length === 0 && <p className="text-sm text-muted-foreground">{t('FOLLOW.NO_FOLLOWING')}</p>}
|
|
<ul className="space-y-1">
|
|
{follows.following.map((entry) => (
|
|
<li key={entry.id} className="flex items-center justify-between gap-2 text-sm">
|
|
{link(entry.displayName)}
|
|
<Button variant="outline" size="sm" onClick={() => end(entry.id)}>{t('FOLLOW.UNFOLLOW')}</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
<section className="space-y-2">
|
|
<h3 className="text-sm font-medium">{t('FOLLOW.FOLLOWERS')}</h3>
|
|
{follows.followers.length === 0 && <p className="text-sm text-muted-foreground">{t('FOLLOW.NO_FOLLOWERS')}</p>}
|
|
<ul className="space-y-1">
|
|
{follows.followers.map((entry) => (
|
|
<li key={entry.id} className="flex items-center justify-between gap-2 text-sm">
|
|
{entry.displayName
|
|
? link(entry.displayName)
|
|
: <span className="min-w-0 truncate text-muted-foreground">{t('FOLLOW.PRIVATE_USER')}</span>}
|
|
<Button variant="outline" size="sm" onClick={() => end(entry.id)}>{t('FOLLOW.REMOVE')}</Button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
</>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|