Группы: приглашения вместо авто-добавления (согласиться/отказаться)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-17 20:07:20 +03:00
co-authored by Claude Opus 4.8
parent 66370c7df4
commit a5c54512a5
20 changed files with 742 additions and 98 deletions
@@ -0,0 +1,58 @@
import { ApiError } from "../api/client";
import { useToast } from "../context/ToastContext";
import { useAcceptInvitation, useDeclineInvitation, useMyInvitations } from "../hooks/invitations";
export function GroupInvitations() {
const { data: invitations } = useMyInvitations();
const accept = useAcceptInvitation();
const decline = useDeclineInvitation();
const toast = useToast();
if (!invitations || invitations.length === 0) return null;
const onAccept = async (id: number) => {
try {
await accept.mutateAsync(id);
toast.show("Вы вступили в группу");
} catch (e) {
toast.show(e instanceof ApiError ? e.message : "Ошибка");
}
};
const onDecline = async (id: number) => {
try {
await decline.mutateAsync(id);
toast.show("Приглашение отклонено");
} catch (e) {
toast.show(e instanceof ApiError ? e.message : "Ошибка");
}
};
const busy = accept.isPending || decline.isPending;
return (
<div className="card">
<h3>Приглашения в группы</h3>
<div className="stack">
{invitations.map((inv) => (
<div key={inv.id} className="row-between">
<div>
<b>{inv.group_name}</b>
{inv.invited_by_nickname && (
<div className="muted small">пригласил: {inv.invited_by_nickname}</div>
)}
</div>
<div className="row" style={{ gap: 6 }}>
<button className="btn btn-primary small" onClick={() => onAccept(inv.id)} disabled={busy}>
Согласиться
</button>
<button className="btn btn-ghost small" onClick={() => onDecline(inv.id)} disabled={busy}>
Отказаться
</button>
</div>
</div>
))}
</div>
</div>
);
}