diff --git a/app/(protected)/saved/page.tsx b/app/(protected)/saved/page.tsx index 86b186e..9855c0f 100644 --- a/app/(protected)/saved/page.tsx +++ b/app/(protected)/saved/page.tsx @@ -1,8 +1,9 @@ +// app/(protected)/saved/page.tsx "use client"; import { useState, useEffect } from "react"; import { useUser } from "@clerk/nextjs"; -import { Heart, Loader2 } from "lucide-react"; +import { Bookmark, Loader2 } from "lucide-react"; import Link from "next/link"; import { ListingCard } from "@/app/marketplace/_components/ListingCard"; import { MarketplaceFeedItem } from "@/types/marketplace"; @@ -16,15 +17,12 @@ interface SavedListingItem { export default function SavedListingsPage() { const { user, isLoaded } = useUser(); - const [mounted] = useState(false); + const [mounted] = useState(() => typeof window !== "undefined"); const [savedListings, setSavedListings] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - useEffect(() => { - if (!mounted) return; - if (!isLoaded || !user) { setLoading(false); return; @@ -48,9 +46,9 @@ export default function SavedListingsPage() { } fetchSavedListings(); - }, [user, isLoaded, mounted]); + }, [user, isLoaded]); - // 1. Prevent hydration mismatch (FIRST CHECK) + // 1. Prevent hydration mismatch - show loader during SSR if (!mounted) { return (
@@ -72,7 +70,7 @@ export default function SavedListingsPage() { if (!user) { return (
- +

Sign in to view saved listings

@@ -119,7 +117,7 @@ export default function SavedListingsPage() { if (savedListings.length === 0) { return (
- +

No saved listings

Start saving listings you're interested in to keep track of them @@ -139,7 +137,7 @@ export default function SavedListingsPage() {

- + Saved Listings

diff --git a/app/api/marketplace/saved/route.ts b/app/api/marketplace/saved/route.ts index 906a0eb..2a6f8b4 100644 --- a/app/api/marketplace/saved/route.ts +++ b/app/api/marketplace/saved/route.ts @@ -1,193 +1,193 @@ -// app/api/marketplace/saved/route.ts -import { NextRequest, NextResponse } from "next/server"; -import { prisma } from "@/lib/prisma"; -import { getCurrentUser } from "@/lib/server/admin-guard"; - -// Type for Prisma error with code -interface PrismaError { - code?: string; - message?: string; -} - -// GET /api/marketplace/saved - Get all saved listings for current user -export async function GET() { - try { - const user = await getCurrentUser(); - - if (!user) { - return NextResponse.json( - { error: "Unauthorized. Please sign in." }, - { status: 401 } - ); + // app/api/marketplace/saved/route.ts + import { NextRequest, NextResponse } from "next/server"; + import { prisma } from "@/lib/prisma"; + import { getCurrentUser } from "@/lib/server/admin-guard"; + + // Type for Prisma error with code + interface PrismaError { + code?: string; + message?: string; } - const savedListings = await prisma.savedListing.findMany({ - where: { userId: user.id }, - include: { - listing: { - include: { - land: { - select: { - size: true, - landType: true, - village: true, - district: true, - state: true, - }, - }, - images: { - where: { isPrimary: true }, - take: 1, + // GET /api/marketplace/saved - Get all saved listings for current user + export async function GET() { + try { + const user = await getCurrentUser(); + + if (!user) { + return NextResponse.json( + { error: "Unauthorized. Please sign in." }, + { status: 401 } + ); + } + + const savedListings = await prisma.savedListing.findMany({ + where: { userId: user.id }, + include: { + listing: { + include: { + land: { + select: { + size: true, + landType: true, + village: true, + district: true, + state: true, + }, + }, + images: { + where: { isPrimary: true }, + take: 1, + }, + _count: { + select: { + bids: true, + }, + }, }, - _count: { - select: { - bids: true, - }, }, - }, }, - }, - orderBy: { - createdAt: "desc", - }, - }); - - return NextResponse.json({ - saved: savedListings, - count: savedListings.length, - }); - } catch (error) { - console.error("[Saved API] GET error:", error); - return NextResponse.json( - { error: "Failed to fetch saved listings" }, - { status: 500 } - ); - } -} - -// POST /api/marketplace/saved - Save a listing -export async function POST(req: NextRequest) { - try { - const user = await getCurrentUser(); - - if (!user) { - return NextResponse.json( - { error: "Unauthorized. Please sign in." }, - { status: 401 } - ); - } - - const body = await req.json(); - const { listingId } = body; + orderBy: { + createdAt: "desc", + }, + }); - if (!listingId) { - return NextResponse.json( - { error: "Listing ID is required" }, - { status: 400 } - ); + return NextResponse.json({ + saved: savedListings, + count: savedListings.length, + }); + } catch (error) { + console.error("[Saved API] GET error:", error); + return NextResponse.json( + { error: "Failed to fetch saved listings" }, + { status: 500 } + ); } - - // Check if listing exists - const listing = await prisma.landListing.findUnique({ - where: { id: listingId }, - select: { id: true }, - }); - - if (!listing) { - return NextResponse.json( - { error: "Listing not found" }, - { status: 404 } - ); } - // Try to create saved listing + // POST /api/marketplace/saved - Save a listing + export async function POST(req: NextRequest) { try { - const saved = await prisma.savedListing.create({ - data: { - listingId, - userId: user.id, - }, - }); + const user = await getCurrentUser(); + + if (!user) { + return NextResponse.json( + { error: "Unauthorized. Please sign in." }, + { status: 401 } + ); + } + + const body = await req.json(); + const { listingId } = body; + + if (!listingId) { + return NextResponse.json( + { error: "Listing ID is required" }, + { status: 400 } + ); + } + + // Check if listing exists + const listing = await prisma.landListing.findUnique({ + where: { id: listingId }, + select: { id: true }, + }); + + if (!listing) { + return NextResponse.json( + { error: "Listing not found" }, + { status: 404 } + ); + } + + // Try to create saved listing + try { + const saved = await prisma.savedListing.create({ + data: { + listingId, + userId: user.id, + }, + }); - return NextResponse.json({ - success: true, - saved, - message: "Listing saved successfully", - }); - } catch (error) { - const prismaError = error as PrismaError; - // P2002 is Prisma's "unique constraint violation" error - if (prismaError.code === "P2002") { - // Already saved - this is fine, return success return NextResponse.json({ - success: true, - message: "Listing already saved", + success: true, + saved, + message: "Listing saved successfully", }); - } - throw error; - } - } catch (error) { - console.error("[Saved API] POST error:", error); - return NextResponse.json( - { error: "Failed to save listing" }, - { status: 500 } - ); - } -} - -// DELETE /api/marketplace/saved - Remove a saved listing -export async function DELETE(req: NextRequest) { - try { - const user = await getCurrentUser(); - - if (!user) { - return NextResponse.json( - { error: "Unauthorized. Please sign in." }, - { status: 401 } - ); + } catch (error) { + const prismaError = error as PrismaError; + // P2002 is Prisma's "unique constraint violation" error + if (prismaError.code === "P2002") { + // Already saved - this is fine, return success + return NextResponse.json({ + success: true, + message: "Listing already saved", + }); + } + throw error; + } + } catch (error) { + console.error("[Saved API] POST error:", error); + return NextResponse.json( + { error: "Failed to save listing" }, + { status: 500 } + ); } - - const body = await req.json(); - const { listingId } = body; - - if (!listingId) { - return NextResponse.json( - { error: "Listing ID is required" }, - { status: 400 } - ); } + // DELETE /api/marketplace/saved - Remove a saved listing + export async function DELETE(req: NextRequest) { try { - await prisma.savedListing.delete({ - where: { - listingId_userId: { - listingId, - userId: user.id, - }, - }, - }); + const user = await getCurrentUser(); + + if (!user) { + return NextResponse.json( + { error: "Unauthorized. Please sign in." }, + { status: 401 } + ); + } + + const body = await req.json(); + const { listingId } = body; + + if (!listingId) { + return NextResponse.json( + { error: "Listing ID is required" }, + { status: 400 } + ); + } + + try { + await prisma.savedListing.delete({ + where: { + listingId_userId: { + listingId, + userId: user.id, + }, + }, + }); - return NextResponse.json({ - success: true, - message: "Listing removed from saved", - }); - } catch (error) { - const prismaError = error as PrismaError; - // P2025 is Prisma's "record not found" error - if (prismaError.code === "P2025") { return NextResponse.json({ - success: true, - message: "Listing was not saved", + success: true, + message: "Listing removed from saved", }); - } - throw error; + } catch (error) { + const prismaError = error as PrismaError; + // P2025 is Prisma's "record not found" error + if (prismaError.code === "P2025") { + return NextResponse.json({ + success: true, + message: "Listing was not saved", + }); + } + throw error; + } + } catch (error) { + console.error("[Saved API] DELETE error:", error); + return NextResponse.json( + { error: "Failed to remove saved listing" }, + { status: 500 } + ); + } } - } catch (error) { - console.error("[Saved API] DELETE error:", error); - return NextResponse.json( - { error: "Failed to remove saved listing" }, - { status: 500 } - ); - } -}