diff --git a/src/app/api/user/invitations/route.ts b/src/app/api/user/invitations/route.ts index f82ee0b..146ccc2 100644 --- a/src/app/api/user/invitations/route.ts +++ b/src/app/api/user/invitations/route.ts @@ -1,5 +1,6 @@ import { db } from "@/app/db/db"; import { NextRequest, NextResponse } from "next/server"; +import { revalidatePath } from "next/cache"; import { portfolio, portfolioInvitations, @@ -166,6 +167,11 @@ export async function POST(req: NextRequest) { newMembership: plan.memberships.length > 0, }); + // /home renders the user's portfolio list from the DB in a server + // component; invalidate so the next navigation there picks up the new + // membership. router.refresh() handles the in-place case client-side. + revalidatePath("/home"); + return NextResponse.json( { success: true, diff --git a/src/app/components/ProfileDropDown.tsx b/src/app/components/ProfileDropDown.tsx index 79fc9e8..24764ad 100644 --- a/src/app/components/ProfileDropDown.tsx +++ b/src/app/components/ProfileDropDown.tsx @@ -1,11 +1,22 @@ "use client"; +import { useState } from "react"; import { Menu } from "@headlessui/react"; import { signOut, useSession } from "next-auth/react"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import Image from "next/image"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useToast } from "@/app/hooks/use-toast"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/app/shadcn_components/ui/dialog"; +import { Button } from "@/app/shadcn_components/ui/button"; type PendingInvitation = { invitationId: string; @@ -49,6 +60,11 @@ function ProfileDropDown({ userImage }: { userImage: string }) { const queryClient = useQueryClient(); const { toast } = useToast(); + const router = useRouter(); + const [acceptedInfo, setAcceptedInfo] = useState<{ + portfolioId: string; + portfolioName: string; + } | null>(null); const { data: invitations = [], isLoading } = useQuery({ queryKey: INVITATIONS_KEY, @@ -87,16 +103,30 @@ function ProfileDropDown({ userImage }: { userImage: string }) { variant: "destructive", }); }, - onSuccess: (_data, vars) => { + onSuccess: (data, vars) => { const inv = invitations.find((i) => i.invitationId === vars.invitationId); - const portfolioLabel = inv ? `the ${inv.portfolioName} portfolio` : "the portfolio"; - toast({ - title: vars.action === "accept" ? "Joined portfolio" : "Invitation declined", - description: - vars.action === "accept" - ? `You now have access to ${portfolioLabel}.` - : `You've declined the invitation to ${portfolioLabel}.`, - }); + if (vars.action === "accept") { + const portfolioId = data?.portfolioId ?? inv?.portfolioId; + const portfolioName = inv?.portfolioName ?? "the portfolio"; + // /home's server-rendered portfolio list won't pick up the new + // membership without an explicit refresh; the API handler also calls + // revalidatePath("/home") so any later navigation is fresh too. + router.refresh(); + if (portfolioId) { + setAcceptedInfo({ portfolioId, portfolioName }); + } else { + toast({ + title: "Joined portfolio", + description: `You now have access to ${portfolioName}.`, + }); + } + } else { + const portfolioLabel = inv ? `the ${inv.portfolioName} portfolio` : "the portfolio"; + toast({ + title: "Invitation declined", + description: `You've declined the invitation to ${portfolioLabel}.`, + }); + } }, onSettled: () => { queryClient.invalidateQueries({ queryKey: INVITATIONS_KEY }); @@ -104,6 +134,7 @@ function ProfileDropDown({ userImage }: { userImage: string }) { }); return ( + <> {userImage ? ( @@ -242,6 +273,40 @@ function ProfileDropDown({ userImage }: { userImage: string }) { + + { + if (!open) setAcceptedInfo(null); + }} + > + + + + You've joined {acceptedInfo?.portfolioName} + + + You now have access. Head over when you're ready. + + + + + + + + + ); }