UMBRA/frontend/src/components/calendar/SharedCalendarSettings.tsx
Kyle Pope 38334b77a3 Shrink color picker swatches (h-8 → h-6)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:02:29 +08:00

146 lines
4.8 KiB
TypeScript

import { useState } from 'react';
import { LogOut } from 'lucide-react';
import { useQuery } from '@tanstack/react-query';
import api from '@/lib/api';
import type { SharedCalendarMembership, CalendarMemberInfo, Connection } from '@/types';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogClose,
} from '@/components/ui/dialog';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { useConfirmAction } from '@/hooks/useConfirmAction';
import { useSharedCalendars } from '@/hooks/useSharedCalendars';
import { useConnections } from '@/hooks/useConnections';
import PermissionBadge from './PermissionBadge';
import CalendarMemberList from './CalendarMemberList';
import CalendarMemberSearch from './CalendarMemberSearch';
const colorSwatches = [
'#3b82f6', '#ef4444', '#f97316', '#eab308',
'#22c55e', '#8b5cf6', '#ec4899', '#06b6d4',
];
interface SharedCalendarSettingsProps {
membership: SharedCalendarMembership;
onClose: () => void;
}
export default function SharedCalendarSettings({ membership, onClose }: SharedCalendarSettingsProps) {
const [localColor, setLocalColor] = useState(membership.local_color || membership.calendar_color);
const { updateColor, leaveCalendar, invite, isInviting } = useSharedCalendars();
const { connections } = useConnections();
const membersQuery = useQuery({
queryKey: ['calendar-members', membership.calendar_id],
queryFn: async () => {
const { data } = await api.get<CalendarMemberInfo[]>(
`/shared-calendars/${membership.calendar_id}/members`
);
return data;
},
});
const members = membersQuery.data ?? [];
const { confirming: leaveConfirming, handleClick: handleLeaveClick } = useConfirmAction(
async () => {
await leaveCalendar({ calendarId: membership.calendar_id, memberId: membership.id });
onClose();
}
);
const handleColorSelect = async (color: string) => {
setLocalColor(color);
await updateColor({ calendarId: membership.calendar_id, localColor: color });
};
const handleInvite = async (conn: Connection) => {
await invite({
calendarId: membership.calendar_id,
connectionId: conn.id,
permission: 'read_only',
canAddOthers: false,
});
membersQuery.refetch();
};
return (
<Dialog open={true} onOpenChange={onClose}>
<DialogContent className="max-w-2xl">
<DialogClose onClick={onClose} />
<DialogHeader>
<DialogTitle>Shared Calendar Settings</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div>
<h3 className="text-sm font-medium">{membership.calendar_name}</h3>
<div className="flex items-center gap-2 mt-1">
<span className="text-xs text-muted-foreground">Your permission:</span>
<PermissionBadge permission={membership.permission} />
</div>
</div>
<div className="space-y-2">
<Label>Your Color</Label>
<div className="flex gap-2">
{colorSwatches.map((c) => (
<button
key={c}
type="button"
onClick={() => handleColorSelect(c)}
className="h-6 w-6 rounded-full border-2 transition-all duration-150 hover:scale-110"
style={{
backgroundColor: c,
borderColor: localColor === c ? 'hsl(0 0% 98%)' : 'transparent',
boxShadow: localColor === c ? `0 0 0 2px ${c}40` : 'none',
}}
/>
))}
</div>
</div>
<div className="space-y-2">
<Label>Members ({members.length})</Label>
<CalendarMemberList
members={members}
isLoading={membersQuery.isLoading}
isOwner={false}
readOnly={true}
/>
</div>
{membership.can_add_others && (
<div className="space-y-2">
<Label>Add Members</Label>
<CalendarMemberSearch
connections={connections}
existingMembers={members}
onSelect={handleInvite}
isLoading={isInviting}
/>
</div>
)}
<div className="flex items-center justify-between pt-2 border-t border-border">
<Button
variant="destructive"
size="sm"
onClick={handleLeaveClick}
>
<LogOut className="mr-2 h-4 w-4" />
{leaveConfirming ? 'Sure?' : 'Leave Calendar'}
</Button>
<Button variant="outline" onClick={onClose}>
Close
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}