UMBRA/frontend/src/hooks/useEventLock.ts
Kyle Pope e5690625eb Fix member removal bug + QA fixes + shared calendar sidebar styling
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>
2026-03-06 06:07:55 +08:00

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,
};
}