Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions client/src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"use client";

import { useRouter } from "next/navigation";
import { FormEvent, useState } from "react";

import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import api, { setAccessToken } from "@/lib/api";
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function 'setAccessToken' is imported from '@/lib/api' but doesn't appear to be exported from that module. This will cause an import error and the login functionality will not work. You need to implement and export this function in the api module.

Copilot uses AI. Check for mistakes.

/**
* - POST /users/login/ with username/password
* - Backend returns access token in JSON and sets refresh token cookie
* - Store access token in memory via setAccessToken()
* - Redirect to home page on success
* - Show error message on failure
*/
export default function LoginPage() {
const router = useRouter();

const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use zod and react-hook-form library instead of useState for the form field


const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);

async function handleSubmit(e: FormEvent) {
e.preventDefault();
setError(null);

Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused variable setUsername.

Suggested change
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: FormEvent) {
e.preventDefault();
setError(null);
const [password, setPassword] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
setError(null);
const form = e.currentTarget;
const formData = new FormData(form);
const username =
String(
formData.get("username") ??
formData.get("email") ??
""
).trim();

Copilot uses AI. Check for mistakes.
if (!username || !password) {
setError("Please enter both username/email and password.");
return;
}

setIsSubmitting(true);
try {
// Uses axios instance with withCredentials: true
// so backend can set refresh cookie on login.
const res = await api.post("/users/login/", { username, password });

// Expecting { access: "..." } (common JWT pattern)
const access: string | undefined =
res.data?.access || res.data?.access_token || res.data?.token;

if (!access) {
throw new Error("Login succeeded but no access token was returned.");
}

// Store access token in-memory for subsequent API calls
setAccessToken(access);

// Redirect to home page
router.push("/");
router.refresh();
} catch (err: any) {
// Axios error shape:
// err.response?.data?.detail / message etc
const message =
err?.response?.data?.detail ||
err?.response?.data?.message ||
err?.message ||
"Login failed. Please check your credentials.";
setError(message);
} finally {
setIsSubmitting(false);
}
}

return (
<main className="flex min-h-screen items-center justify-center bg-[#f5f5f7] px-4">
{/* Login box */}
<div className="w-full max-w-md rounded-3xl border border-slate-100 bg-white px-10 py-12 shadow-md">
<h1 className="mb-2 text-center text-2xl font-semibold text-slate-900">
Login
</h1>
<p className="mb-8 text-center text-sm text-slate-600">
Welcome back! Please enter your account details.
</p>

<form onSubmit={handleSubmit} className="space-y-6">
{/* Email */}
<div className="space-y-1">
<Label
htmlFor="email"
className="text-sm font-medium text-slate-800"
>
Username / Email
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label says "Username / Email" (with spaces around the slash) but the placeholder says "Enter Username/ Email" (with inconsistent spacing). These should be consistent for better user experience.

Copilot uses AI. Check for mistakes.
</Label>
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The state variable is named 'username' (line 21) but the input is using an undefined variable 'email'. This will cause a runtime error. The value and onChange handler should reference 'username' instead of 'email', or the state variable should be renamed to 'email'.

Suggested change
value={email}
onChange={(e) => setEmail(e.target.value)}
value={username}
onChange={(e) => setUsername(e.target.value)}

Copilot uses AI. Check for mistakes.
placeholder="Enter Username/ Email"
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an inconsistency in spacing. The placeholder should either be "Enter Username/Email" (no space before slash) or "Enter Username / Email" (spaces around slash) for consistency.

Suggested change
placeholder="Enter Username/ Email"
placeholder="Enter Username / Email"

Copilot uses AI. Check for mistakes.
autoComplete="email"
className="h-11 border border-slate-200 shadow-[0_2px_0_rgba(148,163,184,0.5)] placeholder:text-slate-400"
/>
</div>

{/* Password */}
<div className="space-y-1">
<Label
htmlFor="password"
className="text-sm font-medium text-slate-800"
>
Password
</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Enter Password"
autoComplete="current-password"
className="h-11 border border-slate-200 shadow-[0_2px_0_rgba(148,163,184,0.5)] placeholder:text-slate-400"
/>
<div className="mt-1 flex justify-end">
<button
type="button"
className="text-xs text-slate-500 underline-offset-2 hover:underline"
Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Forgot Password?" button doesn't have any functionality or navigation. It should either be removed if not implemented yet, or implement the forgot password functionality, or at least provide a disabled state or proper link to indicate it's not yet available.

Suggested change
className="text-xs text-slate-500 underline-offset-2 hover:underline"
disabled
aria-disabled="true"
title="Forgot password functionality is not available yet"
className="text-xs text-slate-400 cursor-not-allowed"

Copilot uses AI. Check for mistakes.
>
Forgot Password?
</button>
</div>
</div>

{/* Error */}
{error && (
<p className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700">
{error}
</p>
)}

{/* Login button */}
<div className="flex justify-center pt-4">
<Button
type="submit"
variant="confirm"
size="lg"
disabled={isSubmitting}
className="rounded-full px-10"
>
{isSubmitting ? "Logging in..." : "Login"}
</Button>
</div>
</form>
</div>
</main>
);
}
Loading
Loading