Files
ToBePlayed-Frontend/src/components/FollowsCard.jsx
T
lobo 43c4862606
Build, Test & Push Frontend / quality-check (push) Successful in 34s
Build, Test & Push Frontend / docker (push) Successful in 23s
feat(ui): add follow button, follows card, and inbox page
2026-10-06 16:22:22 +02:00

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>
);
}