Files
IdP/apps/frontend/hooks/use-primary-family.ts
2026-07-01 23:27:48 +03:00

125 lines
4.0 KiB
TypeScript

'use client';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useFamilyOverlay } from '@/components/family/family-overlay-provider';
import { useAuth } from '@/components/id/auth-provider';
import {
FamilyGroup,
FamilyPresenceMember,
fetchFamilyGroup,
fetchFamilyGroups,
fetchFamilyPresence,
getAccessToken
} from '@/lib/api';
import { resolveFamilyGroupId } from '@/lib/selected-family-storage';
export function useSelectedFamily(enabled = true) {
const { user, token, isPinLocked, isLoading, isContentReady } = useAuth();
const { selectedGroupId, setSelectedGroupId, isSelectionHydrated } = useFamilyOverlay();
const userId = user?.id;
const [groups, setGroups] = useState<FamilyGroup[]>([]);
const [group, setGroup] = useState<FamilyGroup | null>(null);
const [presenceMembers, setPresenceMembers] = useState<FamilyPresenceMember[]>([]);
const [loading, setLoading] = useState(true);
const hasLoadedRef = useRef(false);
const refresh = useCallback(async () => {
const accessToken = getAccessToken() ?? token?.trim() ?? null;
if (!enabled || !userId || !accessToken || isPinLocked || isLoading || !isContentReady) {
setGroups([]);
setGroup(null);
setPresenceMembers([]);
hasLoadedRef.current = false;
setLoading(false);
return;
}
let pendingHydration = false;
try {
const groupsResponse = await fetchFamilyGroups(userId, accessToken);
const list = groupsResponse.groups ?? [];
setGroups(list);
if (!list.length) {
setGroup(null);
setPresenceMembers([]);
if (selectedGroupId) setSelectedGroupId(null);
hasLoadedRef.current = true;
return;
}
const effectiveId = resolveFamilyGroupId(list, selectedGroupId, isSelectionHydrated);
if (!effectiveId) {
pendingHydration = !isSelectionHydrated;
return;
}
if (effectiveId !== selectedGroupId) {
setSelectedGroupId(effectiveId);
}
const [groupResponse, presenceResponse] = await Promise.all([
fetchFamilyGroup(effectiveId, accessToken),
fetchFamilyPresence(effectiveId, accessToken)
]);
setGroup(groupResponse);
setPresenceMembers(presenceResponse.members ?? []);
hasLoadedRef.current = true;
} catch {
// Фоновая ошибка не должна сбрасывать уже показанную семью в сайдбаре.
} finally {
if (!pendingHydration) {
setLoading(false);
}
}
}, [enabled, isContentReady, isLoading, isPinLocked, isSelectionHydrated, selectedGroupId, setSelectedGroupId, token, userId]);
useEffect(() => {
if (!enabled || !userId || !token || isPinLocked || isLoading || !isContentReady) {
setGroups([]);
setGroup(null);
setPresenceMembers([]);
hasLoadedRef.current = false;
setLoading(false);
return;
}
setLoading(!hasLoadedRef.current);
void refresh();
}, [enabled, isContentReady, isLoading, isPinLocked, isSelectionHydrated, refresh, token, userId]);
useEffect(() => {
const accessToken = getAccessToken() ?? token?.trim() ?? null;
if (!enabled || !accessToken || isPinLocked || isLoading || !isContentReady || !group) return;
const timer = window.setInterval(() => void refresh(), 25_000);
return () => window.clearInterval(timer);
}, [enabled, group, isContentReady, isLoading, isPinLocked, refresh, token]);
const presenceByUserId = useMemo(
() => new Map(presenceMembers.map((member) => [member.userId, member])),
[presenceMembers]
);
const groupSummaries = useMemo(
() => groups.map((item) => ({ id: item.id, name: item.name })),
[groups]
);
return {
groups,
groupSummaries,
group,
loading,
hasFamily: Boolean(group),
selectedGroupId: group?.id ?? selectedGroupId,
setSelectedGroupId,
presenceByUserId,
refresh
};
}
/** @deprecated Используйте useSelectedFamily */
export function usePrimaryFamily(enabled = true) {
return useSelectedFamily(enabled);
}