source: frontend/src/app/page.tsx

Last change on this file was b8093a0, checked in by imbrsk <boris696boris@…>, 3 days ago

Merge iknow-remaster into frontend/

Bring the Next.js frontend into this repository as a monorepo subdirectory,
preserving its full commit history via a subtree merge.

  • Property mode set to 100644
File size: 10.4 KB
RevLine 
[6b8332f]1"use client"
2
3import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4import {
5 faUser,
6 faLock,
7 faEye,
8 faEyeSlash,
9 faSignInAlt,
10 faGraduationCap
11} from '@fortawesome/free-solid-svg-icons';
12import { useState } from 'react';
[b67f274]13import { login } from '@/lib/auth';
[50f2fb4]14import { useTranslation } from 'react-i18next';
15import LanguageSwitcher from '@/components/LanguageSwitcher';
[6b8332f]16
17export default function LoginPage() {
[50f2fb4]18 const { t } = useTranslation();
[6b8332f]19 const [showPassword, setShowPassword] = useState(false);
[b67f274]20 const [isSubmitting, setIsSubmitting] = useState(false);
21 const [errorMessage, setErrorMessage] = useState<string | null>(null);
[6b8332f]22 const [formData, setFormData] = useState({
[b67f274]23 email: '',
[6b8332f]24 password: ''
25 });
26
27 const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
28 const { name, value } = e.target;
29 setFormData(prev => ({
30 ...prev,
31 [name]: value
32 }));
33 };
34
[b67f274]35 const handleSubmit = async (e: React.FormEvent) => {
[6b8332f]36 e.preventDefault();
[b67f274]37 setErrorMessage(null);
38 setIsSubmitting(true);
39 try {
[298f9b3]40 const session = await login({ email: formData.email, password: formData.password });
[8496f3c]41 // Admins previously fell through to /students, where they have no
42 // enrolments and every page renders empty.
43 const home =
44 session.role === 'Professor' ? '/professor'
45 : session.role === 'Admin' ? '/admin'
46 : '/students';
47 window.location.href = home;
[b67f274]48 } catch (err) {
49 setErrorMessage(err instanceof Error ? err.message : 'Login failed.');
50 } finally {
51 setIsSubmitting(false);
52 }
[6b8332f]53 };
[65f7b64]54
55 return (
[6b8332f]56 <div className="container mx-auto px-4">
57 <div className="min-h-screen ">
58 {/* Login Form */}
59 <div className="flex items-center justify-center min-h-[calc(100vh-160px)] p-4">
[90f7842]60 <div className="w-full max-w-5xl">
61 <div className="grid grid-cols-1 gap-6 lg:grid-cols-[24rem_28rem_24rem] lg:items-start lg:justify-center">
62 {/* Sticky Notes */}
63 <div className="w-full flex flex-col gap-4">
[ba52069]64 <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
65 <div className="text-sm font-bold text-card-foreground mb-2">{t('login_student_title')}</div>
[4fe4582]66 <div className="text-xs text-card-foreground whitespace-pre-wrap">
[50f2fb4]67 {t('login_student_example')}
[90f7842]68 </div>
[4fe4582]69 <div className="mt-3 text-xs text-blue-600 font-medium">
70 {t('login_demo_note')}
[90f7842]71 </div>
72 </div>
73
[ba52069]74 <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
75 <div className="text-sm font-bold text-card-foreground mb-2">{t('login_professor_title')}</div>
[4fe4582]76 <div className="text-xs text-card-foreground whitespace-pre-wrap">
[50f2fb4]77 {t('login_professor_example')}
[90f7842]78 </div>
[4fe4582]79 <div className="mt-3 text-xs text-blue-600 font-medium">
80 {t('login_demo_note')}
81 </div>
[90f7842]82 </div>
[8496f3c]83
84 <div className="bg-yellow-500/10 border border-yellow-500/20 rounded-xl shadow-sm p-5">
85 <div className="text-sm font-bold text-card-foreground mb-2">{t('login_admin_title')}</div>
86 <div className="text-xs text-card-foreground whitespace-pre-wrap">
87 {t('login_admin_example')}
88 </div>
89 <div className="mt-3 text-xs text-blue-600 font-medium">
90 {t('login_demo_note')}
91 </div>
92 </div>
[90f7842]93 </div>
94
95 {/* Login Card Column */}
96 <div className="w-full max-w-md lg:max-w-none lg:w-[28rem] mx-auto">
97 {/* Login Card */}
[ba52069]98 <div className="bg-card rounded-2xl shadow-xl border border-border overflow-hidden">
[90f7842]99 {/* Header */}
100 <div className="bg-primary text-white px-8 py-6 text-center">
[6b8332f]101 <div className="mb-4">
[ba52069]102 <div className="inline-flex items-center justify-center w-16 h-16 bg-white rounded-full shadow-lg">
103 <FontAwesomeIcon icon={faUser} className="text-2xl text-[#0272D1]" />
[6b8332f]104 </div>
105 </div>
[50f2fb4]106 <h1 className="text-2xl font-bold mb-2">{t('login_welcome')}</h1>
[6b8332f]107 <p className="text-blue-100 text-sm">
[50f2fb4]108 {t('login_subtitle')}
[6b8332f]109 </p>
110 </div>
111
112 {/* Form */}
113 <div className="p-8">
[50f2fb4]114 {/* Language Switcher above email field, centered */}
115 <div className="flex justify-center mb-6">
116 <LanguageSwitcher />
117 </div>
[6b8332f]118 <form onSubmit={handleSubmit} className="space-y-6">
[b67f274]119 {/* Email Field */}
[6b8332f]120 <div>
[ba52069]121 <label htmlFor="email" className="block text-sm font-medium text-muted-foreground mb-2">
[50f2fb4]122 {t('login_email_label')}
[6b8332f]123 </label>
124 <div className="relative">
125 <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
[ba52069]126 <FontAwesomeIcon icon={faUser} className="h-4 w-4 text-muted-foreground" />
[6b8332f]127 </div>
128 <input
[b67f274]129 id="email"
130 name="email"
131 type="email"
[6b8332f]132 required
[b67f274]133 value={formData.email}
[6b8332f]134 onChange={handleInputChange}
[ba52069]135 className="w-full pl-10 pr-4 py-3 border border-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors"
[50f2fb4]136 placeholder={t('login_email_placeholder')}
[6b8332f]137 />
138 </div>
139 </div>
140
141 {/* Password Field */}
142 <div>
[ba52069]143 <label htmlFor="password" className="block text-sm font-medium text-muted-foreground mb-2">
[50f2fb4]144 {t('login_password_label')}
[6b8332f]145 </label>
146 <div className="relative">
147 <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
[ba52069]148 <FontAwesomeIcon icon={faLock} className="h-4 w-4 text-muted-foreground" />
[6b8332f]149 </div>
150 <input
151 id="password"
152 name="password"
153 type={showPassword ? "text" : "password"}
154 required
155 value={formData.password}
156 onChange={handleInputChange}
[ba52069]157 className="w-full pl-10 pr-12 py-3 border border-input rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary transition-colors"
[50f2fb4]158 placeholder={t('login_password_placeholder')}
[6b8332f]159 />
160 <button
161 type="button"
162 onClick={() => setShowPassword(!showPassword)}
[ba52069]163 className="absolute inset-y-0 right-0 pr-3 flex items-center text-muted-foreground hover:text-foreground transition-colors"
[6b8332f]164 >
165 <FontAwesomeIcon
166 icon={showPassword ? faEyeSlash : faEye}
167 className="h-4 w-4"
168 />
169 </button>
170 </div>
171 </div>
172
173 {/* Remember Me & Forgot Password */}
174 <div className="flex items-center justify-between">
175 <div className="flex items-center">
176 <input
177 id="remember"
178 name="remember"
179 type="checkbox"
[ba52069]180 className="h-4 w-4 text-primary focus:ring-primary border-input rounded"
[6b8332f]181 />
[ba52069]182 <label htmlFor="remember" className="ml-2 block text-sm text-muted-foreground">
[50f2fb4]183 {t('login_remember_me')}
[6b8332f]184 </label>
185 </div>
186 <button
187 type="button"
188 className="text-sm text-primary hover:text-blue-700 font-medium transition-colors"
189 >
[50f2fb4]190 {t('login_forgot_password')}
[6b8332f]191 </button>
192 </div>
193
[b67f274]194 {errorMessage && (
[ba52069]195 <div className="rounded-lg border border-destructive/20 bg-destructive/10 px-4 py-3 text-sm text-destructive">
[50f2fb4]196 {t(errorMessage) || errorMessage}
[b67f274]197 </div>
198 )}
199
[6b8332f]200 {/* Submit Button */}
201 <button
202 type="submit"
[b67f274]203 disabled={isSubmitting}
204 className="w-full bg-primary hover:bg-blue-700 disabled:opacity-60 disabled:hover:bg-primary text-white font-medium py-3 px-4 rounded-lg transition-colors duration-200 flex items-center justify-center gap-2 shadow-lg hover:shadow-xl"
[6b8332f]205 >
206 <FontAwesomeIcon icon={faSignInAlt} className="h-4 w-4" />
[50f2fb4]207 {isSubmitting ? t('login_button_loading') : t('login_button')}
[6b8332f]208 </button>
209 </form>
210 </div>
211
212 {/* Footer */}
[ba52069]213 <div className="bg-accent px-8 py-4 border-t border-border">
214 <p className="text-center text-sm text-muted-foreground">
[50f2fb4]215 {t('login_no_account')}{' '}
[6b8332f]216 <button className="text-primary hover:text-blue-700 font-medium transition-colors">
[50f2fb4]217 {t('login_contact_admin')}
[6b8332f]218 </button>
219 </p>
220 </div>
[90f7842]221 </div>
[6b8332f]222
[90f7842]223 {/* Additional Info */}
224 <div className="mt-6 text-center">
[ba52069]225 <div className="inline-flex items-center gap-2 px-4 py-2 bg-card bg-opacity-80 rounded-lg shadow-sm">
[90f7842]226 <FontAwesomeIcon icon={faGraduationCap} className="text-primary" />
[ba52069]227 <span className="text-sm text-muted-foreground">
[50f2fb4]228 {t('login_footer')}
[90f7842]229 </span>
230 </div>
231 </div>
232 </div>
233
234 {/* Right spacer column (keeps login centered) */}
235 <div className="hidden lg:block" />
[6b8332f]236 </div>
237 </div>
238 </div>
239 </div>
240 </div>
[65f7b64]241 );
[6b8332f]242}
Note: See TracBrowser for help on using the repository browser.