diff --git a/middleware.ts b/middleware.ts
deleted file mode 100644
index bad4641..0000000
--- a/middleware.ts
+++ /dev/null
@@ -1,66 +0,0 @@
-import { createServerClient } from "@supabase/ssr";
-import { NextResponse, type NextRequest } from "next/server";
-
-export async function middleware(request: NextRequest) {
- let supabaseResponse = NextResponse.next({ request });
-
- const supabase = createServerClient(
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
- {
- cookies: {
- getAll() {
- return request.cookies.getAll();
- },
- setAll(cookiesToSet) {
- cookiesToSet.forEach(({ name, value }) =>
- request.cookies.set(name, value),
- );
- supabaseResponse = NextResponse.next({ request });
- cookiesToSet.forEach(({ name, value, options }) =>
- supabaseResponse.cookies.set(name, value, options),
- );
- },
- },
- },
- );
-
- // Refresh session — must not run any code between createServerClient and getUser
- const {
- data: { user },
- } = await supabase.auth.getUser();
-
- const { pathname } = request.nextUrl;
-
- // Public paths that never require auth
- const isPublicPath =
- pathname.startsWith("/login") || pathname.startsWith("/auth");
-
- if (isPublicPath) {
- // Already logged in — send them to the map instead of showing login again
- if (user && pathname === "/login") {
- return NextResponse.redirect(new URL("/map", request.url));
- }
- return supabaseResponse;
- }
-
- // Everything else requires a session
- if (!user) {
- const loginUrl = new URL("/login", request.url);
- return NextResponse.redirect(loginUrl);
- }
-
- return supabaseResponse;
-}
-
-export const config = {
- matcher: [
- /*
- * Match all paths except:
- * - _next/static (static files)
- * - _next/image (image optimisation)
- * - favicon, icons, brand, logo assets, manifest
- */
- "/((?!_next/static|_next/image|favicon|icons|brand|logo|manifest).*)",
- ],
-};
diff --git a/src/app/auth/callback/route.ts b/src/app/auth/callback/route.ts
index 7c4c5f1..303bedf 100644
--- a/src/app/auth/callback/route.ts
+++ b/src/app/auth/callback/route.ts
@@ -5,7 +5,7 @@ import { NextResponse } from "next/server";
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url);
const code = searchParams.get("code");
- const next = searchParams.get("next") ?? "/map";
+ const next = searchParams.get("next") ?? "/";
if (code) {
const cookieStore = await cookies();
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index eec8985..136f94d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -12,6 +12,7 @@ import { Analytics } from "@/components/analytics";
import { cn } from "@/lib/utils";
export const metadata: Metadata = {
+ metadataBase: new URL(site.url),
title: {
default: `${site.name}: student places and benefits`,
template: `%s | ${site.name}`,
diff --git a/src/app/login/login-form.tsx b/src/app/login/login-form.tsx
new file mode 100644
index 0000000..0bce45c
--- /dev/null
+++ b/src/app/login/login-form.tsx
@@ -0,0 +1,195 @@
+"use client";
+
+import * as React from "react";
+import { useRouter, useSearchParams } from "next/navigation";
+import { toast } from "sonner";
+import { Loader2 } from "lucide-react";
+
+import { createClient } from "@/lib/supabase/client";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+
+export function LoginForm() {
+ const router = useRouter();
+ const searchParams = useSearchParams();
+ const next = searchParams.get("next") ?? "/";
+
+ const supabase = createClient();
+
+ const [mode, setMode] = React.useState<"signin" | "signup">("signin");
+ const [email, setEmail] = React.useState("");
+ const [password, setPassword] = React.useState("");
+ const [loading, setLoading] = React.useState(false);
+
+ async function handleEmailAuth(e: React.FormEvent) {
+ e.preventDefault();
+ setLoading(true);
+
+ if (mode === "signup") {
+ const { error } = await supabase.auth.signUp({ email, password });
+ if (error) {
+ toast.error(error.message);
+ } else {
+ toast.success("Check your email to confirm your account.");
+ }
+ } else {
+ const { error } = await supabase.auth.signInWithPassword({
+ email,
+ password,
+ });
+ if (error) {
+ toast.error(error.message);
+ } else {
+ router.push(next);
+ router.refresh();
+ }
+ }
+
+ setLoading(false);
+ }
+
+ async function handleGoogle() {
+ const { error } = await supabase.auth.signInWithOAuth({
+ provider: "google",
+ options: {
+ redirectTo: `${window.location.origin}/auth/callback?next=${encodeURIComponent(next)}`,
+ },
+ });
+ if (error) toast.error(error.message);
+ }
+
+ return (
+
+
+ {/* Logo */}
+
+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

+ {/* eslint-disable-next-line @next/next/no-img-element */}
+

+
+ {mode === "signin" ? "Sign in to continue" : "Create your account"}
+
+
+
+ {/* Card */}
+
+ {/* Google */}
+
+
+
+
+ {/* Email form */}
+
+
+
+ {/* Toggle mode */}
+
+ {mode === "signin" ? (
+ <>
+ No account?{" "}
+
+ >
+ ) : (
+ <>
+ Already have an account?{" "}
+
+ >
+ )}
+
+
+
+ );
+}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index 7533c81..e399b29 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -1,196 +1,10 @@
-"use client";
-
-import * as React from "react";
-import { useRouter } from "next/navigation";
-import { toast } from "sonner";
-import { Loader2 } from "lucide-react";
-
-import { createClient } from "@/lib/supabase/client";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Label } from "@/components/ui/label";
+import { Suspense } from "react";
+import { LoginForm } from "./login-form";
export default function LoginPage() {
- const router = useRouter();
- const supabase = createClient();
-
- const [mode, setMode] = React.useState<"signin" | "signup">("signin");
- const [email, setEmail] = React.useState("");
- const [password, setPassword] = React.useState("");
- const [loading, setLoading] = React.useState(false);
-
- async function handleEmailAuth(e: React.FormEvent) {
- e.preventDefault();
- setLoading(true);
-
- if (mode === "signup") {
- const { error } = await supabase.auth.signUp({ email, password });
- if (error) {
- toast.error(error.message);
- } else {
- toast.success("Check your email to confirm your account.");
- }
- } else {
- const { error } = await supabase.auth.signInWithPassword({
- email,
- password,
- });
- if (error) {
- toast.error(error.message);
- } else {
- router.push("/map");
- router.refresh();
- }
- }
-
- setLoading(false);
- }
-
- async function handleGoogle() {
- const { error } = await supabase.auth.signInWithOAuth({
- provider: "google",
- options: {
- redirectTo: `${window.location.origin}/auth/callback`,
- },
- });
- if (error) toast.error(error.message);
- }
-
return (
-
-
- {/* Logo */}
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
- {mode === "signin"
- ? "Sign in to continue"
- : "Create your account"}
-
-
-
- {/* Card */}
-
- {/* Google */}
-
-
-
-
- {/* Email form */}
-
-
-
- {/* Toggle mode */}
-
- {mode === "signin" ? (
- <>
- No account?{" "}
-
- >
- ) : (
- <>
- Already have an account?{" "}
-
- >
- )}
-
-
-
+
+
+
);
}
diff --git a/src/components/layout/navbar.tsx b/src/components/layout/navbar.tsx
index d9a726e..a74d410 100644
--- a/src/components/layout/navbar.tsx
+++ b/src/components/layout/navbar.tsx
@@ -3,7 +3,7 @@
import * as React from "react";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
-import { Menu, LogOut } from "lucide-react";
+import { Menu, LogOut, LogIn } from "lucide-react";
import { cn } from "@/lib/utils";
import { navLinks, site } from "@/lib/site";
@@ -30,7 +30,6 @@ export function Navbar() {
async function handleSignOut() {
const supabase = createClient();
await supabase.auth.signOut();
- router.push("/login");
router.refresh();
}
@@ -64,7 +63,7 @@ export function Navbar() {
- {loggedIn && (
+ {loggedIn ? (
+ ) : (
+
)}
@@ -98,7 +109,7 @@ export function Navbar() {
{link.label}
))}
- {loggedIn && (
+ {loggedIn ? (
+ ) : (
+ setOpen(false)}
+ className="flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
+ >
+
+ Sign in
+
)}
diff --git a/src/lib/site.ts b/src/lib/site.ts
index 9e81aa1..399cd01 100644
--- a/src/lib/site.ts
+++ b/src/lib/site.ts
@@ -3,6 +3,7 @@ export const site = {
tagline: "Find student places and perks across Mumbai, Thane, and Navi Mumbai.",
description:
"A crowdsourced map of student-important places (exam centres, libraries, book shops, and more) for the Mumbai Metropolitan Region.",
+ url: "https://studymapp.vercel.app",
repo: "https://github.com/anaydhawan/studymap",
} as const;
diff --git a/src/proxy.ts b/src/proxy.ts
new file mode 100644
index 0000000..363eecd
--- /dev/null
+++ b/src/proxy.ts
@@ -0,0 +1,39 @@
+import { createServerClient } from "@supabase/ssr";
+import { NextResponse, type NextRequest } from "next/server";
+
+export async function proxy(request: NextRequest) {
+ let proxyResponse = NextResponse.next({ request });
+
+ const supabase = createServerClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ {
+ cookies: {
+ getAll() {
+ return request.cookies.getAll();
+ },
+ setAll(cookiesToSet) {
+ cookiesToSet.forEach(({ name, value }) =>
+ request.cookies.set(name, value),
+ );
+ proxyResponse = NextResponse.next({ request });
+ cookiesToSet.forEach(({ name, value, options }) =>
+ proxyResponse.cookies.set(name, value, options),
+ );
+ },
+ },
+ },
+ );
+
+ // Refresh the session so it doesn't expire mid-visit.
+ // No auth enforcement — the site is fully public.
+ await supabase.auth.getUser();
+
+ return proxyResponse;
+}
+
+export const config = {
+ matcher: [
+ "/((?!_next/static|_next/image|favicon\\.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)",
+ ],
+};