diff --git a/src/api/auth/useUserPermissions.ts b/src/api/auth/useUserPermissions.ts index 8b2f1fe4a..902db3b1d 100644 --- a/src/api/auth/useUserPermissions.ts +++ b/src/api/auth/useUserPermissions.ts @@ -2,10 +2,9 @@ import { queryOptions, useQuery } from "@tanstack/react-query"; import { supabase } from "@/integrations/supabase/client"; import { userPermissionsKeys } from "./types"; -async function checkUserPermissions( - userId: string, - permission: "edit_artists" | "is_admin", -) { +type Permission = "edit_artists" | "is_admin" | "is_super_admin"; + +async function checkUserPermissions(userId: string, permission: Permission) { try { // Use new admin roles system if (permission === "edit_artists") { @@ -26,6 +25,16 @@ async function checkUserPermissions( return false; } return data || false; + } else if (permission === "is_super_admin") { + const { data, error } = await supabase.rpc("has_admin_role", { + check_user_id: userId, + check_role: "super_admin", + }); + if (error) { + console.error("Error checking is_super_admin permission:", error); + return false; + } + return data || false; } return false; @@ -35,10 +44,7 @@ async function checkUserPermissions( } } -export function userPermissionsQuery( - userId: string, - permission: "edit_artists" | "is_admin", -) { +export function userPermissionsQuery(userId: string, permission: Permission) { return queryOptions({ queryKey: userPermissionsKeys.user(userId, permission), queryFn: () => checkUserPermissions(userId, permission), @@ -48,7 +54,7 @@ export function userPermissionsQuery( export function useUserPermissionsQuery( userId: string | undefined, - permission: "edit_artists" | "is_admin", + permission: Permission, ) { return useQuery({ ...userPermissionsQuery(userId!, permission), diff --git a/src/api/groups/types.ts b/src/api/groups/types.ts index 98268a938..efa1848e4 100644 --- a/src/api/groups/types.ts +++ b/src/api/groups/types.ts @@ -18,8 +18,8 @@ export type GroupMember = export const groupsKeys = { all: ["groups"] as const, - user: (userId: string, params: unknown = {}) => - [...groupsKeys.all, "user", userId, params] as const, + myGroups: (userId: string) => [...groupsKeys.all, "my", userId] as const, + allGroups: (userId: string) => [...groupsKeys.all, "all", userId] as const, details: () => [...groupsKeys.all, "detail"] as const, detail: (groupId: string) => [...groupsKeys.details(), groupId] as const, bySlug: () => [...groupsKeys.all, "by-slug"] as const, diff --git a/src/api/groups/useAllGroups.integration.test.ts b/src/api/groups/useAllGroups.integration.test.ts new file mode 100644 index 000000000..8fbb98f03 --- /dev/null +++ b/src/api/groups/useAllGroups.integration.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { renderHook, waitFor } from "@testing-library/react"; +import { useQuery } from "@tanstack/react-query"; +import { allGroupsQuery } from "./useAllGroups"; +import { createQueryWrapper } from "@/test/integration/harness"; +import { signInAsTestUser } from "@/test/integration/fixtures/auth"; +import { grantAdminRole } from "@/test/integration/fixtures/adminRoles"; +import { + addGroupMember, + createGroup, +} from "@/test/integration/fixtures/groups"; + +describe("allGroupsQuery", () => { + it("falls back to member-only results for a non-admin caller", async () => { + const userId = await signInAsTestUser(); + const myGroupId = await createGroup(userId); + await addGroupMember(myGroupId, userId); + // A group the caller has no membership row in at all. + const otherGroupId = await createGroup(crypto.randomUUID()); + await addGroupMember(otherGroupId, crypto.randomUUID()); + + const { result } = renderHook(() => useQuery(allGroupsQuery(userId)), { + wrapper: createQueryWrapper(), + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data?.map((group) => group.id)).toEqual([myGroupId]); + }); + + it("returns every non-archived group for a super-admin caller, with is_member computed per group", async () => { + const userId = await signInAsTestUser(); + await grantAdminRole(userId, "super_admin"); + const myGroupId = await createGroup(userId); + await addGroupMember(myGroupId, userId); + const otherGroupId = await createGroup(crypto.randomUUID()); + await addGroupMember(otherGroupId, crypto.randomUUID()); + + const { result } = renderHook(() => useQuery(allGroupsQuery(userId)), { + wrapper: createQueryWrapper(), + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + const myGroup = result.current.data?.find((g) => g.id === myGroupId); + const otherGroup = result.current.data?.find((g) => g.id === otherGroupId); + expect(myGroup?.is_member).toBe(true); + expect(otherGroup).toBeDefined(); + expect(otherGroup?.is_member).toBe(false); + }); +}); diff --git a/src/api/groups/useAllGroups.ts b/src/api/groups/useAllGroups.ts new file mode 100644 index 000000000..2b6ca67e6 --- /dev/null +++ b/src/api/groups/useAllGroups.ts @@ -0,0 +1,55 @@ +import { queryOptions } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; +import type { Group } from "./types"; +import { groupsKeys } from "./types"; +import { attachGroupMeta, fetchMyGroups, getUserGroupIds } from "./useMyGroups"; + +export function allGroupsQuery(userId: string) { + return queryOptions({ + queryKey: groupsKeys.allGroups(userId), + queryFn: () => fetchAllGroups(userId), + }); +} + +/** + * Every non-archived group, for a super-admin caller (the only role `groups` RLS + * lets read them all). Any other caller falls + * back to exactly the member-only result (never an error, never a leaked + * full group list). + */ +async function fetchAllGroups(userId: string): Promise { + const canViewAll = await isSuperAdmin(userId); + + if (!canViewAll) { + return fetchMyGroups(userId); + } + + const [userGroupIds, { data: groupsData, error }] = await Promise.all([ + getUserGroupIds(userId), + supabase + .from("groups") + .select("*") + .eq("archived", false) + .order("created_at", { ascending: false }), + ]); + + if (error) { + throw new Error(error.message || "Failed to fetch groups"); + } + + return attachGroupMeta(groupsData || [], userId, userGroupIds); +} + +async function isSuperAdmin(userId: string): Promise { + const { data, error } = await supabase.rpc("has_admin_role", { + check_user_id: userId, + check_role: "super_admin", + }); + + if (error) { + console.error("Error checking super admin role:", error); + return false; + } + + return data === true; +} diff --git a/src/api/groups/useCreateGroup.ts b/src/api/groups/useCreateGroup.ts index 2a5f2e9fd..4f16eadd2 100644 --- a/src/api/groups/useCreateGroup.ts +++ b/src/api/groups/useCreateGroup.ts @@ -55,10 +55,7 @@ export function useCreateGroupMutation() { return useMutation({ mutationFn: createGroup, - onSuccess: (_data, variables) => { - queryClient.invalidateQueries({ - queryKey: groupsKeys.user(variables.userId), - }); + onSuccess: () => { queryClient.invalidateQueries({ queryKey: groupsKeys.all, }); diff --git a/src/api/groups/useDeleteGroup.ts b/src/api/groups/useDeleteGroup.ts index c8f0103d8..981517033 100644 --- a/src/api/groups/useDeleteGroup.ts +++ b/src/api/groups/useDeleteGroup.ts @@ -27,11 +27,8 @@ export function useDeleteGroupMutation() { return useMutation({ mutationFn: deleteGroup, - onSuccess: (_data, variables) => { + onSuccess: () => { // Invalidate all group-related queries - queryClient.invalidateQueries({ - queryKey: groupsKeys.user(variables.userId), - }); queryClient.invalidateQueries({ queryKey: groupsKeys.all, }); diff --git a/src/api/groups/useGroupBySlug.ts b/src/api/groups/useGroupBySlug.ts index d8ec9a777..3d85ab2d4 100644 --- a/src/api/groups/useGroupBySlug.ts +++ b/src/api/groups/useGroupBySlug.ts @@ -31,12 +31,11 @@ async function fetchGroupBySlug(slug: string, userId: string): Promise { return membership.groups as Group; } - // If not found as a member, check if user is the creator + // Not a member: let `groups` RLS decide (creator, or super admin who can read every group) const { data, error } = await supabase .from("groups") .select("*") .eq("slug", slug) - .eq("created_by", userId) .eq("archived", false) .single(); diff --git a/src/api/groups/useJoinGroup.ts b/src/api/groups/useJoinGroup.ts index f2bc35a94..8c5368b1e 100644 --- a/src/api/groups/useJoinGroup.ts +++ b/src/api/groups/useJoinGroup.ts @@ -28,7 +28,10 @@ export function useJoinGroupMutation() { mutationFn: joinGroup, onSuccess: (_data, variables) => { queryClient.invalidateQueries({ - queryKey: groupsKeys.user(variables.userId), + queryKey: groupsKeys.myGroups(variables.userId), + }); + queryClient.invalidateQueries({ + queryKey: groupsKeys.allGroups(variables.userId), }); toast({ title: "Success", diff --git a/src/api/groups/useLeaveGroup.ts b/src/api/groups/useLeaveGroup.ts index 8059c2811..0ce9740c5 100644 --- a/src/api/groups/useLeaveGroup.ts +++ b/src/api/groups/useLeaveGroup.ts @@ -29,7 +29,10 @@ export function useLeaveGroupMutation() { mutationFn: leaveGroup, onSuccess: (_data, variables) => { queryClient.invalidateQueries({ - queryKey: groupsKeys.user(variables.userId), + queryKey: groupsKeys.myGroups(variables.userId), + }); + queryClient.invalidateQueries({ + queryKey: groupsKeys.allGroups(variables.userId), }); toast({ title: "Success", diff --git a/src/api/groups/useMyGroups.integration.test.ts b/src/api/groups/useMyGroups.integration.test.ts new file mode 100644 index 000000000..0bc8fb07a --- /dev/null +++ b/src/api/groups/useMyGroups.integration.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from "vitest"; +import { renderHook, waitFor } from "@testing-library/react"; +import { useQuery } from "@tanstack/react-query"; +import { myGroupsQuery } from "./useMyGroups"; +import { createQueryWrapper } from "@/test/integration/harness"; +import { signInAsTestUser } from "@/test/integration/fixtures/auth"; +import { + addGroupMember, + createGroup, +} from "@/test/integration/fixtures/groups"; + +describe("myGroupsQuery", () => { + it("returns only the groups the caller is a member of", async () => { + const userId = await signInAsTestUser(); + const myGroupId = await createGroup(userId); + await addGroupMember(myGroupId, userId); + // A group the caller has no membership row in at all. + const otherGroupId = await createGroup(crypto.randomUUID()); + await addGroupMember(otherGroupId, crypto.randomUUID()); + + const { result } = renderHook(() => useQuery(myGroupsQuery(userId)), { + wrapper: createQueryWrapper(), + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data?.map((group) => group.id)).toEqual([myGroupId]); + }); + + it("marks is_member true and is_creator correctly, without an admin check", async () => { + const userId = await signInAsTestUser(); + const ownGroupId = await createGroup(userId); + await addGroupMember(ownGroupId, userId); + const joinedGroupId = await createGroup(crypto.randomUUID()); + await addGroupMember(joinedGroupId, userId); + + const { result } = renderHook(() => useQuery(myGroupsQuery(userId)), { + wrapper: createQueryWrapper(), + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + const ownGroup = result.current.data?.find((g) => g.id === ownGroupId); + const joinedGroup = result.current.data?.find( + (g) => g.id === joinedGroupId, + ); + expect(ownGroup).toMatchObject({ is_member: true, is_creator: true }); + expect(joinedGroup).toMatchObject({ is_member: true, is_creator: false }); + }); + + it("reflects every member in member_count, not just the caller", async () => { + const userId = await signInAsTestUser(); + const groupId = await createGroup(userId); + await addGroupMember(groupId, userId); + await addGroupMember(groupId, crypto.randomUUID()); + + const { result } = renderHook(() => useQuery(myGroupsQuery(userId)), { + wrapper: createQueryWrapper(), + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect( + result.current.data?.find((g) => g.id === groupId)?.member_count, + ).toBe(2); + }); + + it("returns an empty array for a user with no groups", async () => { + const userId = await signInAsTestUser(); + + const { result } = renderHook(() => useQuery(myGroupsQuery(userId)), { + wrapper: createQueryWrapper(), + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + expect(result.current.data).toEqual([]); + }); +}); diff --git a/src/api/groups/useMyGroups.ts b/src/api/groups/useMyGroups.ts new file mode 100644 index 000000000..ff790ce01 --- /dev/null +++ b/src/api/groups/useMyGroups.ts @@ -0,0 +1,88 @@ +import { queryOptions } from "@tanstack/react-query"; +import { supabase } from "@/integrations/supabase/client"; +import type { Group } from "./types"; +import { groupsKeys } from "./types"; + +export function myGroupsQuery(userId: string) { + return queryOptions({ + queryKey: groupsKeys.myGroups(userId), + queryFn: () => fetchMyGroups(userId), + }); +} + +/** Groups the given user is a member of. `is_member` is always `true` — no admin check. */ +export async function fetchMyGroups(userId: string): Promise { + const userGroupIds = await getUserGroupIds(userId); + + if (userGroupIds.length === 0) { + return []; + } + + const { data: groupsData, error } = await supabase + .from("groups") + .select("*") + .eq("archived", false) + .in("id", userGroupIds) + .order("created_at", { ascending: false }); + + if (error) { + throw new Error(error.message || "Failed to fetch groups"); + } + + return attachGroupMeta(groupsData || [], userId, userGroupIds); +} + +export async function getUserGroupIds(userId: string): Promise { + const { data: userGroups, error } = await supabase + .from("group_members") + .select("group_id") + .eq("user_id", userId); + + if (error) { + console.error("Error fetching user groups:", error); + throw new Error("Failed to fetch user groups"); + } + + return userGroups?.map((ug) => ug.group_id) || []; +} + +export async function attachGroupMeta( + groups: Group[], + userId: string, + memberGroupIds: string[], +): Promise { + const memberCountsByGroupId = await fetchMemberCountsByGroupId( + groups.map((group) => group.id), + ); + + return groups.map((group) => ({ + ...group, + member_count: memberCountsByGroupId.get(group.id) || 0, + is_creator: group.created_by === userId, + is_member: memberGroupIds.includes(group.id), + })); +} + +async function fetchMemberCountsByGroupId( + groupIds: string[], +): Promise> { + const memberCountsByGroupId = new Map(); + + if (groupIds.length === 0) { + return memberCountsByGroupId; + } + + const { data: counts, error } = await supabase.rpc("group_member_counts", { + p_group_ids: groupIds, + }); + + if (error) { + throw new Error("Failed to fetch group member counts"); + } + + for (const row of counts || []) { + memberCountsByGroupId.set(row.group_id, row.member_count); + } + + return memberCountsByGroupId; +} diff --git a/src/api/groups/useUserGroups.ts b/src/api/groups/useUserGroups.ts deleted file mode 100644 index 632de3c67..000000000 --- a/src/api/groups/useUserGroups.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { queryOptions } from "@tanstack/react-query"; -import { supabase } from "@/integrations/supabase/client"; -import type { Group } from "./types"; -import { groupsKeys } from "./types"; - -// Check if user is admin -async function isUserAdmin(userId: string): Promise { - const { data: isAdminData, error: isAdminError } = await supabase - .from("admin_roles") - .select("id") - .eq("user_id", userId) - .limit(1); - - if (isAdminError) { - console.error("Error checking admin role:", isAdminError); - return false; - } - - return isAdminData && isAdminData.length > 0; -} - -// Get user's group IDs -async function getUserGroupIds(userId: string): Promise { - const { data: userGroups, error: userGroupsError } = await supabase - .from("group_members") - .select("group_id") - .eq("user_id", userId); - - if (userGroupsError) { - console.error("Error fetching user groups:", userGroupsError); - throw new Error("Failed to fetch user groups"); - } - - return userGroups?.map((ug) => ug.group_id) || []; -} - -// Helper function to add member counts to groups -async function addMemberCounts( - groups: Group[], - userId: string, - userGroupIds: string[], - isUserGroupsOnly: boolean = false, -): Promise { - const groupIds = groups.map((group) => group.id); - const memberCountsByGroupId = await fetchMemberCountsByGroupId(groupIds); - - return groups.map((group) => ({ - ...group, - member_count: memberCountsByGroupId.get(group.id) || 0, - is_creator: group.created_by === userId, - is_member: isUserGroupsOnly ? true : userGroupIds.includes(group.id), - })); -} - -async function fetchMemberCountsByGroupId( - groupIds: string[], -): Promise> { - const memberCountsByGroupId = new Map(); - - if (groupIds.length === 0) { - return memberCountsByGroupId; - } - - const { data: counts, error } = await supabase.rpc("group_member_counts", { - p_group_ids: groupIds, - }); - - if (error) { - throw new Error("Failed to fetch group member counts"); - } - - for (const row of counts || []) { - memberCountsByGroupId.set(row.group_id, row.member_count); - } - - return memberCountsByGroupId; -} - -// Fetch groups from database -async function fetchGroupsFromDb( - shouldFetchAll: boolean, - userGroupIds: string[], -) { - let groupsQuery = supabase - .from("groups") - .select("*") - .eq("archived", false) - .order("created_at", { ascending: false }); - - if (!shouldFetchAll) { - // Fetch only user's groups - if (userGroupIds.length === 0) { - return []; // User has no groups - } - groupsQuery = groupsQuery.in("id", userGroupIds); - } - - const { data: groupsData, error } = await groupsQuery; - - if (error) { - throw new Error(error.message || "Failed to fetch groups"); - } - - return groupsData || []; -} - -// Business logic function -async function fetchUserGroups( - userId: string, - { all = false }: { all?: boolean } = {}, -): Promise { - const userGroupIds = await getUserGroupIds(userId); - - let shouldFetchAllGroups = false; - if (all) { - const isAdmin = await isUserAdmin(userId); - shouldFetchAllGroups = isAdmin; - } - - const groupsData = await fetchGroupsFromDb( - shouldFetchAllGroups, - userGroupIds, - ); - - if (groupsData.length === 0) { - return []; - } - - return addMemberCounts( - groupsData, - userId, - userGroupIds, - !shouldFetchAllGroups, - ); -} - -export function userGroupsQuery( - userId: string, - params: { all?: boolean } = {}, -) { - return queryOptions({ - queryKey: groupsKeys.user(userId, params), - queryFn: () => fetchUserGroups(userId, params), - }); -} diff --git a/src/components/layout/AppHeader/GroupsIndicator.tsx b/src/components/layout/AppHeader/GroupsIndicator.tsx index 42348e6b6..e9a5ab7e0 100644 --- a/src/components/layout/AppHeader/GroupsIndicator.tsx +++ b/src/components/layout/AppHeader/GroupsIndicator.tsx @@ -1,10 +1,10 @@ +import { useSuspenseQuery } from "@tanstack/react-query"; import { Suspense } from "react"; import { Link } from "@tanstack/react-router"; import { UserPlus } from "lucide-react"; -import { useSuspenseQuery } from "@tanstack/react-query"; import { Skeleton } from "@/components/ui/skeleton"; import { useAuth } from "@/contexts/AuthContext"; -import { userGroupsQuery } from "@/api/groups/useUserGroups"; +import { myGroupsQuery } from "@/api/groups/useMyGroups"; import { cn } from "@/lib/utils"; import { TooltipButton } from "./TooltipButton"; import { ActiveGroupSwitcher } from "./GroupSwitcher/ActiveGroupSwitcher"; @@ -42,7 +42,7 @@ function GroupsIndicatorContent({ isMobile: boolean; userId: string; }) { - const { data: groups } = useSuspenseQuery(userGroupsQuery(userId)); + const { data: groups } = useSuspenseQuery(myGroupsQuery(userId)); if (groups.length === 0) { return ( diff --git a/src/contexts/ActiveScopeContext.tsx b/src/contexts/ActiveScopeContext.tsx index 418535afa..7d87264b6 100644 --- a/src/contexts/ActiveScopeContext.tsx +++ b/src/contexts/ActiveScopeContext.tsx @@ -2,7 +2,7 @@ import { createContext, useContext, useMemo, useState } from "react"; import type { ReactNode } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "@/contexts/AuthContext"; -import { userGroupsQuery } from "@/api/groups/useUserGroups"; +import { myGroupsQuery } from "@/api/groups/useMyGroups"; import { resolveActiveGroupId, resolvePinnedScope } from "@/lib/activeGroup"; import type { PinnedScope } from "@/lib/activeGroup"; @@ -66,7 +66,7 @@ function AuthedActiveScopeProvider({ children: ReactNode; }) { const { profile } = useAuth(); - const { data: groups = [] } = useQuery(userGroupsQuery(userId)); + const { data: groups = [] } = useQuery(myGroupsQuery(userId)); const [override, setOverride] = useState(null); const groupIds = useMemo(() => groups.map((group) => group.id), [groups]); diff --git a/src/pages/EditionView/tabs/VoteTab/AuthedFilteredSetsPanel.tsx b/src/pages/EditionView/tabs/VoteTab/AuthedFilteredSetsPanel.tsx index 579664a08..7129cc194 100644 --- a/src/pages/EditionView/tabs/VoteTab/AuthedFilteredSetsPanel.tsx +++ b/src/pages/EditionView/tabs/VoteTab/AuthedFilteredSetsPanel.tsx @@ -1,10 +1,10 @@ -import { useState } from "react"; import { useSuspenseQuery } from "@tanstack/react-query"; +import { useState } from "react"; import { FilterSortControls } from "@/pages/EditionView/tabs/VoteTab/filters/FilterSortControls"; import { GroupScopedSetsPanel } from "@/pages/EditionView/tabs/VoteTab/GroupScopedSetsPanel"; import { EveryoneSetsPanel } from "@/pages/EditionView/tabs/VoteTab/SetsPanelContent"; import { useActiveScope } from "@/contexts/ActiveScopeContext"; -import { userGroupsQuery } from "@/api/groups/useUserGroups"; +import { myGroupsQuery } from "@/api/groups/useMyGroups"; import type { FilteredSetsPanelProps } from "@/pages/EditionView/tabs/VoteTab/FilteredSetsPanel"; import type { BinaryVoteScope, VoteScope } from "@/lib/voteScope"; @@ -12,7 +12,7 @@ export function AuthedFilteredSetsPanel( props: FilteredSetsPanelProps & { userId: string }, ) { const { current, activeGroupId } = useActiveScope(); - const { data: groups } = useSuspenseQuery(userGroupsQuery(props.userId)); + const { data: groups } = useSuspenseQuery(myGroupsQuery(props.userId)); const perspectiveGroupId = current.kind === "group" ? current.groupId : activeGroupId; const perspectiveGroupName = perspectiveGroupId diff --git a/src/pages/SetDetails/SetGroupVoting.tsx b/src/pages/SetDetails/SetGroupVoting.tsx index ce561382f..c519d97cb 100644 --- a/src/pages/SetDetails/SetGroupVoting.tsx +++ b/src/pages/SetDetails/SetGroupVoting.tsx @@ -3,7 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { useAuth } from "@/contexts/AuthContext"; import { useActiveScope } from "@/contexts/ActiveScopeContext"; -import { userGroupsQuery } from "@/api/groups/useUserGroups"; +import { myGroupsQuery } from "@/api/groups/useMyGroups"; import { useGroupVotesQuery } from "@/api/voting/useGroupVotes"; import { Users } from "lucide-react"; import { VOTE_CONFIG, VOTES_TYPES, getVoteConfig } from "@/lib/votes/config"; @@ -31,7 +31,7 @@ function SetGroupVotingContent({ artistId: string; userId: string; }) { - const { data: groups } = useSuspenseQuery(userGroupsQuery(userId)); + const { data: groups } = useSuspenseQuery(myGroupsQuery(userId)); const { current } = useActiveScope(); const activeGroupId = current.kind === "group" ? current.groupId : undefined; diff --git a/src/pages/Settings/SettingsPage.tsx b/src/pages/Settings/SettingsPage.tsx index 244be0684..733e710a6 100644 --- a/src/pages/Settings/SettingsPage.tsx +++ b/src/pages/Settings/SettingsPage.tsx @@ -3,7 +3,7 @@ import { Link } from "@tanstack/react-router"; import { useSuspenseQuery } from "@tanstack/react-query"; import { TopBar } from "@/components/layout/TopBar"; import { useAuth } from "@/contexts/AuthContext"; -import { userGroupsQuery } from "@/api/groups/useUserGroups"; +import { myGroupsQuery } from "@/api/groups/useMyGroups"; import { profileQuery } from "@/api/auth/useProfile"; import { SignInRequired } from "@/pages/groups/Groups/SignInRequired"; import { ActiveGroupSetting } from "./ActiveGroupSetting"; @@ -33,7 +33,7 @@ export function SettingsPage() { } function SettingsContent({ userId }: { userId: string }) { - const { data: groups } = useSuspenseQuery(userGroupsQuery(userId)); + const { data: groups } = useSuspenseQuery(myGroupsQuery(userId)); const { data: profile } = useSuspenseQuery(profileQuery(userId)); const hasGroups = groups.length > 0; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index f792ef160..79de18e23 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -26,7 +26,7 @@ import { z } from "zod"; import type { QueryClient } from "@tanstack/react-query"; import type { User } from "@supabase/supabase-js"; import { supabase } from "@/integrations/supabase/client"; -import { userGroupsQuery } from "@/api/groups/useUserGroups"; +import { myGroupsQuery } from "@/api/groups/useMyGroups"; import { pageMeta } from "@/lib/pageHead"; import { useClearStaticTags } from "@/hooks/useClearStaticTags"; @@ -69,9 +69,7 @@ export const Route = createRootRouteWithContext()({ }, loader: async ({ context }) => { if (context.user) { - void context.queryClient.ensureQueryData( - userGroupsQuery(context.user.id, { all: false }), - ); + void context.queryClient.ensureQueryData(myGroupsQuery(context.user.id)); } }, }); diff --git a/src/routes/groups/index.tsx b/src/routes/groups/index.tsx index 92e8b3578..33f1c4b5d 100644 --- a/src/routes/groups/index.tsx +++ b/src/routes/groups/index.tsx @@ -1,9 +1,10 @@ +import { useSuspenseQuery } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; -import { userGroupsQuery } from "@/api/groups/useUserGroups"; +import { myGroupsQuery } from "@/api/groups/useMyGroups"; +import { allGroupsQuery } from "@/api/groups/useAllGroups"; import { Suspense, useState } from "react"; import { useNavigate, useRouteContext } from "@tanstack/react-router"; import type { User } from "@supabase/supabase-js"; -import { useSuspenseQuery } from "@tanstack/react-query"; import { useUserPermissionsQuery } from "@/api/auth/useUserPermissions"; import { useDeleteGroupMutation } from "@/api/groups/useDeleteGroup"; import { DeleteGroupDialog } from "@/pages/groups/Groups/DeleteGroupDialog"; @@ -21,9 +22,7 @@ export const Route = createFileRoute("/groups/")({ }), loader: async ({ context }) => { if (context.user) { - void context.queryClient.ensureQueryData( - userGroupsQuery(context.user.id, { all: false }), - ); + void context.queryClient.ensureQueryData(myGroupsQuery(context.user.id)); } }, }); @@ -42,9 +41,9 @@ function GroupsContent({ user }: { user: User }) { const navigate = useNavigate(); const [showAllGroups, setShowAllGroups] = useState(false); - const { data: isAdmin = false } = useUserPermissionsQuery( + const { data: isSuperAdmin = false } = useUserPermissionsQuery( user.id, - "is_admin", + "is_super_admin", ); const deleteGroupMutation = useDeleteGroupMutation(); const [createDialogOpen, setCreateDialogOpen] = useState(false); @@ -83,7 +82,7 @@ function GroupsContent({ user }: { user: User }) {
setCreateDialogOpen(true)} /> - {isAdmin && ( + {isSuperAdmin && (