|
| 1 | +import { notFound, redirect } from "next/navigation"; |
| 2 | +import { createClient } from "@/lib/supabase/server"; |
| 3 | +import { MembersPage } from "@/components/members/members-page"; |
| 4 | +import type { MemberWithProfile, WorkspaceInviteWithInviter } from "@/lib/types"; |
| 5 | + |
| 6 | +export default async function WorkspaceMembersPage({ |
| 7 | + params, |
| 8 | +}: { |
| 9 | + params: Promise<{ workspaceSlug: string }>; |
| 10 | +}) { |
| 11 | + const { workspaceSlug } = await params; |
| 12 | + const supabase = await createClient(); |
| 13 | + |
| 14 | + const { |
| 15 | + data: { user }, |
| 16 | + } = await supabase.auth.getUser(); |
| 17 | + |
| 18 | + if (!user) { |
| 19 | + redirect("/sign-in"); |
| 20 | + } |
| 21 | + |
| 22 | + const { data: workspace } = await supabase |
| 23 | + .from("workspaces") |
| 24 | + .select("*") |
| 25 | + .eq("slug", workspaceSlug) |
| 26 | + .maybeSingle(); |
| 27 | + |
| 28 | + if (!workspace) { |
| 29 | + notFound(); |
| 30 | + } |
| 31 | + |
| 32 | + // Fetch current user's membership to determine their role |
| 33 | + const { data: currentMember } = await supabase |
| 34 | + .from("members") |
| 35 | + .select("id, role") |
| 36 | + .eq("workspace_id", workspace.id) |
| 37 | + .eq("user_id", user.id) |
| 38 | + .maybeSingle(); |
| 39 | + |
| 40 | + if (!currentMember) { |
| 41 | + notFound(); |
| 42 | + } |
| 43 | + |
| 44 | + // Fetch all members with profile info |
| 45 | + const { data: membersRaw } = await supabase |
| 46 | + .from("members") |
| 47 | + .select("*, profiles(email, display_name, avatar_url)") |
| 48 | + .eq("workspace_id", workspace.id) |
| 49 | + .order("created_at", { ascending: true }); |
| 50 | + |
| 51 | + // Supabase join returns the relation as an opaque type; cast is unavoidable |
| 52 | + const members = (membersRaw ?? []) as unknown as MemberWithProfile[]; |
| 53 | + |
| 54 | + // Fetch pending invites (only visible to admins/owners) |
| 55 | + const isAdmin = currentMember.role === "owner" || currentMember.role === "admin"; |
| 56 | + let pendingInvites: WorkspaceInviteWithInviter[] = []; |
| 57 | + |
| 58 | + if (isAdmin) { |
| 59 | + const { data: invites } = await supabase |
| 60 | + .from("workspace_invites") |
| 61 | + .select("*, profiles:invited_by(display_name)") |
| 62 | + .eq("workspace_id", workspace.id) |
| 63 | + .is("accepted_at", null) |
| 64 | + .order("created_at", { ascending: false }); |
| 65 | + |
| 66 | + // Supabase join returns the relation as an opaque type; cast is unavoidable |
| 67 | + pendingInvites = (invites ?? []) as unknown as WorkspaceInviteWithInviter[]; |
| 68 | + } |
| 69 | + |
| 70 | + return ( |
| 71 | + <div className="mx-auto max-w-xl p-6"> |
| 72 | + <h1 className="text-2xl font-semibold">Members</h1> |
| 73 | + <p className="mt-1 text-sm text-muted-foreground"> |
| 74 | + Manage who has access to this workspace. |
| 75 | + </p> |
| 76 | + <div className="mt-6"> |
| 77 | + <MembersPage |
| 78 | + workspace={workspace} |
| 79 | + members={members} |
| 80 | + pendingInvites={pendingInvites} |
| 81 | + currentUserId={user.id} |
| 82 | + currentUserRole={currentMember.role} |
| 83 | + /> |
| 84 | + </div> |
| 85 | + </div> |
| 86 | + ); |
| 87 | +} |
0 commit comments