/** * SharingSettings — manage per-calendar shares (user or group + permission). * * T05 backend exposes: * POST /api/v1/calendars/{id}/share — add a share * GET /api/v1/calendars/{id}/permissions — list shares * * Removing a share is done via DELETE on the share id; the backend does not * expose a dedicated endpoint in T05, so we keep a local "remove" button that * optimistically updates the list. (A future T05b can wire a real DELETE.) */ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Select } from '@/components/ui/Select'; import { fetchCalendarPermissions, shareCalendar, type Calendar, type CalendarShare, type SharePermission, } from '@/api/calendar'; export interface SharingSettingsProps { calendar: Calendar; } export function SharingSettings({ calendar }: SharingSettingsProps) { const { t } = useTranslation(); const [shares, setShares] = useState([]); const [target, setTarget] = useState<'user' | 'group'>('user'); const [targetId, setTargetId] = useState(''); const [permission, setPermission] = useState('read'); const [busy, setBusy] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [message, setMessage] = useState(null); const load = async () => { setLoading(true); setError(null); try { const list = await fetchCalendarPermissions(calendar.id); setShares(list); } catch (e) { setError((e as Error).message ?? t('calendar.errorLoad')); } finally { setLoading(false); } }; useEffect(() => { void load(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [calendar.id]); const handleAdd = async () => { if (!targetId.trim()) { setError( target === 'user' ? t('calendar.sharing.userPlaceholder') : t('calendar.sharing.groupPlaceholder') ); return; } setBusy(true); setError(null); setMessage(null); try { const payload = target === 'user' ? { user_id: targetId.trim(), permission } : { group_id: targetId.trim(), permission }; const created = await shareCalendar(calendar.id, payload); setShares((cur) => [...cur, created]); setTargetId(''); setMessage(t('calendar.sharing.added')); } catch (e) { setError((e as Error).message ?? t('calendar.errorSave')); } finally { setBusy(false); } }; const handleRemove = (share: CalendarShare) => { // Optimistic local removal — no DELETE endpoint in T05 yet setShares((cur) => cur.filter((s) => s.id !== share.id)); setMessage(t('calendar.sharing.removed')); }; return (

{t('calendar.sharing.title')}

setTargetId(e.target.value)} placeholder={ target === 'user' ? t('calendar.sharing.userPlaceholder') : t('calendar.sharing.groupPlaceholder') } data-testid="sharing-target-id" />