Skip to content
9 changes: 7 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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

# Production DB connection used with local development
PROD_MONGO_URI=
Expand Down
19 changes: 18 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
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 */
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"],
Expand Down
32 changes: 11 additions & 21 deletions src/app/[id]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -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 }>;
Expand All @@ -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) {
Expand All @@ -37,7 +25,11 @@ export default async function EditBillPage({ params }: Params) {
return (
<div className="mx-auto max-w-[900px] px-6 py-8">
<h1 className="text-xl font-semibold mb-6">Edit Bill</h1>
<form className="space-y-6" action={`/bills/api/${id}`} method="post">
<form
className="space-y-6"
action={`${BASE_PATH}/api/${id}`}
method="post"
>
<div className="space-y-2">
<label className="block text-sm font-medium" htmlFor="title">
Title
Expand Down Expand Up @@ -223,9 +215,7 @@ export default async function EditBillPage({ params }: Params) {
);
})}
</div>
<button type="submit" className="underline">
Save
</button>
<Button type="submit">Save</Button>
</form>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion src/app/api/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { connectToDatabase } from "@/lib/mongoose";
import { Bill } from "@/models/Bill";
import { User } from "@/models/User";
import { authOptions } from "@/lib/auth";
import { BASE_PATH } from "@/utils/basePath";

export async function POST(
request: Request,
Expand Down Expand Up @@ -306,5 +307,8 @@ export async function POST(

await Bill.updateOne({ billId: id }, { $set: update }, { upsert: false });

return NextResponse.redirect(new URL(`/bills/${id}`, 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);
}
220 changes: 0 additions & 220 deletions src/app/bills/[id]/edit/page.tsx

This file was deleted.

3 changes: 2 additions & 1 deletion src/app/sign-in/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/unauthorized/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import Link from "next/link";
import { BASE_PATH } from "@/utils/basePath";

export default function UnauthorizedPage() {
return (
<div className="mx-auto max-w-[800px] px-6 py-10">
<h1 className="text-xl font-semibold">Sorry! </h1>
<p className="mt-2 text-sm">You don’t have access to view this page.</p>
<div className="mt-4 space-x-4">
<Link className="underline text-sm" href="/">
<Link className="underline text-sm" href={BASE_PATH || "/"}>
Go home
</Link>
</div>
Expand Down
Loading