Files
edbridge-scholars/src/pages/auth/Login.tsx

730 lines
26 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect } from "react";
import type { FormEvent } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import { useAuthStore } from "../../stores/authStore";
import { Loader2, Mail, Lock, Target, Clock, BarChart2 } from "lucide-react";
interface LocationState {
from?: { pathname: string };
}
const STYLES = `
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700;800;900&family=Nunito+Sans:wght@400;600;700&display=swap');
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
.lg-root {
min-height: 100vh;
display: flex;
font-family: 'Nunito', sans-serif;
background: #fffbf4;
}
/* ─── LEFT PANEL ─── */
.lg-left {
position: relative;
width: 50%;
min-height: 100vh;
background: linear-gradient(160deg, #060d1f 0%, #0f2044 40%, #0e3476 75%, #1a56c4 100%);
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
padding: 3rem 3.5rem;
overflow: hidden;
flex-shrink: 0;
}
/* Animated grid */
.lg-grid {
position: absolute; inset: 0; pointer-events: none;
background-image:
linear-gradient(rgba(99,179,255,0.06) 1px, transparent 1px),
linear-gradient(90deg, rgba(99,179,255,0.06) 1px, transparent 1px);
background-size: 52px 52px;
animation: gridScroll 25s linear infinite;
}
@keyframes gridScroll {
from { background-position: 0 0; }
to { background-position: 52px 52px; }
}
/* Radial glow spots */
.lg-glow {
position: absolute; pointer-events: none; border-radius: 50%;
filter: blur(60px);
}
.lg-glow-1 { width: 380px; height: 380px; background: #1d4ed8; opacity: 0.35; top: -120px; right: -80px; animation: glowPulse 8s ease-in-out infinite; }
.lg-glow-2 { width: 280px; height: 280px; background: #0ea5e9; opacity: 0.2; bottom: -60px; left: -60px; animation: glowPulse 10s ease-in-out infinite 2s; }
.lg-glow-3 { width: 200px; height: 200px; background: #f97316; opacity: 0.12; top: 55%; left: 55%; animation: glowPulse 12s ease-in-out infinite 1s; }
@keyframes glowPulse {
0%,100% { transform: scale(1); opacity: 0.2; }
50% { transform: scale(1.2); opacity: 0.35; }
}
/* ── Floating score card ── */
.lg-score-card {
position: absolute;
top: 9%; right: 6%;
width: 162px;
background: linear-gradient(135deg, rgba(255,255,255,0.11), rgba(255,255,255,0.05));
border: 1px solid rgba(255,255,255,0.18);
border-radius: 20px;
padding: 1.1rem 1.2rem 1rem;
backdrop-filter: blur(16px);
box-shadow: 0 8px 32px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.15);
animation: floatA 6s ease-in-out infinite;
z-index: 2;
}
@keyframes floatA {
0%,100% { transform: translateY(0) rotate(-1.5deg); }
50% { transform: translateY(-14px) rotate(0.5deg); }
}
.lg-score-tag {
font-size: 0.58rem; font-weight: 800; letter-spacing: 0.14em;
text-transform: uppercase; color: #7dd3fc; margin-bottom: 0.5rem;
display: flex; align-items: center; gap: 0.3rem;
}
.lg-score-tag::before { content:''; width:6px;height:6px;border-radius:50%;background:#34d399;display:inline-block;box-shadow:0 0 6px #34d399; }
.lg-score-num {
font-size: 2.2rem; font-weight: 900; color: white; line-height: 1; margin-bottom: 0.2rem;
text-shadow: 0 2px 12px rgba(99,179,255,0.4);
}
.lg-score-delta {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.7rem; font-weight: 700; color: #4ade80;
display: flex; align-items: center; gap: 0.2rem; margin-bottom: 0.75rem;
}
.lg-bars {
display: flex; align-items: flex-end; gap: 3px; height: 40px;
}
.lg-bar {
flex: 1; border-radius: 3px 3px 0 0;
animation: barGrow 1s cubic-bezier(0.34,1.56,0.64,1) both;
transform-origin: bottom;
}
@keyframes barGrow {
from { transform: scaleY(0); }
to { transform: scaleY(1); }
}
/* ── Streak pill ── */
.lg-streak {
position: absolute;
bottom: 12%; left: 5%;
background: linear-gradient(135deg, rgba(249,115,22,0.18), rgba(239,68,68,0.1));
border: 1px solid rgba(249,115,22,0.4);
border-radius: 100px;
padding: 0.65rem 1.2rem;
backdrop-filter: blur(14px);
box-shadow: 0 4px 20px rgba(249,115,22,0.2), inset 0 1px 0 rgba(255,255,255,0.1);
display: flex; align-items: center; gap: 0.65rem;
animation: floatB 7s ease-in-out infinite 1s;
z-index: 2;
}
@keyframes floatB {
0%,100% { transform: translateY(0) rotate(1deg); }
50% { transform: translateY(-10px) rotate(-0.5deg); }
}
.lg-streak-fire { font-size: 1.5rem; filter: drop-shadow(0 0 8px #f97316); }
.lg-streak-text strong { display:block; font-size:0.85rem; font-weight:900; color:white; }
.lg-streak-text span { font-family:'Nunito Sans',sans-serif; font-size:0.68rem; font-weight:600; color:#fed7aa; }
/* ── Questions badge ── */
.lg-q-badge {
position: absolute;
bottom: 28%; right: 5%;
background: linear-gradient(135deg, rgba(139,92,246,0.18), rgba(99,102,241,0.1));
border: 1px solid rgba(139,92,246,0.4);
border-radius: 16px;
padding: 0.7rem 1.05rem;
backdrop-filter: blur(14px);
box-shadow: 0 4px 20px rgba(139,92,246,0.2), inset 0 1px 0 rgba(255,255,255,0.1);
animation: floatA 9s ease-in-out infinite 0.5s;
z-index: 2;
}
.lg-q-badge p { font-family:'Nunito Sans',sans-serif; font-size:0.68rem; font-weight:700; color:#c4b5fd; margin-bottom:0.15rem; }
.lg-q-badge strong { font-size:1.35rem; font-weight:900; color:white; display:block; text-shadow:0 0 20px rgba(167,139,250,0.5); }
/* ── Accuracy ring ── */
.lg-ring-wrap {
position: absolute;
top: 52%; left: 6%;
width: 80px; height: 80px;
animation: floatB 10s ease-in-out infinite 0.8s;
z-index: 2;
}
.lg-ring-svg { width: 80px; height: 80px; transform: rotate(-90deg); }
.lg-ring-bg { fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 5; }
.lg-ring-fill { fill: none; stroke: #34d399; stroke-width: 5; stroke-linecap: round;
stroke-dasharray: 188; stroke-dashoffset: 18;
animation: ringFill 1.8s ease both 0.3s; }
@keyframes ringFill {
from { stroke-dashoffset: 188; }
to { stroke-dashoffset: 18; }
}
.lg-ring-label {
position: absolute; inset: 0;
display: flex; flex-direction: column; align-items: center; justify-content: center;
}
.lg-ring-label strong { font-size: 0.95rem; font-weight: 900; color: white; line-height: 1; }
.lg-ring-label span { font-family:'Nunito Sans',sans-serif; font-size: 0.52rem; font-weight: 700; color: #7dd3fc; text-transform: uppercase; letter-spacing: 0.05em; }
/* Scattered glitter dots */
.lg-glitter { position: absolute; border-radius: 50%; pointer-events: none; animation: glitterFloat 8s ease-in-out infinite; }
@keyframes glitterFloat {
0%,100% { transform: translateY(0) scale(1); opacity: 0.5; }
50% { transform: translateY(-16px) scale(1.3); opacity: 0.9; }
}
/* Stars */
.lg-star { position: absolute; pointer-events: none; animation: starTwinkle 2.5s ease-in-out infinite; color: #fde68a; }
@keyframes starTwinkle {
0%,100% { opacity: 0.4; transform: scale(0.9) rotate(0deg); }
50% { opacity: 1; transform: scale(1.4) rotate(20deg); }
}
/* Thin decorative rings */
.lg-deco-ring {
position: absolute; border-radius: 50%; pointer-events: none;
border: 1.5px solid rgba(255,255,255,0.07);
animation: decoSpin 40s linear infinite;
}
@keyframes decoSpin { to { transform: rotate(360deg); } }
/* Panel content */
.lg-panel-content {
position: relative; z-index: 3;
display: flex; flex-direction: column;
align-items: flex-start; gap: 2rem;
width: 100%;
}
.lg-panel-logo { display: flex; align-items: center; gap: 0.75rem; }
.lg-panel-logo-badge {
width: 46px; height: 46px; border-radius: 13px;
background: linear-gradient(135deg, #f97316, #ef4444);
display: flex; align-items: center; justify-content: center;
box-shadow: 0 5px 0 rgba(0,0,0,0.3), 0 8px 20px rgba(249,115,22,0.45);
font-size: 1.3rem;
}
.lg-panel-logo-text { font-size: 1.35rem; font-weight: 900; color: white; letter-spacing: -0.02em; }
.lg-panel-headline { display: flex; flex-direction: column; gap: 0.6rem; }
.lg-panel-headline h2 {
font-size: 2.6rem; font-weight: 900; line-height: 1.1;
color: white; letter-spacing: -0.035em;
text-shadow: 0 4px 24px rgba(0,0,0,0.3);
}
.lg-panel-headline h2 span {
background: linear-gradient(90deg, #fbbf24 0%, #f97316 60%, #ef4444 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
filter: drop-shadow(0 0 16px rgba(249,115,22,0.4));
}
.lg-panel-headline p {
font-family: 'Nunito Sans', sans-serif;
font-size: 0.92rem; font-weight: 600; color: #93c5fd; line-height: 1.65;
}
/* Stats row */
.lg-stats { display: flex; gap: 0.75rem; width: 100%; }
.lg-stat {
flex: 1;
background: linear-gradient(135deg, rgba(255,255,255,0.09), rgba(255,255,255,0.04));
border: 1px solid rgba(255,255,255,0.13);
border-radius: 18px; padding: 0.9rem 0.85rem;
backdrop-filter: blur(12px);
box-shadow: 0 4px 16px rgba(0,0,0,0.25), inset 0 1px 0 rgba(255,255,255,0.12);
display: flex; flex-direction: column; gap: 0.2rem;
animation: statSlide 0.5s ease both;
}
.lg-stat:nth-child(1) { animation-delay: 0.1s; }
.lg-stat:nth-child(2) { animation-delay: 0.2s; }
.lg-stat:nth-child(3) { animation-delay: 0.3s; }
@keyframes statSlide {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
.lg-stat-icon { width: 28px; height: 28px; border-radius: 8px; display:flex;align-items:center;justify-content:center; margin-bottom:0.3rem; }
.lg-stat strong { font-size: 1.3rem; font-weight: 900; color: white; line-height: 1; }
.lg-stat span { font-family:'Nunito Sans',sans-serif; font-size:0.64rem; font-weight:600; color:#93c5fd; }
/* Social proof */
.lg-social { display: flex; align-items: center; gap: 0.75rem; }
.lg-avs { display: flex; }
.lg-av {
width: 30px; height: 30px; border-radius: 50%;
border: 2px solid #0f2044; margin-left: -8px;
display: flex; align-items: center; justify-content: center;
font-size: 0.6rem; font-weight: 800; color: white;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.lg-av:first-child { margin-left: 0; }
.lg-social p { font-family:'Nunito Sans',sans-serif; font-size:0.75rem; font-weight:700; color:#93c5fd; }
.lg-social p strong { color:#fbbf24; }
/* ─── RIGHT PANEL ─── */
.lg-right {
flex: 1;
display: flex; align-items: center; justify-content: center;
padding: 3rem 4rem;
position: relative; overflow: hidden;
}
.lg-bg-dot { position:absolute;border-radius:50%;pointer-events:none;opacity:0.09;animation:bgFloat 10s ease-in-out infinite; }
@keyframes bgFloat { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-14px);} }
.lg-form-wrap {
position: relative; z-index: 1;
width: 100%; max-width: 400px;
display: flex; flex-direction: column; gap: 2rem;
animation: formPop 0.55s cubic-bezier(0.34,1.56,0.64,1) both;
}
@keyframes formPop {
from { opacity:0; transform:translateY(22px) scale(0.97); }
to { opacity:1; transform:translateY(0) scale(1); }
}
.lg-form-header { display:flex;flex-direction:column;gap:0.4rem; }
.lg-form-header h1 { font-size:2rem;font-weight:900;color:#1e1b4b;letter-spacing:-0.03em;line-height:1.2; }
.lg-form-header p { font-family:'Nunito Sans',sans-serif;font-size:0.88rem;font-weight:600;color:#9ca3af; }
.lg-fields { display:flex;flex-direction:column;gap:1.1rem; }
.lg-field { display:flex;flex-direction:column;gap:0.4rem; }
.lg-label { font-size:0.7rem;font-weight:800;letter-spacing:0.1em;text-transform:uppercase;color:#6b7280;padding-left:0.2rem; }
.lg-input-wrap { position:relative; }
.lg-input-icon { position:absolute;left:0.9rem;top:50%;transform:translateY(-50%);pointer-events:none;color:#9ca3af;transition:color 0.2s; }
.lg-input {
width:100%;padding:0.9rem 1rem 0.9rem 2.65rem;
background:#f9fafb;border:2.5px solid #f3f4f6;border-radius:14px;
font-family:'Nunito Sans',sans-serif;font-size:0.9rem;font-weight:600;color:#1e1b4b;
outline:none;transition:all 0.2s;box-sizing:border-box;
}
.lg-input:focus { background:white;border-color:#93c5fd;box-shadow:0 0 0 3.5px rgba(59,130,246,0.1); }
.lg-input:disabled { opacity:0.5;cursor:not-allowed; }
.lg-input::placeholder { color:#d1d5db; }
.lg-remember { display:flex;align-items:center;gap:0.5rem;padding:0 0.1rem; }
.lg-checkbox { width:17px;height:17px;border-radius:5px;accent-color:#3b82f6;cursor:pointer;flex-shrink:0; }
.lg-remember-label { font-family:'Nunito Sans',sans-serif;font-size:0.8rem;font-weight:600;color:#6b7280;cursor:pointer; }
.lg-error {
background:#fff1f2;border:2px solid #fecdd3;border-radius:14px;padding:0.8rem 1rem;
font-family:'Nunito Sans',sans-serif;font-size:0.82rem;font-weight:700;color:#e11d48;
display:flex;align-items:center;gap:0.5rem;
}
.lg-success {
background:#f0fdf4;border:2px solid #bbf7d0;border-radius:14px;padding:0.8rem 1rem;
font-family:'Nunito Sans',sans-serif;font-size:0.82rem;font-weight:700;color:#15803d;
display:flex;align-items:center;gap:0.5rem;
animation: formPop 0.4s cubic-bezier(0.34,1.56,0.64,1) both;
}
.lg-btn {
width:100%;padding:1rem;background:#f97316;color:white;border:none;border-radius:100px;cursor:pointer;
font-family:'Nunito',sans-serif;font-size:1rem;font-weight:900;
display:flex;align-items:center;justify-content:center;gap:0.5rem;
box-shadow:0 6px 0 #c2560e,0 10px 24px rgba(249,115,22,0.28);
transition:transform 0.1s,box-shadow 0.1s;
}
.lg-btn:hover { transform:translateY(-2px);box-shadow:0 8px 0 #c2560e,0 14px 28px rgba(249,115,22,0.32); }
.lg-btn:active { transform:translateY(3px);box-shadow:0 3px 0 #c2560e; }
.lg-btn:disabled { background:#e5e7eb;color:#9ca3af;cursor:not-allowed;box-shadow:0 4px 0 #d1d5db; }
.lg-btn:disabled:hover { transform:none;box-shadow:0 4px 0 #d1d5db; }
.lg-spinner { animation:spin 0.8s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
.lg-footer { text-align:center;font-family:'Nunito Sans',sans-serif;font-size:0.75rem;font-weight:600;color:#9ca3af; }
.lg-signup-footer { text-align:center;font-family:'Nunito Sans',sans-serif;font-size:0.8rem;font-weight:600;color:#9ca3af; }
.lg-link { color:#f97316;font-weight:800;text-decoration:none;cursor:pointer; }
.lg-link:hover { color:#ea6c00; }
@media (max-width: 860px) {
.lg-left { display:none; }
.lg-right { padding:2rem 1.5rem; }
}
`;
const BAR_HEIGHTS = [30, 50, 42, 75, 55, 88, 65];
const BAR_COLORS = [
"#60a5fa",
"#60a5fa",
"#7dd3fc",
"#38bdf8",
"#60a5fa",
"#7dd3fc",
"#38bdf8",
];
const AV_COLORS = [
"linear-gradient(135deg,#3b82f6,#1d4ed8)",
"linear-gradient(135deg,#f97316,#ef4444)",
"linear-gradient(135deg,#22c55e,#15803d)",
"linear-gradient(135deg,#a855f7,#7c3aed)",
"linear-gradient(135deg,#eab308,#ca8a04)",
];
const AV_INITIALS = ["SK", "NR", "TM", "AB", "PL"];
export const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const navigate = useNavigate();
const location = useLocation();
const {
login,
isAuthenticated,
isLoading,
error,
clearError,
registrationMessage,
} = useAuthStore();
const from =
(location.state as LocationState)?.from?.pathname || "/student/home";
useEffect(() => {
if (isAuthenticated) navigate("/student/home", { replace: true });
}, [isAuthenticated, navigate]);
useEffect(() => {
return () => clearError();
}, [clearError]);
const handleSubmit = async (e: FormEvent<HTMLButtonElement>) => {
e.preventDefault();
clearError();
const success = await login({ email, password });
if (success) navigate(from, { replace: true });
};
if (isAuthenticated) return null;
return (
<div className="lg-root">
<style>{STYLES}</style>
{/* ── LEFT PANEL ── */}
<div className="lg-left">
{/* Background layers */}
<div className="lg-grid" />
<div className="lg-glow lg-glow-1" />
<div className="lg-glow lg-glow-2" />
<div className="lg-glow lg-glow-3" />
{/* Decorative spinning rings */}
{[
{ s: 260, t: "38%", l: "58%", mt: -130, ml: -130 },
{
s: 380,
t: "42%",
l: "54%",
mt: -190,
ml: -190,
dir: "reverse" as const,
},
].map((r, i) => (
<div
key={i}
className="lg-deco-ring"
style={
{
width: r.s,
height: r.s,
top: r.t,
left: r.l,
marginTop: r.mt,
marginLeft: r.ml,
animationDirection: r.dir ?? "normal",
animationDuration: `${35 + i * 10}s`,
} as React.CSSProperties
}
/>
))}
{/* Floating score card */}
<div className="lg-score-card">
<p className="lg-score-tag">SAT Score</p>
<p className="lg-score-num">1480</p>
<p className="lg-score-delta"> +120 pts this month</p>
<div className="lg-bars">
{BAR_HEIGHTS.map((h, i) => (
<div
key={i}
className="lg-bar"
style={{
height: `${h}%`,
background: BAR_COLORS[i],
animationDelay: `${i * 0.08}s`,
}}
/>
))}
</div>
</div>
{/* Accuracy ring */}
<div className="lg-ring-wrap">
<svg className="lg-ring-svg" viewBox="0 0 80 80">
<circle className="lg-ring-bg" cx="40" cy="40" r="30" />
<circle className="lg-ring-fill" cx="40" cy="40" r="30" />
</svg>
<div className="lg-ring-label">
<strong>94%</strong>
<span>Accuracy</span>
</div>
</div>
{/* Streak pill */}
<div className="lg-streak">
<span className="lg-streak-fire">🔥</span>
<div className="lg-streak-text">
<strong>14-day streak!</strong>
<span>Keep it going</span>
</div>
</div>
{/* Questions badge */}
<div className="lg-q-badge">
<p>Questions solved</p>
<strong>2,847</strong>
</div>
{/* Glitter dots */}
{[
{ s: 10, c: "#60a5fa", t: "13%", l: "58%", d: "0s", dur: "9s" },
{ s: 7, c: "#fbbf24", t: "70%", l: "70%", d: "1s", dur: "11s" },
{ s: 12, c: "#34d399", t: "38%", l: "7%", d: "0.4s", dur: "8s" },
{ s: 5, c: "#f472b6", t: "82%", l: "35%", d: "1.8s", dur: "13s" },
{ s: 8, c: "#a78bfa", t: "20%", l: "36%", d: "0.9s", dur: "10s" },
{ s: 6, c: "#fb923c", t: "62%", l: "80%", d: "1.3s", dur: "7s" },
].map((d, i) => (
<div
key={i}
className="lg-glitter"
style={
{
width: d.s,
height: d.s,
background: d.c,
top: d.t,
left: d.l,
animationDelay: d.d,
animationDuration: d.dur,
} as React.CSSProperties
}
/>
))}
{/* Stars */}
{[
{ t: "8%", l: "16%", s: "1.1rem", d: "0s" },
{ t: "22%", l: "74%", s: "0.85rem", d: "0.7s" },
{ t: "55%", l: "20%", s: "0.95rem", d: "1.4s" },
{ t: "86%", l: "58%", s: "0.75rem", d: "0.3s" },
{ t: "44%", l: "88%", s: "1rem", d: "1.0s" },
].map((s, i) => (
<span
key={i}
className="lg-star"
style={
{
top: s.t,
left: s.l,
fontSize: s.s,
animationDelay: s.d,
} as React.CSSProperties
}
>
</span>
))}
{/* Panel content */}
<div className="lg-panel-content">
<div className="lg-panel-logo">
<div className="lg-panel-logo-badge">📚</div>
<span className="lg-panel-logo-text">EdBridge</span>
</div>
<div className="lg-panel-headline">
<h2>
Welcome
<br />
back,
<br />
<span>champion.</span>
</h2>
<p>
Your SAT goals are waiting.
<br />
Pick up right where you left off.
</p>
</div>
<div className="lg-stats">
{[
{
icon: <Target size={14} color="#fff" />,
bg: "linear-gradient(135deg,#3b82f6,#1d4ed8)",
val: "94%",
label: "Accuracy",
},
{
icon: <Clock size={14} color="#fff" />,
bg: "linear-gradient(135deg,#f97316,#ef4444)",
val: "47m",
label: "Daily study",
},
{
icon: <BarChart2 size={14} color="#fff" />,
bg: "linear-gradient(135deg,#22c55e,#15803d)",
val: "+180",
label: "Score gain",
},
].map((s, i) => (
<div className="lg-stat" key={i}>
<div className="lg-stat-icon" style={{ background: s.bg }}>
{s.icon}
</div>
<strong>{s.val}</strong>
<span>{s.label}</span>
</div>
))}
</div>
<div className="lg-social">
<div className="lg-avs">
{AV_INITIALS.map((init, i) => (
<div
key={i}
className="lg-av"
style={{ background: AV_COLORS[i] }}
>
{init}
</div>
))}
</div>
<p>
<strong>2,400+</strong> students improved their scores
</p>
</div>
</div>
</div>
{/* ── RIGHT PANEL ── */}
<div className="lg-right">
{[
{ s: 200, c: "#f97316", t: "4%", r: "4%", d: "0s", dur: "12s" },
{ s: 120, c: "#3b82f6", b: "8%", l: "2%", d: "1.5s", dur: "10s" },
{ s: 70, c: "#22c55e", t: "52%", r: "2%", d: "0.8s", dur: "8s" },
].map((d, i) => (
<div
key={i}
className="lg-bg-dot"
style={
{
width: d.s,
height: d.s,
background: d.c,
top: (d as any).t,
right: (d as any).r,
bottom: (d as any).b,
left: (d as any).l,
animationDelay: d.d,
animationDuration: d.dur,
} as React.CSSProperties
}
/>
))}
<div className="lg-form-wrap">
<div className="lg-form-header">
<h1>Welcome back 👋</h1>
<p>Sign in to continue your SAT prep</p>
</div>
<div className="lg-fields">
<div className="lg-field">
<label className="lg-label" htmlFor="email">
Email
</label>
<div className="lg-input-wrap">
<Mail size={15} className="lg-input-icon" />
<input
id="email"
type="email"
className="lg-input"
placeholder="you@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="lg-field">
<label className="lg-label" htmlFor="password">
Password
</label>
<div className="lg-input-wrap">
<Lock size={15} className="lg-input-icon" />
<input
id="password"
type="password"
className="lg-input"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="lg-remember">
<input id="rememberMe" type="checkbox" className="lg-checkbox" />
<label htmlFor="rememberMe" className="lg-remember-label">
Keep me signed in
</label>
</div>
{registrationMessage && (
<div className="lg-success">
<span></span> {registrationMessage}
</div>
)}
{error && (
<div className="lg-error">
<span></span> {error}
</div>
)}
<button
className="lg-btn"
onClick={handleSubmit}
disabled={isLoading || !email || !password}
>
{isLoading ? (
<>
<Loader2 size={18} className="lg-spinner" /> Signing in...
</>
) : (
"Sign In →"
)}
</button>
</div>
<p className="lg-footer">
By signing in you agree to EdBridge's Terms & Privacy Policy.
</p>
<p className="lg-signup-footer">
Don't have an account?{" "}
<span className="lg-link" onClick={() => navigate("/register")}>
Sign up
</span>
</p>
</div>
</div>
</div>
);
};