Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 12x 6x 6x 6x 12x 12x 6x 1x 1x 5x 6x 6x 1x 1x 5x 5x 4x 4x 4x 4x 5x 5x 5x 5x 5x 5x 12x | import { useEffect, useMemo, useState } from "preact/hooks";
import { checkHomeownerAuth } from "../../components/homeowner/auth-check";
export type Membership = { boardId: string; boardName: string };
export type MembershipMap = Record<string, Membership[]>;
export function useBoardMemberships(
items: Array<{ entityType: string; entityId: string }>,
apiUrl: string,
): MembershipMap {
const itemsKey = useMemo(() => {
const keys = items.map((it) => `${it.entityType}:${it.entityId}`);
keys.sort();
return keys.join(",");
}, [items]);
const [memberships, setMemberships] = useState<MembershipMap>({});
useEffect(() => {
if (!itemsKey) {
setMemberships({});
return;
}
let cancelled = false;
async function load() {
// Skip the fetch for anonymous visitors — the endpoint requires
// auth and a 401 surfaces in the browser console as a network
// error, dropping Lighthouse Best-Practices below 100. Mirrors
// the guard in MembershipsProvider for grid pages.
const loggedIn = await checkHomeownerAuth(apiUrl);
if (cancelled || !loggedIn) {
Eif (!cancelled) setMemberships({});
return;
}
try {
const resp = await fetch(
`${apiUrl}/api/homeowner/mood-boards/memberships?items=${encodeURIComponent(itemsKey)}`,
{ credentials: "include" },
);
Iif (!resp.ok) return;
const body = await resp.json();
Iif (cancelled) return;
setMemberships(body?.memberships ?? {});
} catch {
// Non-essential UI — silent on network error.
}
}
load();
const onChange = () => load();
window.addEventListener("ho:board-membership-changed", onChange);
return () => {
cancelled = true;
window.removeEventListener("ho:board-membership-changed", onChange);
};
}, [apiUrl, itemsKey]);
return memberships;
}
|