diff --git a/apps/web/app/(protected)/disputes/[id]/page.tsx b/apps/web/app/(protected)/disputes/[id]/page.tsx index ab82ee3..5e87bed 100644 --- a/apps/web/app/(protected)/disputes/[id]/page.tsx +++ b/apps/web/app/(protected)/disputes/[id]/page.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' -import { createClient } from '@/lib/superbase/client' +import { createClient } from '@/lib/supabase/client' import { useParams, useRouter } from 'next/navigation' import { ShieldAlert, @@ -32,6 +32,11 @@ export default function DisputeDetailPage() { async function fetchDispute() { try { setLoading(true) + if (!params?.id) { + setError('Dispute ID not found') + setLoading(false) + return + } const { data, error } = await supabase .from('disputes') .select(` @@ -42,7 +47,7 @@ export default function DisputeDetailPage() { listings (title) ) `) - .eq('id', params.id) + .eq('id', params.id as string) .single() if (error) throw error diff --git a/apps/web/app/(protected)/disputes/page.tsx b/apps/web/app/(protected)/disputes/page.tsx index 3cce6da..df3530d 100644 --- a/apps/web/app/(protected)/disputes/page.tsx +++ b/apps/web/app/(protected)/disputes/page.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' -import { createClient } from '@/lib/superbase/client' +import { createClient } from '@/lib/supabase/client' import { AlertCircle, CheckCircle2, diff --git a/apps/web/app/admin/disputes/[id]/page.tsx b/apps/web/app/admin/disputes/[id]/page.tsx index e340e1a..ce9e14e 100644 --- a/apps/web/app/admin/disputes/[id]/page.tsx +++ b/apps/web/app/admin/disputes/[id]/page.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' -import { createClient } from '@/lib/superbase/client' +import { createClient } from '@/lib/supabase/client' import { useParams, useRouter } from 'next/navigation' import { ShieldCheck, @@ -37,6 +37,7 @@ export default function AdminDisputeDetailPage() { async function fetchDispute() { try { setLoading(true) + if (!params?.id) throw new Error('Dispute ID not found') const { data, error } = await supabase .from('disputes') .select(` @@ -64,6 +65,7 @@ export default function AdminDisputeDetailPage() { const handleUpdate = async (status: string) => { setUpdating(true) try { + if (!params?.id) throw new Error('Dispute ID not found') const response = await fetch(`/api/disputes/${params.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, diff --git a/apps/web/app/admin/disputes/page.tsx b/apps/web/app/admin/disputes/page.tsx index 7417cfb..5d1c742 100644 --- a/apps/web/app/admin/disputes/page.tsx +++ b/apps/web/app/admin/disputes/page.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' -import { createClient } from '@/lib/superbase/client' +import { createClient } from '@/lib/supabase/client' import { ShieldCheck, AlertTriangle, diff --git a/apps/web/app/admin/errors/[id]/page.tsx b/apps/web/app/admin/errors/[id]/page.tsx index ab31feb..42727e6 100644 --- a/apps/web/app/admin/errors/[id]/page.tsx +++ b/apps/web/app/admin/errors/[id]/page.tsx @@ -7,6 +7,10 @@ export const dynamic = "force-dynamic"; export default async function ErrorDetailPage({ params }: { params: { id: string } }) { const supabase = await getServerClient(); + if (!supabase) { + notFound(); + } + // 1. Fetch the error group const { data: group, error: groupError } = await supabase .from("error_groups") diff --git a/apps/web/app/admin/errors/page.tsx b/apps/web/app/admin/errors/page.tsx index 97c477f..ed99d70 100644 --- a/apps/web/app/admin/errors/page.tsx +++ b/apps/web/app/admin/errors/page.tsx @@ -1,11 +1,16 @@ import { getServerClient } from "@/lib/supabase/server"; import Link from "next/link"; +import { notFound } from "next/navigation"; export const dynamic = "force-dynamic"; export default async function ErrorListPage() { const supabase = await getServerClient(); + if (!supabase) { + notFound(); + } + const { data: groups, error } = await supabase .from("error_groups") .select("*") diff --git a/apps/web/app/admin/errors/rules/page.tsx b/apps/web/app/admin/errors/rules/page.tsx index ab7b19b..5d3183d 100644 --- a/apps/web/app/admin/errors/rules/page.tsx +++ b/apps/web/app/admin/errors/rules/page.tsx @@ -1,11 +1,16 @@ import { getServerClient } from "@/lib/supabase/server"; import Link from "next/link"; +import { notFound } from "next/navigation"; export const dynamic = "force-dynamic"; export default async function AlertRulesPage() { const supabase = await getServerClient(); + if (!supabase) { + notFound(); + } + const { data: rules, error } = await supabase .from("alert_rules") .select("*") diff --git a/apps/web/app/api/admin/users/[id]/route.ts b/apps/web/app/api/admin/users/[id]/route.ts index 516fa4b..2cda63d 100644 --- a/apps/web/app/api/admin/users/[id]/route.ts +++ b/apps/web/app/api/admin/users/[id]/route.ts @@ -17,7 +17,7 @@ function buildUserDetail(user: AdminUser): AdminUserDetail { const activityLog: ActivityEvent[] = [ { id: `${user.id}-act-1`, - type: "auth", + type: "auth" as const, description: "User account created", created_at: user.created_at, }, diff --git a/apps/web/app/api/disputes/[id]/route.ts b/apps/web/app/api/disputes/[id]/route.ts index 430f778..ef4587b 100644 --- a/apps/web/app/api/disputes/[id]/route.ts +++ b/apps/web/app/api/disputes/[id]/route.ts @@ -1,5 +1,5 @@ -import { createClient } from '@/lib/superbase/server' -import { createAdminClient } from '@/lib/superbase/admin' +import { createClient } from '@/lib/supabase/server' +import { createAdminClient } from '@/lib/supabase/admin' import { NextResponse } from 'next/server' export async function GET( diff --git a/apps/web/app/api/disputes/route.ts b/apps/web/app/api/disputes/route.ts index 76be131..b4bbf09 100644 --- a/apps/web/app/api/disputes/route.ts +++ b/apps/web/app/api/disputes/route.ts @@ -1,4 +1,4 @@ -import { createClient } from '@/lib/superbase/server' +import { createClient } from '@/lib/supabase/server' import { NextResponse } from 'next/server' export async function POST(request: Request) { diff --git a/apps/web/app/api/ratings/route.ts b/apps/web/app/api/ratings/route.ts index 83c1795..4226d22 100644 --- a/apps/web/app/api/ratings/route.ts +++ b/apps/web/app/api/ratings/route.ts @@ -1,4 +1,4 @@ -import { createClient } from '@/lib/superbase/server' +import { createClient } from '@/lib/supabase/server' import { NextResponse } from 'next/server' import { validateSubmitRating } from '@/lib/validators/ratings' import { Rating } from '@/lib/db/types' diff --git a/apps/web/app/api/users/[id]/stats/route.ts b/apps/web/app/api/users/[id]/stats/route.ts index a55aeea..972ed41 100644 --- a/apps/web/app/api/users/[id]/stats/route.ts +++ b/apps/web/app/api/users/[id]/stats/route.ts @@ -1,6 +1,6 @@ -import { createClient } from '@/lib/superbase/server' +import { createClient } from '@/lib/supabase/server' import { errorResponse, successResponse } from '@/lib/api-utils' -import { fetchUserStatsCached } from '@/lib/queries/user' +import { fetchUserStatsCached } from '@/lib/queries/users' /** * GET /api/users/[id]/stats diff --git a/apps/web/app/api/users/profile/route.ts b/apps/web/app/api/users/profile/route.ts index ecd2a19..19dbef5 100644 --- a/apps/web/app/api/users/profile/route.ts +++ b/apps/web/app/api/users/profile/route.ts @@ -1,5 +1,5 @@ import { NextRequest } from "next/server"; -import { createClient } from "@/lib/superbase/server"; +import { createClient } from "@/lib/supabase/server"; import { successResponse, handleError } from "@/app/api/utils/response"; import { requireAuth } from "@/app/api/utils/auth"; diff --git a/apps/web/app/auth/register/page.tsx b/apps/web/app/auth/register/page.tsx index b3f15a9..be7f152 100644 --- a/apps/web/app/auth/register/page.tsx +++ b/apps/web/app/auth/register/page.tsx @@ -42,10 +42,27 @@ export const dynamic = "force-dynamic"; export default function RegisterPage() { const router = useRouter(); - const [serverError, setServerError] = useState(""); + const [error, setError] = useState(""); - const { isConnected, publicKey: walletPublicKey, connect, isInitializing } = - useWallet(); + let isConnected: boolean; + let walletPublicKey: string | null; + let connect: () => Promise; + let isWalletConnecting: boolean; + + try { + const wallet = useWallet(); + isConnected = wallet.isConnected; + walletPublicKey = wallet.publicKey; + connect = wallet.connect; + isWalletConnecting = wallet.isInitializing; + } catch { + isConnected = false; + walletPublicKey = null; + connect = async () => {}; + isWalletConnecting = false; + } + + const supabase = createClient(); const { register, @@ -55,7 +72,15 @@ export default function RegisterPage() { formState: { errors, isSubmitting }, } = useForm({ resolver: zodResolver(registerSchema), - mode: "onChange", + // FIX 1: Only validate on submit, not on every keystroke. + mode: "onSubmit", + // FIX 2: All fields start as empty strings, not undefined. + defaultValues: { + username: "", + email: "", + password: "", + walletAddress: "", + }, }); const walletAddress = watch("walletAddress"); @@ -74,40 +99,50 @@ export default function RegisterPage() { setServerError("Failed to connect wallet. Is Freighter installed?"); } }; + // FIX 3: shouldValidate: false so connecting a wallet doesn't trigger + useEffect(() => { + if (walletPublicKey) { + setValue("walletAddress", walletPublicKey, { shouldValidate: false }); + } + }, [walletPublicKey, setValue]); const onSubmit = async (data: RegisterFormData) => { - setServerError(""); - - const payload: Record = { - public_key: data.walletAddress, - username: data.username, - }; - if (data.email) payload.email = data.email; - + setError(""); try { - const res = await fetch("/api/auth/register", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload), + const { data: authData, error: authError } = await supabase.auth.signUp({ + email: data.email, + password: data.password, + options: { + data: { + username: data.username, + wallet_address: data.walletAddress, + }, + }, }); - const json = await res.json(); - - if (!res.ok) { - // Extract the human-readable error message from either response shape - const msg = - typeof json?.error === "string" - ? json.error - : (json?.error?.message ?? "Registration failed. Please try again."); - setServerError(msg); + if (authError) { + setError(authError.message); return; } - // Server set auth-token cookie → redirect straight to dashboard - router.push("/dashboard"); - router.refresh(); + if (authData.user) { + const { error: profileError } = await supabase.from("profiles").insert({ + id: authData.user.id, + username: data.username, + email: data.email, + wallet_address: data.walletAddress, + }); + + if (profileError) { + setError(profileError.message); + return; + } + + router.push("/dashboard"); + router.refresh(); + } } catch { - setServerError("Network error. Please check your connection and try again."); + setError("Something went wrong"); } }; @@ -194,29 +229,53 @@ export default function RegisterPage() { register={register("email")} /> - + + {/* + FIX 4: Register walletAddress as a hidden input so react-hook-form + actually tracks the field. Without this, the field is unknown to RHF + and its value stays `undefined` in the form state — causing Zod to + throw "expected string, received undefined" on any validation pass. + */} + - - Create Account - - - +
+ + + {errors.walletAddress && ( +

{errors.walletAddress.message}

+ )} +
-

- By creating an account you agree to our{" "} - - Terms of Service - - . -

+ + + + Create Account + + + + ); -} +} \ No newline at end of file diff --git a/apps/web/app/disputes/new/page.tsx b/apps/web/app/disputes/new/page.tsx index 9d642ca..02486e1 100644 --- a/apps/web/app/disputes/new/page.tsx +++ b/apps/web/app/disputes/new/page.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' -import { createClient } from '@/lib/superbase/client' +import { createClient } from '@/lib/supabase/client' import { useSearchParams, useRouter } from 'next/navigation' import { ShieldAlert, diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index d5bc15e..26e851e 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,6 +1,5 @@ import "../lib/env"; import type { Metadata } from "next"; -import { ThemeProvider } from "@/lib/theme/provider"; import { ServiceWorkerProvider } from "@/components/providers/ServiceWorkerProvider"; import { ErrorProvider } from "@/components/providers/ErrorProvider"; import NextTopLoader from "nextjs-toploader"; @@ -19,6 +18,8 @@ import Header from "@/components/Header"; import Sidebar from "@/components/Sidebar"; import "./globals.css"; import "@fontsource-variable/inter"; +import { ThemeProvider } from "@/lib/theme/provider"; +import ProtectedRoute from "@/components/protectedRoute"; // Dynamically import ComparisonBar to reduce initial bundle size const ComparisonBar = dynamic(() => import("@/components/ComparisonBar"), { @@ -52,9 +53,11 @@ export default function RootLayout({ children }: { children: React.ReactNode }) > {/* Global error boundary - wraps everything inside ThemeProvider */} + {/* Loading bar at the top */} + {/* Analytics tracking */} @@ -107,6 +110,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) }, }} /> + diff --git a/apps/web/app/payments/page.tsx b/apps/web/app/payments/page.tsx index 163e7ee..1e6b06f 100644 --- a/apps/web/app/payments/page.tsx +++ b/apps/web/app/payments/page.tsx @@ -1,7 +1,7 @@ 'use client' import React, { useState, useEffect } from 'react' -import { createClient } from '@/lib/superbase/client' +import { createClient } from '@/lib/supabase/client' import { CreditCard, CheckCircle2, diff --git a/apps/web/app/profile/edit/page.tsx b/apps/web/app/profile/edit/page.tsx index 261ae7f..777956f 100644 --- a/apps/web/app/profile/edit/page.tsx +++ b/apps/web/app/profile/edit/page.tsx @@ -1,12 +1,8 @@ import { redirect } from "next/navigation"; import { createClient } from "@/lib/supabase/server"; import EditProfileForm from "@/components/profile/EditProfileForm"; - -import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; import Image from "next/image"; -import useAuth from "@/lib/hooks/useAuth"; -import { createBrowserClient } from "@/lib/supabase/client"; + export default async function EditProfilePage() { const supabase = await createClient(); @@ -19,6 +15,9 @@ export default async function EditProfilePage() { redirect("/auth/login"); } + const uploading = false; // Replace with actual uploading state + const saving = false; // Replace with actual saving state + const { data: user, error: userError } = await supabase .from("users") .select("*") @@ -34,23 +33,36 @@ export default async function EditProfilePage() { ); } + const { username, email, bio, avatar_url: avatarUrl } = user; + const handleSave = async (updatedData: { username: string; email: string; bio: string; avatarUrl: string | null }) => { + // Implement profile update logic here, e.g. call an API route to update the user profile in the database + console.log("Saving profile with data:", updatedData); + } + const handleFileChange = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + // Implement file upload logic here, e.g. upload to Supabase Storage and get the public URL + console.log("Selected file for avatar:", file); + } + return (

Edit Profile

-
+ handleSave({ username, email, bio, avatarUrl })} aria-labelledby="edit-profile-heading" className="grid gap-4 md:grid-cols-2">