Bug fix: - CalendarMemberRow: add type="button" to remove button (was submitting parent form) QA fixes: - EventDetailPanel: use axios.isAxiosError() instead of duck-typing for lock errors - EventDetailPanel: only call onSaved on create (edits return to view mode, not close) - CalendarForm: remove 4 redundant membersQuery.refetch() calls (mutations already invalidate) - useEventLock: remove unused lockHeld ref from return, fix stale eventId in onSuccess - EventLockBanner: guard against invalid date parse UI: - SharedCalendarSection: add purple Ghost icon next to "SHARED CALENDARS" header Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
import { useRef, useEffect, useCallback } from 'react';
|
|
import { useMutation } from '@tanstack/react-query';
|
|
import api from '@/lib/api';
|
|
import type { EventLockInfo } from '@/types';
|
|
|
|
export function useEventLock(eventId: number | null) {
|
|
const lockHeldRef = useRef(false);
|
|
const activeEventIdRef = useRef<number | null>(null);
|
|
|
|
const acquireMutation = useMutation({
|
|
mutationFn: async (id: number) => {
|
|
const { data } = await api.post<EventLockInfo>(
|
|
`/shared-calendars/events/${id}/lock`
|
|
);
|
|
return data;
|
|
},
|
|
onSuccess: (_data, lockedId) => {
|
|
lockHeldRef.current = true;
|
|
activeEventIdRef.current = lockedId;
|
|
},
|
|
});
|
|
|
|
const releaseMutation = useMutation({
|
|
mutationFn: async (id: number) => {
|
|
await api.delete(`/shared-calendars/events/${id}/lock`);
|
|
},
|
|
onSuccess: () => {
|
|
lockHeldRef.current = false;
|
|
activeEventIdRef.current = null;
|
|
},
|
|
});
|
|
|
|
const acquire = useCallback(async () => {
|
|
if (!eventId) return null;
|
|
const data = await acquireMutation.mutateAsync(eventId);
|
|
return data;
|
|
}, [eventId]);
|
|
|
|
const release = useCallback(async () => {
|
|
const id = activeEventIdRef.current;
|
|
if (!id || !lockHeldRef.current) return;
|
|
try {
|
|
await releaseMutation.mutateAsync(id);
|
|
} catch {
|
|
// Fire-and-forget on release errors
|
|
}
|
|
}, []);
|
|
|
|
// Auto-release on unmount or eventId change
|
|
useEffect(() => {
|
|
return () => {
|
|
const id = activeEventIdRef.current;
|
|
if (id && lockHeldRef.current) {
|
|
// Fire-and-forget cleanup
|
|
api.delete(`/shared-calendars/events/${id}/lock`).catch(() => {});
|
|
lockHeldRef.current = false;
|
|
activeEventIdRef.current = null;
|
|
}
|
|
};
|
|
}, [eventId]);
|
|
|
|
return {
|
|
acquire,
|
|
release,
|
|
isAcquiring: acquireMutation.isPending,
|
|
acquireError: acquireMutation.error,
|
|
};
|
|
}
|