diff --git a/next-env.d.ts b/next-env.d.ts
index c4b7818..9edff1c 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx
index 0b7670f..bdfb771 100644
--- a/src/app/(public)/login/page.tsx
+++ b/src/app/(public)/login/page.tsx
@@ -1,6 +1,6 @@
"use client";
-import { useState, useEffect } from "react";
+import { useState, useEffect, Suspense } from "react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { signIn } from "next-auth/react";
@@ -13,6 +13,14 @@ import { AlertCircle } from "lucide-react";
import { toast } from "sonner";
export default function LoginPage() {
+ return (
+
+
+
+ );
+}
+
+function LoginPageContent() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [isLoading, setIsLoading] = useState(false);
@@ -36,15 +44,11 @@ export default function LoginPage() {
setError(null);
setIsLoading(true);
try {
- const result = await signIn("credentials", {
+ await signIn("credentials", {
email,
password,
redirectTo: "/app",
});
- // If signIn returns with ok: false, it means auth failed
- if (result && !result.ok) {
- setError("Invalid email or password");
- }
} catch {
setError("An error occurred. Please try again.");
} finally {
diff --git a/src/app/(public)/navbar.tsx b/src/app/(public)/navbar.tsx
index f2f654c..b2b5676 100644
--- a/src/app/(public)/navbar.tsx
+++ b/src/app/(public)/navbar.tsx
@@ -2,8 +2,9 @@
import Link from "next/link";
import { useSession } from "next-auth/react";
-import { Button } from "@/components/ui/button";
+import { buttonVariants } from "@/components/ui/button";
import { ChartColumn } from "lucide-react";
+import { cn } from "@/lib/utils";
export function Navbar() {
const { data: session } = useSession();
@@ -19,17 +20,26 @@ export function Navbar() {
{session ? (
-
+
+ Go to App
+
) : (
<>
-
-
+
+ Log in
+
+
+ Get Started
+
>
)}
diff --git a/src/app/(public)/page.tsx b/src/app/(public)/page.tsx
index c347641..a8d8d9d 100644
--- a/src/app/(public)/page.tsx
+++ b/src/app/(public)/page.tsx
@@ -1,5 +1,6 @@
import Link from "next/link";
-import { Button } from "@/components/ui/button";
+import { buttonVariants } from "@/components/ui/button";
+import { cn } from "@/lib/utils";
import { Navbar } from "./navbar";
import {
ChartColumn,
@@ -36,19 +37,18 @@ export default function LandingPage() {
keyboard-driven workspace.
-
-
+ Start Annotating
+
+
+ Try Demo
+
@@ -162,11 +162,12 @@ export default function LandingPage() {
No credit card required. Start annotating charts in seconds.
-
+
+ Create Free Account
+
{/* Footer */}
diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts
index 2e06971..7c62e2d 100644
--- a/src/app/api/auth/[...nextauth]/route.ts
+++ b/src/app/api/auth/[...nextauth]/route.ts
@@ -1 +1,2 @@
-export { GET, POST } from "@/auth";
+import { handlers } from "@/auth";
+export const { GET, POST } = handlers;
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000..f62edea
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,79 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
index 1edbe95..51cfa0f 100644
--- a/src/lib/auth.ts
+++ b/src/lib/auth.ts
@@ -1,6 +1,10 @@
import { auth } from "@/auth";
-export async function getAuthUser() {
+export type AuthUser = { id: string; name?: string | null; email?: string | null; image?: string | null };
+
+export async function getAuthUser(): Promise {
const session = await auth();
- return session?.user ?? null;
+ const user = session?.user;
+ if (!user?.id) return null;
+ return user as AuthUser;
}
diff --git a/src/middleware.ts b/src/middleware.ts
deleted file mode 100644
index 1509671..0000000
--- a/src/middleware.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { auth } from "@/auth";
-import { NextResponse } from "next/server";
-
-export const middleware = auth((req) => {
- const { pathname } = req.nextUrl;
- const isAuthenticated = !!req.auth;
-
- // Protect /api/* except /api/auth/* and /api/health
- if (pathname.startsWith("/api/")) {
- const isAuthRoute = pathname.startsWith("/api/auth/");
- const isHealthRoute = pathname === "/api/health";
-
- if (!isAuthRoute && !isHealthRoute && !isAuthenticated) {
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
- }
-
- return NextResponse.next();
- }
-
- // Redirect authenticated users away from /login and /register
- if (isAuthenticated && (pathname === "/login" || pathname === "/register")) {
- return NextResponse.redirect(new URL("/app", req.nextUrl.origin));
- }
-
- // Protect /app/* routes — redirect unauthenticated users to /login
- if (pathname.startsWith("/app") && !isAuthenticated) {
- return NextResponse.redirect(new URL("/login", req.nextUrl.origin));
- }
-
- return NextResponse.next();
-});
-
-export const config = {
- matcher: [
- /*
- * Match all request paths except:
- * - _next/static (static files)
- * - _next/image (image optimisation)
- * - favicon.ico / favicon.png / favicon.svg
- * - public assets (top-level files that are not pages)
- */
- "/((?!_next/static|_next/image|favicon\\.ico|favicon\\.png|favicon\\.svg).*)",
- ],
-};
diff --git a/src/proxy.ts b/src/proxy.ts
index f97c340..e30ab0f 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -1,42 +1,53 @@
+import { auth } from "@/auth";
import { NextResponse } from "next/server";
-import type { NextRequest } from "next/server";
-export function proxy(request: NextRequest) {
- const { pathname } = request.nextUrl;
+export const proxy = auth((req) => {
+ const { pathname } = req.nextUrl;
+ const isAuthenticated = !!req.auth;
+
+ // Protect /api/* except /api/auth/* and /api/health
+ if (pathname.startsWith("/api/")) {
+ const isAuthRoute = pathname.startsWith("/api/auth/");
+ const isHealthRoute = pathname === "/api/health";
+
+ if (!isAuthRoute && !isHealthRoute && !isAuthenticated) {
+ // Allow external clients with a valid API key
+ const apiKey = process.env.API_KEY;
+ if (apiKey) {
+ const requestApiKey = req.headers.get("X-API-Key");
+ if (requestApiKey === apiKey) {
+ return NextResponse.next();
+ }
+ }
+
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ }
- // Skip auth check for the health endpoint
- if (pathname === "/api/health") {
return NextResponse.next();
}
- const apiKey = process.env.API_KEY;
-
- // If API_KEY is not configured, skip auth check (fail-open for development)
- if (!apiKey) {
- console.warn(
- "Warning: API_KEY environment variable is not set. API authentication is disabled."
- );
- return NextResponse.next();
+ // Redirect authenticated users away from /login and /register
+ if (isAuthenticated && (pathname === "/login" || pathname === "/register")) {
+ return NextResponse.redirect(new URL("/app", req.nextUrl.origin));
}
- const requestApiKey = request.headers.get("X-API-Key");
-
- // Allow same-origin browser requests (UI -> /api/*) without exposing API_KEY to client JS.
- // Keep API key auth for non-browser/external clients.
- const fetchSite = request.headers.get("sec-fetch-site");
- const isSameOriginBrowserRequest = fetchSite === "same-origin";
-
- if (isSameOriginBrowserRequest) {
- return NextResponse.next();
- }
-
- if (!requestApiKey || requestApiKey !== apiKey) {
- return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ // Protect /app/* routes — redirect unauthenticated users to /login
+ if (pathname.startsWith("/app") && !isAuthenticated) {
+ return NextResponse.redirect(new URL("/login", req.nextUrl.origin));
}
return NextResponse.next();
-}
+});
export const config = {
- matcher: ["/api/:path*"],
+ matcher: [
+ /*
+ * Match all request paths except:
+ * - _next/static (static files)
+ * - _next/image (image optimisation)
+ * - favicon.ico / favicon.png / favicon.svg
+ * - public assets (top-level files that are not pages)
+ */
+ "/((?!_next/static|_next/image|favicon\\.ico|favicon\\.png|favicon\\.svg).*)",
+ ],
};