feat(federation): add room sync and deletion notification endpoints for federated instances fix(federation): handle room deletion and update settings during sync process feat(federation): enhance FederatedRoomCard and FederatedRoomDetail components to display deleted rooms i18n: add translations for room deletion messages in English and German
118 lines
4.4 KiB
JavaScript
118 lines
4.4 KiB
JavaScript
import { Globe, Trash2, ExternalLink, Hash, Users, Video, VideoOff, AlertTriangle } from 'lucide-react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useLanguage } from '../contexts/LanguageContext';
|
|
import api from '../services/api';
|
|
import toast from 'react-hot-toast';
|
|
|
|
export default function FederatedRoomCard({ room, onRemove }) {
|
|
const { t } = useLanguage();
|
|
const navigate = useNavigate();
|
|
|
|
const isDeleted = room.deleted === 1 || room.deleted === true;
|
|
|
|
const handleJoin = (e) => {
|
|
e.stopPropagation();
|
|
if (isDeleted) return;
|
|
window.open(room.join_url, '_blank');
|
|
};
|
|
|
|
const handleRemove = async (e) => {
|
|
e.stopPropagation();
|
|
if (!confirm(t('federation.removeRoomConfirm'))) return;
|
|
try {
|
|
await api.delete(`/federation/federated-rooms/${room.id}`);
|
|
toast.success(t('federation.roomRemoved'));
|
|
onRemove?.();
|
|
} catch {
|
|
toast.error(t('federation.roomRemoveFailed'));
|
|
}
|
|
};
|
|
|
|
const recordingOn = room.allow_recording === 1 || room.allow_recording === true;
|
|
|
|
return (
|
|
<div className={`card-hover group p-5 cursor-pointer ${isDeleted ? 'opacity-60' : ''}`} onClick={() => navigate(`/federation/rooms/${room.id}`)}>
|
|
<div className="flex items-start justify-between mb-3">
|
|
<div className="flex-1 min-w-0">
|
|
<div className="flex items-center gap-2">
|
|
<Globe size={14} className="text-th-accent flex-shrink-0" />
|
|
<h3 className="text-base font-semibold text-th-text truncate group-hover:text-th-accent transition-colors">
|
|
{room.room_name}
|
|
</h3>
|
|
{isDeleted ? (
|
|
<span className="flex-shrink-0 px-2 py-0.5 bg-red-500/15 text-red-500 rounded-full text-xs font-medium flex items-center gap-1">
|
|
<AlertTriangle size={10} />
|
|
{t('federation.roomDeleted')}
|
|
</span>
|
|
) : (
|
|
<span className="flex-shrink-0 px-2 py-0.5 bg-th-accent/15 text-th-accent rounded-full text-xs font-medium">
|
|
{t('federation.federated')}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<p className="text-sm text-th-text-s mt-0.5 truncate">
|
|
{t('federation.from')}: <span className="font-medium">{room.from_user}</span>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Basic room info */}
|
|
<div className="grid grid-cols-2 gap-2 mb-4">
|
|
{room.meet_id && (
|
|
<div className="flex items-center gap-1.5 text-xs text-th-text-s">
|
|
<Hash size={12} className="text-th-accent flex-shrink-0" />
|
|
<span className="truncate font-mono" title={room.meet_id}>{room.meet_id.slice(0, 10)}…</span>
|
|
</div>
|
|
)}
|
|
<div className="flex items-center gap-1.5 text-xs text-th-text-s">
|
|
<Users size={12} className="text-th-accent flex-shrink-0" />
|
|
<span>
|
|
{t('federation.maxParticipants')}:{' '}
|
|
<span className="text-th-text font-medium">
|
|
{room.max_participants > 0 ? room.max_participants : t('federation.unlimited')}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center gap-1.5 text-xs col-span-2">
|
|
{recordingOn ? (
|
|
<>
|
|
<Video size={12} className="text-amber-500 flex-shrink-0" />
|
|
<span className="text-amber-500 font-medium">{t('federation.recordingOn')}</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<VideoOff size={12} className="text-th-text-s flex-shrink-0" />
|
|
<span className="text-th-text-s">{t('federation.recordingOff')}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Read-only notice */}
|
|
<p className="text-xs text-th-text-s mb-4 italic">
|
|
{isDeleted ? t('federation.roomDeletedNotice') : t('federation.readOnlyNotice')}
|
|
</p>
|
|
|
|
{/* Actions */}
|
|
<div className="flex items-center gap-2 pt-3 border-t border-th-border">
|
|
{!isDeleted && (
|
|
<button
|
|
onClick={handleJoin}
|
|
className="btn-primary text-xs py-1.5 px-3 flex-1"
|
|
>
|
|
<ExternalLink size={14} />
|
|
{t('federation.joinMeeting')}
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={handleRemove}
|
|
className="btn-ghost text-xs py-1.5 px-2 text-th-error hover:text-th-error"
|
|
title={t('federation.removeRoom')}
|
|
>
|
|
<Trash2 size={14} />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|