From 8befaf43106747fa6af9f1eb2160e13c37446da5 Mon Sep 17 00:00:00 2001 From: Mikaal Naik Date: Sat, 27 Sep 2025 11:52:05 -0400 Subject: [PATCH 01/10] try auth fix --- next.config.ts | 17 +++ package-lock.json | 112 -------------------- src/app/api/[id]/route.ts | 7 +- src/app/bills/[id]/edit/page.tsx | 3 +- src/app/sign-in/page.tsx | 3 +- src/app/unauthorized/page.tsx | 3 +- src/components/Nav/nav.component.tsx | 4 +- src/components/SessionProvider.tsx | 4 +- src/components/SignIn/sign-in.component.tsx | 3 +- 9 files changed, 37 insertions(+), 119 deletions(-) diff --git a/next.config.ts b/next.config.ts index ed83a10..9197365 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,11 +1,28 @@ import type { NextConfig } from "next"; +const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || "/bills"; + const nextConfig: NextConfig = { basePath: "/bills", + assetPrefix: BASE_PATH, /* config options here */ output: "standalone", + // Ensure incoming requests scoped under BASE_PATH resolve to root routes + async rewrites() { + return [ + { + source: `${BASE_PATH}`, + destination: "/", + }, + { + source: `${BASE_PATH}/:path*`, + destination: "/:path*", + }, + ]; + }, + // Performance optimizations experimental: { optimizePackageImports: ["lucide-react", "react-markdown"], diff --git a/package-lock.json b/package-lock.json index 691def6..6cf2254 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3902,7 +3902,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -3918,7 +3917,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3934,7 +3932,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3950,7 +3947,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3966,7 +3962,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -3982,7 +3977,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -3998,7 +3992,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -38718,111 +38711,6 @@ "type": "github", "url": "https://github.com/sponsors/wooorm" } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.0.tgz", - "integrity": "sha512-s2Nk6ec+pmYmAb/utawuURy7uvyYKDk+TRE5aqLRsdnj3AhwC9IKUBmhfnLmY/+P+DnwqpeXEFIKe9tlG0p6CA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.0.tgz", - "integrity": "sha512-mGlPJMZReU4yP5fSHjOxiTYvZmwPSWn/eF/dcg21pwfmiUCKS1amFvf1F1RkLHPIMPfocxLViNWFvkvDB14Isg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.0.tgz", - "integrity": "sha512-biWqIOE17OW/6S34t1X8K/3vb1+svp5ji5QQT/IKR+VfM3B7GvlCwmz5XtlEan2ukOUf9tj2vJJBffaGH4fGRw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.0.tgz", - "integrity": "sha512-zPisT+obYypM/l6EZ0yRkK3LEuoZqHaSoYKj+5jiD9ESHwdr6QhnabnNxYkdy34uCigNlWIaCbjFmQ8FY5AlxA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.0.tgz", - "integrity": "sha512-+t3+7GoU9IYmk+N+FHKBNFdahaReoAktdOpXHFIPOU1ixxtdge26NgQEEkJkCw2dHT9UwwK5zw4mAsURw4E8jA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.0.tgz", - "integrity": "sha512-d8MrXKh0A+c9DLiy1BUFwtg3Hu90Lucj3k6iKTUdPOv42Ve2UiIG8HYi3UAb8kFVluXxEfdpCoPPCSODk5fDcw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.0.tgz", - "integrity": "sha512-Fe1tGHxOWEyQjmygWkkXSwhFcTJuimrNu52JEuwItrKJVV4iRjbWp9I7zZjwqtiNnQmxoEvoisn8wueFLrNpvQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/src/app/api/[id]/route.ts b/src/app/api/[id]/route.ts index 2fdc5b5..1b83970 100644 --- a/src/app/api/[id]/route.ts +++ b/src/app/api/[id]/route.ts @@ -4,6 +4,10 @@ import { connectToDatabase } from "@/lib/mongoose"; import { Bill } from "@/models/Bill"; import { User } from "@/models/User"; import { authOptions } from "@/lib/auth"; +import { getUnifiedBillById } from "@/server/get-unified-bill-by-id"; +import { z } from "zod"; +import { stripBasePath } from "@/utils/basePath"; +import { BASE_PATH } from "@/utils/basePath"; export async function POST( request: Request, @@ -150,5 +154,6 @@ export async function POST( await Bill.updateOne({ billId: id }, { $set: update }, { upsert: false }); - return NextResponse.redirect(new URL(`/bills/${id}`, request.url)); + const redirectPath = `${BASE_PATH || ""}/${id}`.replace(/\/+/g, "/"); + return NextResponse.redirect(new URL(redirectPath, request.url)); } diff --git a/src/app/bills/[id]/edit/page.tsx b/src/app/bills/[id]/edit/page.tsx index 8bf04e8..4b119b6 100644 --- a/src/app/bills/[id]/edit/page.tsx +++ b/src/app/bills/[id]/edit/page.tsx @@ -2,6 +2,7 @@ import { redirect } from "next/navigation"; import { getServerSession } from "next-auth"; import { authOptions } from "@/auth"; import { getBillByIdFromDB } from "@/server/get-bill-by-id-from-db"; +import { BASE_PATH } from "@/utils/basePath"; interface Params { params: Promise<{ id: string }>; @@ -16,7 +17,7 @@ export default async function EditBillPage({ params }: Params) { const bill = await getBillByIdFromDB(id); if (!bill) { - redirect(`/bills/${id}`); + redirect(`${BASE_PATH || ""}/${id}`.replace(/\/+/g, "/")); } return ( diff --git a/src/app/sign-in/page.tsx b/src/app/sign-in/page.tsx index 4a3e787..0c31b04 100644 --- a/src/app/sign-in/page.tsx +++ b/src/app/sign-in/page.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, Suspense } from "react"; import { useSearchParams } from "next/navigation"; import { Button } from "@/components/ui/button"; import { signIn } from "next-auth/react"; +import { BASE_PATH } from "@/utils/basePath"; function SignInContent() { const [loading, setLoading] = useState(false); @@ -16,7 +17,7 @@ function SignInContent() { const onGoogle = async () => { try { setLoading(true); - await signIn("google", { callbackUrl: "/" }); + await signIn("google", { callbackUrl: BASE_PATH || "/" }); } finally { setLoading(false); } diff --git a/src/app/unauthorized/page.tsx b/src/app/unauthorized/page.tsx index 658419e..5bfa1f0 100644 --- a/src/app/unauthorized/page.tsx +++ b/src/app/unauthorized/page.tsx @@ -1,4 +1,5 @@ import Link from "next/link"; +import { BASE_PATH } from "@/utils/basePath"; export default function UnauthorizedPage() { return ( @@ -6,7 +7,7 @@ export default function UnauthorizedPage() {

Sorry!

You don’t have access to view this page.

- + Go home
diff --git a/src/components/Nav/nav.component.tsx b/src/components/Nav/nav.component.tsx index 269b8b3..cb94993 100644 --- a/src/components/Nav/nav.component.tsx +++ b/src/components/Nav/nav.component.tsx @@ -1,6 +1,7 @@ "use client"; import { PROJECT_NAME } from "@/consts/general"; +import { BASE_PATH } from "@/utils/basePath"; import { Session } from "next-auth"; import { signOut, useSession } from "next-auth/react"; import Link from "next/link"; @@ -10,7 +11,8 @@ export const Nav = () => { const { data: session }: { data: Session | null } = useSession(); const handleSignOut = () => { - signOut({ callbackUrl: "/bills" }); + const redirect = BASE_PATH || "/"; + signOut({ callbackUrl: redirect }); }; return ( diff --git a/src/components/SessionProvider.tsx b/src/components/SessionProvider.tsx index bbc0448..1f34529 100644 --- a/src/components/SessionProvider.tsx +++ b/src/components/SessionProvider.tsx @@ -2,14 +2,16 @@ import { SessionProvider as NextAuthSessionProvider } from "next-auth/react"; import { ReactNode } from "react"; +import { BASE_PATH } from "@/utils/basePath"; interface SessionProviderProps { children: ReactNode; } export function SessionProvider({ children }: SessionProviderProps) { + const authBasePath = `${BASE_PATH || ""}/api/auth`; return ( - + {children} ); diff --git a/src/components/SignIn/sign-in.component.tsx b/src/components/SignIn/sign-in.component.tsx index ff64f20..34f0e49 100644 --- a/src/components/SignIn/sign-in.component.tsx +++ b/src/components/SignIn/sign-in.component.tsx @@ -1,9 +1,10 @@ "use client"; import { signIn } from "next-auth/react"; +import { BASE_PATH } from "@/utils/basePath"; export const SignIn = () => { const handleSignInClick = () => { - signIn("google"); + signIn("google", { callbackUrl: BASE_PATH || "/" }); }; return (
From 5a2e48e6c3d42015bc8f4b6af30704315c4367cc Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Sat, 4 Oct 2025 21:01:11 -0700 Subject: [PATCH 02/10] Update .env.example. Fix Sign In url after error --- .env.example | 9 +++++++-- next.config.ts | 2 +- src/auth.ts | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 6e11dc0..28c2a51 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,14 @@ # Node environment (development, production, test) NODE_ENV=development + +# App base path +NEXT_PUBLIC_APP_URL=http://localhost:3000 +NEXT_PUBLIC_BASE_PATH=/bills + # NextAuth Configuration # Required for authentication -NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_URL=http://localhost:3000/bills/api/auth NEXTAUTH_SECRET=your-nextauth-secret-here # Alternative: AUTH_SECRET can be used instead of NEXTAUTH_SECRET # AUTH_SECRET=your-auth-secret-here @@ -18,7 +23,7 @@ GOOGLE_CLIENT_SECRET=your-google-client-secret # MongoDB Configuration # Required for database connection -MONGO_URI=mongodb://localhost:27017/billstracker +MONGO_URI=mongodb://localhost:27017 # Civics Project API # Required for fetching bill data diff --git a/next.config.ts b/next.config.ts index 9197365..cbf43d9 100644 --- a/next.config.ts +++ b/next.config.ts @@ -3,7 +3,7 @@ import type { NextConfig } from "next"; const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH || "/bills"; const nextConfig: NextConfig = { - basePath: "/bills", + basePath: BASE_PATH, assetPrefix: BASE_PATH, /* config options here */ diff --git a/src/auth.ts b/src/auth.ts index 7e42dd0..f86ce42 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -3,6 +3,7 @@ import Google from "next-auth/providers/google"; import { env, assertServerEnv } from "@/env"; import { connectToDatabase } from "@/lib/mongoose"; import { User } from "@/models/User"; +import { BASE_PATH } from "@/utils/basePath"; if (env.NODE_ENV !== "production") { try { @@ -76,7 +77,7 @@ export const authOptions: NextAuthOptions = { }, }, pages: { - signIn: "/sign-in", + signIn: `${BASE_PATH}/sign-in`, }, secret: env.NEXTAUTH_SECRET || env.AUTH_SECRET, }; From 73bdef4729295199d980522a9adf5b6888e7426a Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Sat, 4 Oct 2025 21:18:05 -0700 Subject: [PATCH 03/10] consolidate auth files --- src/app/[id]/edit/page.tsx | 2 + src/app/bills/[id]/edit/page.tsx | 2 +- src/auth.ts | 86 ----------------------------- src/lib/auth.ts | 93 +++++++++++++++++++------------- 4 files changed, 60 insertions(+), 123 deletions(-) delete mode 100644 src/auth.ts diff --git a/src/app/[id]/edit/page.tsx b/src/app/[id]/edit/page.tsx index 496d35f..0005b68 100644 --- a/src/app/[id]/edit/page.tsx +++ b/src/app/[id]/edit/page.tsx @@ -14,6 +14,7 @@ export default async function EditBillPage({ params }: Params) { const session = await getServerSession(authOptions); if (!session?.user?.email) { + console.log("!session?.user?.email") redirect(`/unauthorized`); } @@ -23,6 +24,7 @@ export default async function EditBillPage({ params }: Params) { emailLower: session.user.email.toLowerCase(), }); if (!dbUser) { + console.log("!dbUser") redirect(`/unauthorized`); } diff --git a/src/app/bills/[id]/edit/page.tsx b/src/app/bills/[id]/edit/page.tsx index a083252..90bd971 100644 --- a/src/app/bills/[id]/edit/page.tsx +++ b/src/app/bills/[id]/edit/page.tsx @@ -1,6 +1,6 @@ import { redirect } from "next/navigation"; import { getServerSession } from "next-auth"; -import { authOptions } from "@/auth"; +import { authOptions } from "@/lib/auth"; import { getBillByIdFromDB } from "@/server/get-bill-by-id-from-db"; import { BASE_PATH } from "@/utils/basePath"; diff --git a/src/auth.ts b/src/auth.ts deleted file mode 100644 index f86ce42..0000000 --- a/src/auth.ts +++ /dev/null @@ -1,86 +0,0 @@ -import NextAuth, { type NextAuthOptions } from "next-auth"; -import Google from "next-auth/providers/google"; -import { env, assertServerEnv } from "@/env"; -import { connectToDatabase } from "@/lib/mongoose"; -import { User } from "@/models/User"; -import { BASE_PATH } from "@/utils/basePath"; - -if (env.NODE_ENV !== "production") { - try { - assertServerEnv(); - } catch (e) { - console.warn("[auth] env check:", e); - } - if (!env.NEXTAUTH_URL) - console.warn( - "[auth] Missing NEXTAUTH_URL (e.g. http://localhost:3000 in dev).", - ); -} - -export const authOptions: NextAuthOptions = { - providers: [ - Google({ - clientId: env.GOOGLE_CLIENT_ID || "", - clientSecret: env.GOOGLE_CLIENT_SECRET || "", - authorization: { - params: { scope: "openid email profile", prompt: "consent" }, - }, - }), - ], - debug: process.env.NODE_ENV !== "production", - session: { strategy: "jwt" }, - callbacks: { - async signIn({ user }) { - const email = user?.email?.trim().toLowerCase(); - if (!email) return false; - - // Prefer DB-backed allowlist. Fall back to stub if DB not configured. - try { - await connectToDatabase(); - const now = new Date(); - const existing = await User.findOne({ emailLower: email }); - if (!existing) { - if (env.NODE_ENV !== "production") { - console.warn( - `[auth] User ${email} not found. No auto-creation. Denying sign-in.`, - ); - } - return false; - } - existing.name = user?.name ?? existing.name; - (existing as any).image = (user as any)?.image ?? existing.image; - existing.lastLoginAt = now; - await existing.save(); - return !!existing.allowed; - } catch (err) { - if (env.NODE_ENV !== "production") { - console.warn("[auth] DB check failed, denying sign-in:", err); - } - return false; - } - }, - async jwt({ token, user }) { - if (user?.email) { - token.email = user.email; - token.name = user.name; - token.picture = (user as any)?.image as string | undefined; - } - return token; - }, - async session({ session, token }) { - if (session?.user) { - session.user.email = token.email as string | undefined; - session.user.name = token.name as string | undefined; - (session.user as any).image = token.picture as string | undefined; - } - return session; - }, - }, - pages: { - signIn: `${BASE_PATH}/sign-in`, - }, - secret: env.NEXTAUTH_SECRET || env.AUTH_SECRET, -}; - -const handler = NextAuth(authOptions); -export { handler as GET, handler as POST }; diff --git a/src/lib/auth.ts b/src/lib/auth.ts index c31a28c..936a21e 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,62 +1,83 @@ import { NextAuthOptions } from "next-auth"; -import GoogleProvider from "next-auth/providers/google"; +import Google from "next-auth/providers/google"; +import { env, assertServerEnv } from "@/env"; import { connectToDatabase } from "@/lib/mongoose"; import { User } from "@/models/User"; -import { env } from "@/env"; +import { BASE_PATH } from "@/utils/basePath"; + +if (env.NODE_ENV !== "production") { + try { + assertServerEnv(); + } catch (e) { + console.warn("[auth] env check:", e); + } + if (!env.NEXTAUTH_URL) + console.warn( + "[auth] Missing NEXTAUTH_URL (e.g. http://localhost:3000 in dev).", + ); +} export const authOptions: NextAuthOptions = { providers: [ - GoogleProvider({ + Google({ clientId: env.GOOGLE_CLIENT_ID || "", clientSecret: env.GOOGLE_CLIENT_SECRET || "", + authorization: { + params: { scope: "openid email profile", prompt: "consent" }, + }, }), ], + debug: process.env.NODE_ENV !== "production", + session: { strategy: "jwt" }, callbacks: { async signIn({ user }) { - // Only allow sign-in if email is provided - if (!user.email) { - return false; - } + const email = user?.email?.trim().toLowerCase(); + if (!email) return false; + // Prefer DB-backed allowlist. Fall back to stub if DB not configured. try { await connectToDatabase(); - - // Check if user exists in our database - const existingUser = await User.findOne({ - emailLower: user.email.toLowerCase(), - }); - - if (existingUser) { - // Only allow sign-in if the user is approved - return existingUser.allowed === true; + const now = new Date(); + const existing = await User.findOne({ emailLower: email }); + if (!existing) { + if (env.NODE_ENV !== "production") { + console.warn( + `[auth] User ${email} not found. No auto-creation. Denying sign-in.`, + ); + } + return false; + } + existing.name = user?.name ?? existing.name; + (existing as any).image = (user as any)?.image ?? existing.image; + existing.lastLoginAt = now; + await existing.save(); + return !!existing.allowed; + } catch (err) { + if (env.NODE_ENV !== "production") { + console.warn("[auth] DB check failed, denying sign-in:", err); } - - return false; - } catch (error) { - console.error("Error during sign-in:", error); return false; } }, - async session({ session }) { - // Add user ID to session if needed - if (session.user?.email) { - try { - await connectToDatabase(); - const dbUser = await User.findOne({ - emailLower: session.user.email.toLowerCase(), - }); - if (dbUser) { - (session.user as any).id = dbUser._id.toString(); - (session.user as any).allowed = dbUser.allowed; - } - } catch (error) { - console.error("Error fetching user from DB:", error); - } + async jwt({ token, user }) { + if (user?.email) { + token.email = user.email; + token.name = user.name; + token.picture = (user as any)?.image as string | undefined; + } + return token; + }, + async session({ session, token }) { + if (session?.user) { + session.user.email = token.email as string | undefined; + session.user.name = token.name as string | undefined; + (session.user as any).image = token.picture as string | undefined; } return session; }, }, pages: { - signIn: "/auth/signin", + signIn: `${BASE_PATH}/sign-in`, }, + secret: env.NEXTAUTH_SECRET || env.AUTH_SECRET, }; From 6606d3ba37879b0b3fe4eddd2359d90ec0e77778 Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Wed, 8 Oct 2025 17:04:21 -0700 Subject: [PATCH 04/10] Empty commit to trigger Github Action for CI From 4b996031a8ccf2758791f82860fe721b16591e30 Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Wed, 8 Oct 2025 17:30:33 -0700 Subject: [PATCH 05/10] Add missing packages --- package.json | 4 +- pnpm-lock.yaml | 104 ++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 94 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index 2cf47d4..c3c4944 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.6", "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", "class-variance-authority": "^0.7.1", @@ -69,7 +70,8 @@ "remark-gfm": "^4.0.1", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", - "vaul": "^1.1.2" + "vaul": "^1.1.2", + "zod": "^4.1.12" }, "devDependencies": { "@biomejs/biome": "2.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f899d5..fbed6ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@google/generative-ai': specifier: ^0.24.1 version: 0.24.1 + '@next/third-parties': + specifier: ^15.5.3 + version: 15.5.4(next@15.5.3(@babel/core@7.1.6)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) '@radix-ui/react-accordion': specifier: ^1.2.12 version: 1.2.12(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -86,6 +89,9 @@ importers: '@radix-ui/react-tabs': specifier: ^1.1.13 version: 1.1.13(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-toggle': + specifier: ^1.1.10 + version: 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) '@radix-ui/react-toggle-group': specifier: ^1.1.11 version: 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -101,6 +107,9 @@ importers: cmdk: specifier: ^1.1.1 version: 1.1.1(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + dayjs: + specifier: ^1.11.18 + version: 1.11.18 fast-xml-parser: specifier: ^5.2.5 version: 5.2.5 @@ -130,7 +139,7 @@ importers: version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) openai: specifier: ^5.15.0 - version: 5.20.2(ws@8.18.3) + version: 5.20.2(ws@8.18.3)(zod@4.1.12) react: specifier: 19.1.1 version: 19.1.1 @@ -164,6 +173,9 @@ importers: vaul: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.1.9(@types/react@19.1.13))(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + zod: + specifier: ^4.1.12 + version: 4.1.12 devDependencies: '@biomejs/biome': specifier: 2.2.0 @@ -189,9 +201,15 @@ importers: happy-dom: specifier: ^18.0.1 version: 18.0.1 + husky: + specifier: ^9.1.7 + version: 9.1.7 tailwindcss: specifier: ^4 version: 4.1.13 + tsx: + specifier: ^4.20.5 + version: 4.20.6 tw-animate-css: specifier: ^1.3.8 version: 1.3.8 @@ -200,7 +218,7 @@ importers: version: 5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.16)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(terser@5.44.0) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.16)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6) packages: @@ -1535,6 +1553,12 @@ packages: cpu: [x64] os: [win32] + '@next/third-parties@15.5.4': + resolution: {integrity: sha512-l3T1M/EA32phPzZx+gkQAWOF3E5iAULL1nX4Ej0JZQOXaBwwJzb/rd2uefr5TAshJj/+HjjwmdFu7olXudvgVg==} + peerDependencies: + next: ^13.0.0 || ^14.0.0 || ^15.0.0 + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + '@nodelib/fs.stat@1.1.3': resolution: {integrity: sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==} engines: {node: '>= 6'} @@ -3781,6 +3805,9 @@ packages: date-fns@4.1.0: resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -4643,6 +4670,9 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} + get-tsconfig@4.12.0: + resolution: {integrity: sha512-LScr2aNr2FbjAjZh2C6X6BxRx1/x+aTDExct/xyq2XKbYOiG5c0aK7pMsSuyc0brz3ibr/lbQiHD9jzt4lccJw==} + get-value@2.0.6: resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} engines: {node: '>=0.10.0'} @@ -4951,6 +4981,11 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -7661,6 +7696,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-url@0.2.1: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} deprecated: https://github.com/lydell/resolve-url#deprecated @@ -8326,6 +8364,9 @@ packages: text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + third-party-capital@1.0.20: + resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==} + throat@4.1.0: resolution: {integrity: sha512-wCVxLDcFxw7ujDxaeJC6nfl2XfHJNYs8yUYJnvMgtPEFlttP9tHSfRUv2vBe6C4hkVFPWoP1P6ZccbYjmSEkKA==} @@ -8461,6 +8502,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.20.6: + resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} + engines: {node: '>=18.0.0'} + hasBin: true + tty-browserify@0.0.0: resolution: {integrity: sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw==} @@ -9160,6 +9206,9 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -10926,6 +10975,12 @@ snapshots: '@next/swc-win32-x64-msvc@15.5.3': optional: true + '@next/third-parties@15.5.4(next@15.5.3(@babel/core@7.1.6)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': + dependencies: + next: 15.5.3(@babel/core@7.1.6)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + third-party-capital: 1.0.20 + '@nodelib/fs.stat@1.1.3': {} '@panva/hkdf@1.2.1': {} @@ -11981,13 +12036,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0))': + '@vitest/mocker@3.2.4(vite@7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.19 optionalDependencies: - vite: 7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0) + vite: 7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6) '@vitest/pretty-format@3.2.4': dependencies: @@ -12018,7 +12073,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.16)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(terser@5.44.0) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.16)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6) '@vitest/utils@3.2.4': dependencies: @@ -13593,6 +13648,8 @@ snapshots: date-fns@4.1.0: {} + dayjs@1.11.18: {} + debug@2.6.9(supports-color@5.5.0): dependencies: ms: 2.0.0 @@ -14665,6 +14722,10 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 + get-tsconfig@4.12.0: + dependencies: + resolve-pkg-maps: 1.0.0 + get-value@2.0.6: {} getpass@0.1.7: @@ -15066,6 +15127,8 @@ snapshots: transitivePeerDependencies: - supports-color + husky@9.1.7: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -17328,9 +17391,10 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openai@5.20.2(ws@8.18.3): + openai@5.20.2(ws@8.18.3)(zod@4.1.12): optionalDependencies: ws: 8.18.3 + zod: 4.1.12 openid-client@5.7.1: dependencies: @@ -18664,6 +18728,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve-url@0.2.1: {} resolve@1.1.7: {} @@ -19531,6 +19597,8 @@ snapshots: text-table@0.2.0: {} + third-party-capital@1.0.20: {} + throat@4.1.0: {} throat@5.0.0: {} @@ -19646,6 +19714,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.20.6: + dependencies: + esbuild: 0.25.9 + get-tsconfig: 4.12.0 + optionalDependencies: + fsevents: 2.3.3 + tty-browserify@0.0.0: {} tunnel-agent@0.6.0: @@ -19948,13 +20023,13 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0): + vite-node@3.2.4(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6): dependencies: cac: 6.7.14 debug: 4.4.3(supports-color@5.5.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0) + vite: 7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6) transitivePeerDependencies: - '@types/node' - jiti @@ -19969,7 +20044,7 @@ snapshots: - tsx - yaml - vite@7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0): + vite@7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6): dependencies: esbuild: 0.25.9 fdir: 6.5.0(picomatch@4.0.3) @@ -19983,12 +20058,13 @@ snapshots: jiti: 2.5.1 lightningcss: 1.30.1 terser: 5.44.0 + tsx: 4.20.6 - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.16)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(terser@5.44.0): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.16)(@vitest/ui@3.2.4)(happy-dom@18.0.1)(jiti@2.5.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)) + '@vitest/mocker': 3.2.4(vite@7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -20006,8 +20082,8 @@ snapshots: tinyglobby: 0.2.15 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0) - vite-node: 3.2.4(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0) + vite: 7.1.5(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6) + vite-node: 3.2.4(@types/node@20.19.16)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.44.0)(tsx@4.20.6) why-is-node-running: 2.3.0 optionalDependencies: '@types/debug': 4.1.12 @@ -20477,4 +20553,6 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + zod@4.1.12: {} + zwitch@2.0.4: {} From 012a55c7bb380eefd9eb92ab10beb6cbad8849c9 Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Wed, 8 Oct 2025 17:39:49 -0700 Subject: [PATCH 06/10] Fix lint and type errors --- src/app/[id]/edit/page.tsx | 4 ++-- src/app/api/[id]/route.ts | 3 --- src/app/opengraph-image.tsx | 2 -- src/app/page.tsx | 10 +++++++--- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/app/[id]/edit/page.tsx b/src/app/[id]/edit/page.tsx index 9c2b268..b6d2393 100644 --- a/src/app/[id]/edit/page.tsx +++ b/src/app/[id]/edit/page.tsx @@ -14,7 +14,7 @@ export default async function EditBillPage({ params }: Params) { const session = await getServerSession(authOptions); if (!session?.user?.email) { - console.log("!session?.user?.email") + console.log("!session?.user?.email"); redirect(`/unauthorized`); } @@ -24,7 +24,7 @@ export default async function EditBillPage({ params }: Params) { emailLower: session.user.email.toLowerCase(), }); if (!dbUser) { - console.log("!dbUser") + console.log("!dbUser"); redirect(`/unauthorized`); } diff --git a/src/app/api/[id]/route.ts b/src/app/api/[id]/route.ts index c6fa07d..979276a 100644 --- a/src/app/api/[id]/route.ts +++ b/src/app/api/[id]/route.ts @@ -4,9 +4,6 @@ import { connectToDatabase } from "@/lib/mongoose"; import { Bill } from "@/models/Bill"; import { User } from "@/models/User"; import { authOptions } from "@/lib/auth"; -import { getUnifiedBillById } from "@/server/get-unified-bill-by-id"; -import { z } from "zod"; -import { stripBasePath } from "@/utils/basePath"; import { BASE_PATH } from "@/utils/basePath"; export async function POST( diff --git a/src/app/opengraph-image.tsx b/src/app/opengraph-image.tsx index 3fda851..8063621 100644 --- a/src/app/opengraph-image.tsx +++ b/src/app/opengraph-image.tsx @@ -1,5 +1,4 @@ import { ImageResponse } from "next/og"; -import { PROJECT_NAME } from "@/consts/general"; export const runtime = "nodejs"; @@ -101,4 +100,3 @@ export default async function HomeOpengraphImage() { }, ); } - diff --git a/src/app/page.tsx b/src/app/page.tsx index 825cca1..4aaff63 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -23,10 +23,12 @@ export async function generateMetadata(): Promise { "Understand Canadian federal bills with builder-first analysis."; const h = headers(); const headerList = await h; - const host = headerList.get("x-forwarded-host") || headerList.get("host") || ""; + const host = + headerList.get("x-forwarded-host") || headerList.get("host") || ""; const proto = (headerList.get("x-forwarded-proto") || "https").split(",")[0]; const baseUrl = - env.NEXT_PUBLIC_APP_URL || (host ? `${proto}://${host}` : "http://localhost:3000"); + env.NEXT_PUBLIC_APP_URL || + (host ? `${proto}://${host}` : "http://localhost:3000"); const pagePath = buildRelativePath(); const pageUrl = `${baseUrl}${pagePath}`; const ogPath = buildRelativePath("opengraph-image"); @@ -42,7 +44,9 @@ export async function generateMetadata(): Promise { url: pageUrl, siteName: PROJECT_NAME, type: "website", - images: [{ url: ogImageUrl, width: 1200, height: 630, alt: PROJECT_NAME }], + images: [ + { url: ogImageUrl, width: 1200, height: 630, alt: PROJECT_NAME }, + ], }, twitter: { card: "summary_large_image", From 3bc22aacc27a1dcceac8deeefe954c2b72ee56d2 Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Wed, 15 Oct 2025 18:26:57 -0700 Subject: [PATCH 07/10] Simpler Admin link --- src/components/SignIn/sign-in.component.tsx | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/components/SignIn/sign-in.component.tsx b/src/components/SignIn/sign-in.component.tsx index 34f0e49..c1a842e 100644 --- a/src/components/SignIn/sign-in.component.tsx +++ b/src/components/SignIn/sign-in.component.tsx @@ -1,18 +1,10 @@ -"use client"; -import { signIn } from "next-auth/react"; -import { BASE_PATH } from "@/utils/basePath"; +import Link from "next/link"; export const SignIn = () => { - const handleSignInClick = () => { - signIn("google", { callbackUrl: BASE_PATH || "/" }); - }; return (
-

- Admin +

+ Admin

); From 158a358f1789c4c7881847a9b35ccde440998ff2 Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Wed, 15 Oct 2025 18:35:13 -0700 Subject: [PATCH 08/10] Remove unused package. Remove console.log --- package.json | 3 +-- src/app/[id]/edit/page.tsx | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/package.json b/package.json index 05f3174..9aa0926 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,7 @@ "remark-gfm": "^4.0.1", "sonner": "^2.0.7", "tailwind-merge": "^3.3.1", - "vaul": "^1.1.2", - "zod": "^4.1.12" + "vaul": "^1.1.2" }, "devDependencies": { "@biomejs/biome": "2.2.0", diff --git a/src/app/[id]/edit/page.tsx b/src/app/[id]/edit/page.tsx index de19d1d..97dbb8e 100644 --- a/src/app/[id]/edit/page.tsx +++ b/src/app/[id]/edit/page.tsx @@ -14,7 +14,6 @@ export default async function EditBillPage({ params }: Params) { const session = await getServerSession(authOptions); if (!session?.user?.email) { - console.log("!session?.user?.email"); redirect(`/unauthorized`); } @@ -24,7 +23,6 @@ export default async function EditBillPage({ params }: Params) { emailLower: session.user.email.toLowerCase(), }); if (!dbUser) { - console.log("!dbUser"); redirect(`/unauthorized`); } From 1f7c036059dc3c5436ddde7628a4e0bf0dc475f9 Mon Sep 17 00:00:00 2001 From: Shawn Price Date: Wed, 15 Oct 2025 19:01:55 -0700 Subject: [PATCH 09/10] simplify auth. Remove redundant page.tsx. Nice Save button --- src/app/[id]/edit/page.tsx | 32 ++--- src/app/api/[id]/route.ts | 6 +- src/app/bills/[id]/edit/page.tsx | 221 ------------------------------- src/lib/auth-guards.ts | 35 +++++ 4 files changed, 50 insertions(+), 244 deletions(-) delete mode 100644 src/app/bills/[id]/edit/page.tsx create mode 100644 src/lib/auth-guards.ts diff --git a/src/app/[id]/edit/page.tsx b/src/app/[id]/edit/page.tsx index 97dbb8e..7408c14 100644 --- a/src/app/[id]/edit/page.tsx +++ b/src/app/[id]/edit/page.tsx @@ -1,9 +1,8 @@ import { redirect } from "next/navigation"; import { getBillByIdFromDB } from "@/server/get-bill-by-id-from-db"; -import { getServerSession } from "next-auth"; -import { authOptions } from "@/lib/auth"; -import { connectToDatabase } from "@/lib/mongoose"; -import { User } from "@/models/User"; +import { requireAuthenticatedUser } from "@/lib/auth-guards"; +import { BASE_PATH } from "@/utils/basePath"; +import { Button } from "@/components/ui/button"; interface Params { params: Promise<{ id: string }>; @@ -12,19 +11,8 @@ interface Params { export default async function EditBillPage({ params }: Params) { const { id } = await params; - const session = await getServerSession(authOptions); - if (!session?.user?.email) { - redirect(`/unauthorized`); - } - - // Verify the signed-in user exists in DB; do not create - await connectToDatabase(); - const dbUser = await User.findOne({ - emailLower: session.user.email.toLowerCase(), - }); - if (!dbUser) { - redirect(`/unauthorized`); - } + // Use reusable auth guard for consistent authentication + await requireAuthenticatedUser(); const bill = await getBillByIdFromDB(id); if (!bill) { @@ -37,7 +25,11 @@ export default async function EditBillPage({ params }: Params) { return (

Edit Bill

-
+
- +
); diff --git a/src/app/api/[id]/route.ts b/src/app/api/[id]/route.ts index 979276a..e953709 100644 --- a/src/app/api/[id]/route.ts +++ b/src/app/api/[id]/route.ts @@ -307,6 +307,8 @@ export async function POST( await Bill.updateOne({ billId: id }, { $set: update }, { upsert: false }); - const redirectPath = `${BASE_PATH || ""}/${id}`.replace(/\/+/g, "/"); - return NextResponse.redirect(new URL(redirectPath, request.url)); + // API routes need to manually include basePath in redirects + const url = new URL(request.url); + const redirectUrl = new URL(`${BASE_PATH}/${id}`, url.origin); + return NextResponse.redirect(redirectUrl); } diff --git a/src/app/bills/[id]/edit/page.tsx b/src/app/bills/[id]/edit/page.tsx deleted file mode 100644 index 816db23..0000000 --- a/src/app/bills/[id]/edit/page.tsx +++ /dev/null @@ -1,221 +0,0 @@ -import { redirect } from "next/navigation"; -import { getServerSession } from "next-auth"; -import { authOptions } from "@/lib/auth"; -import { getBillByIdFromDB } from "@/server/get-bill-by-id-from-db"; -import { BASE_PATH } from "@/utils/basePath"; - -interface Params { - params: Promise<{ id: string }>; -} - -export default async function EditBillPage({ params }: Params) { - const { id } = await params; - const session = await getServerSession(authOptions); - if (!session?.user) { - redirect("/"); - } - - const bill = await getBillByIdFromDB(id); - if (!bill) { - redirect(`${BASE_PATH || ""}/${id}`.replace(/\/+/g, "/")); - } - - const questionPeriodQuestions = bill.question_period_questions || []; - const questionFields = [...questionPeriodQuestions, { question: "" }]; - - return ( -
-

Edit Bill

-
-
- -