@replyke/core
Version:
Replyke: Build interactive apps with social features like comments, votes, feeds, user lists, notifications, and more.
238 lines • 8.71 kB
JavaScript
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import useFetchSpace from "./useFetchSpace";
import useFetchSpaceByShortId from "./useFetchSpaceByShortId";
import useFetchSpaceBySlug from "./useFetchSpaceBySlug";
import useFetchSpaceBreadcrumb from "./useFetchSpaceBreadcrumb";
import useUpdateSpace from "./useUpdateSpace";
import useDeleteSpace from "./useDeleteSpace";
import useJoinSpace from "./useJoinSpace";
import useLeaveSpace from "./useLeaveSpace";
import useSpacePermissions from "./useSpacePermissions";
import { handleError } from "../../utils/handleError";
function useSpaceData({ spaceId, shortId, slug, space: spaceProp, include, }) {
const [space, setSpace] = useState(spaceProp);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [breadcrumb, setBreadcrumb] = useState([]);
// Cache to store fetched spaces keyed by unique identifier
const spaceCache = useRef({});
// Fetch hooks
const fetchSpace = useFetchSpace();
const fetchSpaceByShortId = useFetchSpaceByShortId();
const fetchSpaceBySlug = useFetchSpaceBySlug();
const fetchSpaceBreadcrumb = useFetchSpaceBreadcrumb();
// Operation hooks
const updateSpaceHook = useUpdateSpace();
const deleteSpaceHook = useDeleteSpace();
const joinSpaceHook = useJoinSpace();
const leaveSpaceHook = useLeaveSpace();
// Stabilize include param to prevent infinite loops when passed as inline array
const stableInclude = useMemo(() => include,
// eslint-disable-next-line react-hooks/exhaustive-deps
[JSON.stringify(include)]);
// Compute permissions
const permissions = useSpacePermissions({
memberPermissions: space?.memberPermissions,
postingPermission: space?.postingPermission || "members",
readingPermission: space?.readingPermission || "anyone",
});
// Handle space update
const handleUpdateSpace = useCallback(async ({ update }) => {
if (!space)
return;
try {
const newSpace = await updateSpaceHook({
spaceId: space.id,
update,
});
if (newSpace)
setSpace(newSpace);
return newSpace;
}
catch (err) {
handleError(err, "Failed to update space");
setError("Failed to update space");
}
}, [space, updateSpaceHook]);
// Handle space delete
const handleDeleteSpace = useCallback(async () => {
if (!space)
return;
try {
await deleteSpaceHook({ spaceId: space.id });
setSpace(undefined);
}
catch (err) {
handleError(err, "Failed to delete space");
setError("Failed to delete space");
}
}, [space, deleteSpaceHook]);
// Handle join space
const handleJoinSpace = useCallback(async () => {
if (!space)
return;
try {
const response = await joinSpaceHook({ spaceId: space.id });
const member = response.membership;
// Update space with new memberPermissions and member count
// Note: When joining, role is always "member", status is "pending" or "active"
setSpace((prev) => {
if (!prev)
return prev;
return {
...prev,
membersCount: prev.membersCount + 1,
memberPermissions: {
isAdmin: false,
isModerator: false,
isMember: member.status === "active",
status: member.status,
canPost: member.status === "active",
canModerate: false,
canRead: true,
},
};
});
}
catch (err) {
handleError(err, "Failed to join space");
setError("Failed to join space");
}
}, [space, joinSpaceHook]);
// Handle leave space
const handleLeaveSpace = useCallback(async () => {
if (!space)
return;
try {
await leaveSpaceHook({ spaceId: space.id });
// Update space to remove memberPermissions and decrement member count
setSpace((prev) => {
if (!prev)
return prev;
return {
...prev,
membersCount: Math.max(0, prev.membersCount - 1),
memberPermissions: null,
};
});
}
catch (err) {
handleError(err, "Failed to leave space");
setError("Failed to leave space");
}
}, [space, leaveSpaceHook]);
// Fetch space effect
useEffect(() => {
const handleFetchSpace = async () => {
if (!spaceId && !shortId && !slug)
return;
// If space is already loaded with matching ID, skip fetch
if (space && spaceId && space.id === spaceId)
return;
if (space && shortId && space.shortId === shortId)
return;
if (space && slug && space.slug === slug)
return;
const uniqueKey = `${spaceId ?? ""}-${shortId ?? ""}-${slug ?? ""}`;
// If we have a cached space, update the state and exit
if (spaceCache.current[uniqueKey]) {
setSpace(spaceCache.current[uniqueKey]);
return;
}
setLoading(true);
setError(null);
try {
let fetchedSpace = null;
if (spaceId) {
fetchedSpace = await fetchSpace({ spaceId, include: stableInclude });
}
else if (shortId) {
fetchedSpace = await fetchSpaceByShortId({ shortId, include: stableInclude });
}
else if (slug) {
fetchedSpace = await fetchSpaceBySlug({ slug, include: stableInclude });
}
if (fetchedSpace) {
// Store the fetched space in cache
spaceCache.current[uniqueKey] = fetchedSpace;
setSpace(fetchedSpace);
}
else {
setSpace(null);
}
}
catch (err) {
handleError(err, "Failed to fetch space");
setError("Failed to fetch space");
setSpace(null);
}
finally {
setLoading(false);
}
};
handleFetchSpace();
}, [
fetchSpace,
fetchSpaceByShortId,
fetchSpaceBySlug,
spaceId,
shortId,
slug,
space,
stableInclude,
]);
// Fetch breadcrumb effect
useEffect(() => {
const handleFetchBreadcrumb = async () => {
if (!space?.id) {
setBreadcrumb([]);
return;
}
try {
const breadcrumbData = await fetchSpaceBreadcrumb({
spaceId: space.id,
});
setBreadcrumb(breadcrumbData.breadcrumb);
}
catch (err) {
// Breadcrumb is not critical, just log the error
handleError(err, "Failed to fetch space breadcrumb");
setBreadcrumb([]);
}
};
handleFetchBreadcrumb();
}, [space?.id, fetchSpaceBreadcrumb]);
// Update space when prop changes
useEffect(() => {
if (spaceProp)
setSpace(spaceProp);
}, [spaceProp]);
return {
space,
setSpace,
// Permissions
isMember: permissions.isMember,
isAdmin: permissions.isAdmin,
isModerator: permissions.isModerator,
canPost: permissions.canPost,
canModerate: permissions.canModerate,
canRead: permissions.canRead,
membershipStatus: space?.memberPermissions?.status || null,
isPending: permissions.isPending,
isBanned: permissions.isBanned,
// Hierarchy
breadcrumb,
parentSpace: space?.parentSpace || null,
childSpaces: space?.childSpaces || [],
// Operations
updateSpace: handleUpdateSpace,
deleteSpace: handleDeleteSpace,
joinSpace: handleJoinSpace,
leaveSpace: handleLeaveSpace,
// State
loading,
error,
};
}
export default useSpaceData;
//# sourceMappingURL=useSpaceData.js.map