Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
Binary file added public/SGA_Logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,17 @@ body {
color: var(--foreground);
font-family: Arial, Helvetica, sans-serif;
}

h1 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These are bare element selectors (h1, button) added outside the existing @layer base block above, so under CSS cascade-layer rules they'll take precedence over any Tailwind utility class on a button or h1 anywhere else in the app — not just this login page — and they don't participate in the dark-mode variables already defined earlier in this file. Scoping this to the two buttons on the login page (Tailwind utility classes, or a .btn-primary class) would avoid the app-wide side effect.

color: #C8102E;
}

button {
background-color: #C8102E;
color: white;
text-align: center;
}

button:hover {
background-color: #a8001e;
}
34 changes: 34 additions & 0 deletions src/app/login/EmailForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"use client";
import { useState } from "react";

interface Props {
onNext: () => void;
}

export function EmailForm({ onNext }: Props) {
const [email, setEmail] = useState("");

function handleSubmit(e: React.SubmitEvent) {
e.preventDefault();
onNext();
}

return (
<form onSubmit={handleSubmit} className="w-125 bg-white flex flex-col items-center justify-center p-8 text-xl rounded-4xl shadow-lg">
<input
className="w-full p-4 m-4 bg-gray-200 rounded-lg"
type="email"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Worth adding autoComplete="email" here (and autoComplete="one-time-code" on the OTP input in OtpForm.tsx) so browsers/password managers and mobile OTP autofill work as expected. Also, both inputs rely on placeholder alone with no <label>/aria-label — placeholder text isn't an accessible name substitute once the field has focus or a value.

placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button
className="w-80 p-2 pl-4 pr-4 m-4 rounded-4xl"
type="submit"
>
Send Code
</button>
</form>
);
}
54 changes: 54 additions & 0 deletions src/app/login/OtpForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";
import { useState, useEffect } from "react";

interface Props {
onResend: () => void;
}

export function OtpForm({ onResend }: Props) {
const [otp, setOtp] = useState("");
const [cooldown, setCooldown] = useState(15);

useEffect(() => {
if (cooldown <= 0) return;
const t = setTimeout(() => setCooldown((c) => c - 1), 1000);
return () => clearTimeout(t);
}, [cooldown]);

function handleSubmit(e: React.SubmitEvent) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Assuming this is intentional for a UI-only stub (nothing in this PR wires up sendOtp/verifyOtp yet, which tracks with the PR description), but flagging so it's not mistaken for a bug during review or a demo: this handler currently just calls preventDefault() with no state change and no onVerify/onSuccess prop for the parent to hook into, unlike EmailForm's equivalent handler which at least calls onNext().

e.preventDefault();
}

function handleResend() {
onResend();
setCooldown(15);
}

return (
<form onSubmit={handleSubmit} className="w-125 bg-white flex flex-col items-center justify-center p-8 text-xl rounded-4xl shadow-lg">
<input
className="w-full p-4 m-4 bg-gray-200 rounded-lg tracking-widest text-center"
type="text"
placeholder="XXXXXX"
maxLength={6}
value={otp}
onChange={(e) => setOtp(e.target.value)}
required
/>
<button
className="w-80 p-2 pl-4 pr-4 m-4 rounded-4xl"
type="submit"
>
Verify Code
</button>
<button
type="button"
onClick={handleResend}
disabled={cooldown > 0}
className="text-sm text-gray-400 disabled:opacity-50"
>
{cooldown > 0 ? `Resend code in ${cooldown}s` : "Resend code"}
</button>
</form>
);
}
27 changes: 27 additions & 0 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";
import Image from "next/image";
import { useState } from "react";
import { EmailForm } from "./EmailForm";
import { OtpForm } from "./OtpForm";

type Step = "email" | "otp";

export default function LoginPage() {
const [step, setStep] = useState<Step>("email");

return (
<main className="min-h-screen bg-gradient-to-b from-white to-red-200">
<div className="flex flex-col items-center justify-center">
<Image className="m-8" src="/SGA_logo.png" alt="Logo" width={350} height={150} style={{ height: "auto" }} loading="eager" />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Small but will bite in prod: this references /SGA_logo.png (lowercase "l"), but the asset this PR adds is public/SGA_Logo.png (capital "L"). Works locally since most dev filesystems are case-insensitive, but Vercel/Linux serves public/ case-sensitively, so this 404s once deployed.

<div className="flex flex-col items-center justify-center pt-4">
<h1 className="font-bold text-4xl m-8">Login</h1>
{step === "email" ? (
<EmailForm onNext={() => setStep("otp")} />
) : (
<OtpForm onResend={() => {}} />
)}
</div>
</div>
</main>
);
}