From b09ccc542e3ca84868139a705e8f375729f90556 Mon Sep 17 00:00:00 2001 From: Sean Date: Sun, 25 Jan 2026 20:20:48 -0500 Subject: [PATCH] fixed login session for user name, and updated the api calls on the product detials pages. --- app/(app)/(builder)/builder/page.tsx | 45 ++++++++++++++++++- .../[partRole]/[productSlug]/page.tsx | 14 ++++-- app/api/builds/public/[id]/route.ts | 35 +++++++++++++++ app/api/builds/public/route.ts | 32 +++++++++++++ app/api/catalog/products/[id]/offers/route.ts | 35 +++++++++++++++ app/api/catalog/products/[id]/route.ts | 35 +++++++++++++++ app/api/catalog/products/route.ts | 32 +++++++++++++ app/builds/[buildId]/page.tsx | 5 ++- app/builds/page.tsx | 2 +- components/parts/PartsListPageClient.tsx | 7 ++- components/parts/ProductDetailPageClient.tsx | 7 +-- context/AuthContext.tsx | 14 ++++-- lib/catalog.ts | 14 ++++-- 13 files changed, 256 insertions(+), 21 deletions(-) create mode 100644 app/api/builds/public/[id]/route.ts create mode 100644 app/api/builds/public/route.ts create mode 100644 app/api/catalog/products/[id]/offers/route.ts create mode 100644 app/api/catalog/products/[id]/route.ts create mode 100644 app/api/catalog/products/route.ts diff --git a/app/(app)/(builder)/builder/page.tsx b/app/(app)/(builder)/builder/page.tsx index fb2f84f..989d2d2 100644 --- a/app/(app)/(builder)/builder/page.tsx +++ b/app/(app)/(builder)/builder/page.tsx @@ -1470,6 +1470,11 @@ function GunbuilderPageContent() { if (groupCategories.length === 0) return null; + // Check if there are locked/conflicting parts in this group + const hasConflicts = tableRows.some( + (row) => row.kind === "category" && row.locked + ); + return (
+ + {hasConflicts && ( +
+
+ +
+

+ Part Conflict Detected +

+

+ Some parts below are disabled because you've selected a complete assembly that already includes them. To customize individual parts, remove the complete assembly first. +

+
+
+
+ )} +
@@ -1635,8 +1657,27 @@ function GunbuilderPageContent() {
{isLocked ? ( -
- — +
+ + ⚠ Disabled + +
+ +
+

+ Part Conflict +

+

+ You've selected a complete assembly which already includes this part. Remove the complete assembly to select individual parts. +

+
+
) : selectedPart ? ( <> diff --git a/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx b/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx index 7e33915..471f743 100644 --- a/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx +++ b/app/(app)/(builder)/parts/p/[platform]/[partRole]/[productSlug]/page.tsx @@ -238,8 +238,11 @@ export default function ProductDetailsPage() { setLoading(true); setError(null); - const url = `${API_BASE_URL}/api/v1/products/${numericId}`; - const res = await fetch(url, { signal: controller.signal }); + const url = `/api/catalog/products/${numericId}`; + const res = await fetch(url, { + signal: controller.signal, + credentials: 'include', + }); if (!res.ok) { throw new Error(`Failed to load product (${res.status})`); @@ -272,8 +275,11 @@ export default function ProductDetailsPage() { try { setOffersLoading(true); - const url = `${API_BASE_URL}/api/v1/products/${numericId}/offers`; - const res = await fetch(url, { signal: controller.signal }); + const url = `/api/catalog/products/${numericId}/offers`; + const res = await fetch(url, { + signal: controller.signal, + credentials: 'include', + }); if (!res.ok) { throw new Error(`Failed to load offers (${res.status})`); diff --git a/app/api/builds/public/[id]/route.ts b/app/api/builds/public/[id]/route.ts new file mode 100644 index 0000000..0fa87f1 --- /dev/null +++ b/app/api/builds/public/[id]/route.ts @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const { id } = params; + + // Public build detail - no auth required + const res = await fetch( + `${API_BASE_URL}/api/v1/builds/${id}`, + { + headers: { 'Content-Type': 'application/json' }, + cache: 'no-store', + } + ); + + if (!res.ok) { + return NextResponse.json( + { error: await res.text() }, + { status: res.status } + ); + } + + return NextResponse.json(await res.json()); + } catch (err: any) { + return NextResponse.json( + { error: err?.message || 'Failed to fetch build' }, + { status: 500 } + ); + } +} diff --git a/app/api/builds/public/route.ts b/app/api/builds/public/route.ts new file mode 100644 index 0000000..4d8e7df --- /dev/null +++ b/app/api/builds/public/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + + // Public builds endpoint - no auth required + const res = await fetch( + `${API_BASE_URL}/api/v1/builds?${searchParams}`, + { + headers: { 'Content-Type': 'application/json' }, + cache: 'no-store', + } + ); + + if (!res.ok) { + return NextResponse.json( + { error: await res.text() }, + { status: res.status } + ); + } + + return NextResponse.json(await res.json()); + } catch (err: any) { + return NextResponse.json( + { error: err?.message || 'Failed to fetch builds' }, + { status: 500 } + ); + } +} diff --git a/app/api/catalog/products/[id]/offers/route.ts b/app/api/catalog/products/[id]/offers/route.ts new file mode 100644 index 0000000..8f61600 --- /dev/null +++ b/app/api/catalog/products/[id]/offers/route.ts @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const { id } = params; + + // No auth required for public catalog + const res = await fetch( + `${API_BASE_URL}/api/v1/products/${id}/offers`, + { + headers: { 'Content-Type': 'application/json' }, + cache: 'no-store', + } + ); + + if (!res.ok) { + return NextResponse.json( + { error: await res.text() }, + { status: res.status } + ); + } + + return NextResponse.json(await res.json()); + } catch (err: any) { + return NextResponse.json( + { error: err?.message || 'Failed to fetch product offers' }, + { status: 500 } + ); + } +} diff --git a/app/api/catalog/products/[id]/route.ts b/app/api/catalog/products/[id]/route.ts new file mode 100644 index 0000000..b8992e9 --- /dev/null +++ b/app/api/catalog/products/[id]/route.ts @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const { id } = params; + + // No auth required for public catalog + const res = await fetch( + `${API_BASE_URL}/api/v1/products/${id}`, + { + headers: { 'Content-Type': 'application/json' }, + cache: 'no-store', + } + ); + + if (!res.ok) { + return NextResponse.json( + { error: await res.text() }, + { status: res.status } + ); + } + + return NextResponse.json(await res.json()); + } catch (err: any) { + return NextResponse.json( + { error: err?.message || 'Failed to fetch product' }, + { status: 500 } + ); + } +} diff --git a/app/api/catalog/products/route.ts b/app/api/catalog/products/route.ts new file mode 100644 index 0000000..9013beb --- /dev/null +++ b/app/api/catalog/products/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server'; + +const API_BASE_URL = process.env.API_BASE_URL || process.env.NEXT_PUBLIC_API_BASE_URL; + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + + // No auth required for public catalog + const res = await fetch( + `${API_BASE_URL}/api/v1/products?${searchParams}`, + { + headers: { 'Content-Type': 'application/json' }, + cache: 'no-store', + } + ); + + if (!res.ok) { + return NextResponse.json( + { error: await res.text() }, + { status: res.status } + ); + } + + return NextResponse.json(await res.json()); + } catch (err: any) { + return NextResponse.json( + { error: err?.message || 'Failed to fetch products' }, + { status: 500 } + ); + } +} diff --git a/app/builds/[buildId]/page.tsx b/app/builds/[buildId]/page.tsx index 1dd06f9..8a24ed7 100644 --- a/app/builds/[buildId]/page.tsx +++ b/app/builds/[buildId]/page.tsx @@ -31,8 +31,9 @@ export default async function BuildBreakdownPage({ }: { params: { buildId: string }; }) { - // Public detail endpoint - const res = await fetch(`${API_BASE_URL}/api/v1/builds/${params.buildId}`, { + // Public detail endpoint - use Next.js API route + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'; + const res = await fetch(`${baseUrl}/api/builds/public/${params.buildId}`, { cache: "no-store", }); diff --git a/app/builds/page.tsx b/app/builds/page.tsx index 783407e..60488c9 100644 --- a/app/builds/page.tsx +++ b/app/builds/page.tsx @@ -132,7 +132,7 @@ export default function BuildsPage() { setLoading(true); setError(null); - const res = await fetch(`${API_BASE_URL}/api/v1/builds?limit=50`, { + const res = await fetch('/api/builds/public?limit=50', { method: "GET", credentials: "include", headers: { "Content-Type": "application/json" }, diff --git a/components/parts/PartsListPageClient.tsx b/components/parts/PartsListPageClient.tsx index ddc6773..de54735 100644 --- a/components/parts/PartsListPageClient.tsx +++ b/components/parts/PartsListPageClient.tsx @@ -50,9 +50,12 @@ export default function PartsListPageClient(props: { // Your current list endpoint: // GET /api/v1/products?platform=AR-15&partRoles=upper-receiver - const url = `${API_BASE_URL}/api/v1/products?platform=${encodeURIComponent(platform)}&partRoles=${encodeURIComponent(partRole)}`; + const url = `/api/catalog/products?platform=${encodeURIComponent(platform)}&partRoles=${encodeURIComponent(partRole)}`; - const res = await fetch(url, { headers: { Accept: "application/json" } }); + const res = await fetch(url, { + headers: { Accept: "application/json" }, + credentials: 'include', + }); if (!res.ok) { const txt = await res.text().catch(() => ""); throw new Error(`Failed to load products (${res.status}): ${txt}`); diff --git a/components/parts/ProductDetailPageClient.tsx b/components/parts/ProductDetailPageClient.tsx index 6b59a54..d99ed80 100644 --- a/components/parts/ProductDetailPageClient.tsx +++ b/components/parts/ProductDetailPageClient.tsx @@ -41,8 +41,9 @@ async function fetchProductDetail(params: { // ---- Preferred (if you add it): GET /api/v1/products/{id} // If it 404s, we fall back. try { - const res = await fetch(`${API_BASE_URL}/api/v1/products/${encodeURIComponent(id)}`, { + const res = await fetch(`/api/catalog/products/${encodeURIComponent(id)}`, { headers: { Accept: "application/json" }, + credentials: 'include', }); if (res.ok) { @@ -54,8 +55,8 @@ async function fetchProductDetail(params: { // ---- Fallback: use list endpoint + find by id (works right now with your current API) const listRes = await fetch( - `${API_BASE_URL}/api/v1/products?platform=${encodeURIComponent(platform)}&partRoles=${encodeURIComponent(partRole)}`, - { headers: { Accept: "application/json" } } + `/api/catalog/products?platform=${encodeURIComponent(platform)}&partRoles=${encodeURIComponent(partRole)}`, + { headers: { Accept: "application/json" }, credentials: 'include' } ); if (!listRes.ok) { diff --git a/context/AuthContext.tsx b/context/AuthContext.tsx index 0a9f41b..ca9842c 100644 --- a/context/AuthContext.tsx +++ b/context/AuthContext.tsx @@ -75,7 +75,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { } // Verify session with server - fetch("/api/auth/me") + fetch("/api/auth/me", { credentials: 'include' }) .then((res) => { if (res.ok) { return res.json(); @@ -104,7 +104,10 @@ export function AuthProvider({ children }: { children: ReactNode }) { }, [persistUser]); const refreshUser = useCallback(async () => { - const res = await fetch("/api/auth/me", { cache: "no-store" }); + const res = await fetch("/api/auth/me", { + cache: "no-store", + credentials: 'include', + }); if (!res.ok) { const text = await res.text().catch(() => res.statusText); @@ -133,6 +136,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { const res = await fetch("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, + credentials: 'include', body: JSON.stringify({ email, password }), }); @@ -167,6 +171,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { const res = await fetch("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, + credentials: 'include', body: JSON.stringify({ email, password, @@ -199,7 +204,10 @@ export function AuthProvider({ children }: { children: ReactNode }) { persistUser(null); // Clear server session cookies - fetch("/api/auth/logout", { method: "POST" }).catch(() => {}); + fetch("/api/auth/logout", { + method: "POST", + credentials: 'include', + }).catch(() => {}); }, [persistUser]); const value: AuthContextValue = { diff --git a/lib/catalog.ts b/lib/catalog.ts index 4b0e451..3b74c97 100644 --- a/lib/catalog.ts +++ b/lib/catalog.ts @@ -58,9 +58,12 @@ export async function fetchProducts(params: { if (sort) sp.set("sort", sort); - const url = `${API_BASE_URL}/api/v1/products?${sp.toString()}`; + const url = `/api/catalog/products?${sp.toString()}`; - const res = await fetch(url, { cache: "no-store" }); + const res = await fetch(url, { + cache: "no-store", + credentials: 'include', + }); if (!res.ok) throw new Error(`Failed to load products (${res.status})`); const data = (await res.json()) as ProductListItem[]; @@ -83,10 +86,13 @@ export async function fetchProductById(params: { const { id, platform } = params; const url = - `${API_BASE_URL}/api/v1/products/${encodeURIComponent(id)}` + + `/api/catalog/products/${encodeURIComponent(id)}` + (platform ? `?${new URLSearchParams({ platform })}` : ""); - const res = await fetch(url, { cache: "no-store" }); + const res = await fetch(url, { + cache: "no-store", + credentials: 'include', + }); if (!res.ok) throw new Error(`Failed to load product (${res.status})`); return (await res.json()) as ProductDetail; } \ No newline at end of file