samar m commited on
Commit
dc20f70
·
1 Parent(s): 17a8d4a

feat: implement Login and Signup pages with dark theme UI

Browse files
frontend/src/pages/Login.tsx CHANGED
@@ -1,3 +1,74 @@
 
 
 
 
 
1
  export default function Login() {
2
- return <div>Login</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  }
 
1
+ import { useState, type FormEvent } from 'react'
2
+ import { useNavigate, Link } from 'react-router-dom'
3
+ import { login } from '../api/auth'
4
+ import { useAuthStore } from '../store/authStore'
5
+
6
  export default function Login() {
7
+ const navigate = useNavigate()
8
+ const setAuth = useAuthStore((s) => s.setAuth)
9
+ const [email, setEmail] = useState('')
10
+ const [password, setPassword] = useState('')
11
+ const [error, setError] = useState('')
12
+ const [loading, setLoading] = useState(false)
13
+
14
+ async function handleSubmit(e: FormEvent) {
15
+ e.preventDefault()
16
+ setError('')
17
+ setLoading(true)
18
+ try {
19
+ const data = await login(email, password)
20
+ setAuth(data.access_token, data.user)
21
+ navigate(data.user.role === 'instructor' ? '/instructor/dashboard' : '/student/dashboard')
22
+ } catch (err) {
23
+ setError(err instanceof Error ? err.message : 'Login failed')
24
+ } finally {
25
+ setLoading(false)
26
+ }
27
+ }
28
+
29
+ return (
30
+ <div className="min-h-screen bg-gray-950 flex items-center justify-center px-4">
31
+ <div className="bg-gray-900 border border-gray-800 rounded-xl p-8 w-full max-w-md">
32
+ <h1 className="text-2xl font-semibold text-white mb-6">Sign in</h1>
33
+ <form onSubmit={handleSubmit} className="space-y-4">
34
+ <div>
35
+ <label className="block text-sm text-gray-400 mb-1">Email</label>
36
+ <input
37
+ type="email"
38
+ value={email}
39
+ onChange={(e) => setEmail(e.target.value)}
40
+ required
41
+ className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
42
+ placeholder="you@example.com"
43
+ />
44
+ </div>
45
+ <div>
46
+ <label className="block text-sm text-gray-400 mb-1">Password</label>
47
+ <input
48
+ type="password"
49
+ value={password}
50
+ onChange={(e) => setPassword(e.target.value)}
51
+ required
52
+ className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
53
+ placeholder="••••••••"
54
+ />
55
+ </div>
56
+ {error && <p className="text-red-400 text-sm">{error}</p>}
57
+ <button
58
+ type="submit"
59
+ disabled={loading}
60
+ className="w-full bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white rounded-lg px-4 py-2 font-medium transition-colors"
61
+ >
62
+ {loading ? '...' : 'Sign in'}
63
+ </button>
64
+ </form>
65
+ <p className="text-gray-500 text-sm mt-4 text-center">
66
+ No account?{' '}
67
+ <Link to="/signup" className="text-indigo-400 hover:text-indigo-300">
68
+ Sign up
69
+ </Link>
70
+ </p>
71
+ </div>
72
+ </div>
73
+ )
74
  }
frontend/src/pages/Signup.tsx CHANGED
@@ -1,3 +1,123 @@
 
 
 
 
 
1
  export default function Signup() {
2
- return <div>Signup</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  }
 
1
+ import { useState, type FormEvent } from 'react'
2
+ import { useNavigate, Link } from 'react-router-dom'
3
+ import { signup } from '../api/auth'
4
+ import { useAuthStore } from '../store/authStore'
5
+
6
  export default function Signup() {
7
+ const navigate = useNavigate()
8
+ const setAuth = useAuthStore((s) => s.setAuth)
9
+ const [fullName, setFullName] = useState('')
10
+ const [email, setEmail] = useState('')
11
+ const [password, setPassword] = useState('')
12
+ const [role, setRole] = useState<'student' | 'instructor'>('student')
13
+ const [classCode, setClassCode] = useState('')
14
+ const [error, setError] = useState('')
15
+ const [loading, setLoading] = useState(false)
16
+
17
+ async function handleSubmit(e: FormEvent) {
18
+ e.preventDefault()
19
+ setError('')
20
+ setLoading(true)
21
+ try {
22
+ const data = await signup(
23
+ fullName, email, password, role,
24
+ role === 'student' ? classCode : undefined
25
+ )
26
+ setAuth(data.access_token, data.user)
27
+ navigate(data.user.role === 'instructor' ? '/instructor/dashboard' : '/student/dashboard')
28
+ } catch (err) {
29
+ setError(err instanceof Error ? err.message : 'Signup failed')
30
+ } finally {
31
+ setLoading(false)
32
+ }
33
+ }
34
+
35
+ return (
36
+ <div className="min-h-screen bg-gray-950 flex items-center justify-center px-4">
37
+ <div className="bg-gray-900 border border-gray-800 rounded-xl p-8 w-full max-w-md">
38
+ <h1 className="text-2xl font-semibold text-white mb-6">Create account</h1>
39
+ <form onSubmit={handleSubmit} className="space-y-4">
40
+ <div>
41
+ <label className="block text-sm text-gray-400 mb-1">Full name</label>
42
+ <input
43
+ type="text"
44
+ value={fullName}
45
+ onChange={(e) => setFullName(e.target.value)}
46
+ required
47
+ className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
48
+ placeholder="Jane Smith"
49
+ />
50
+ </div>
51
+ <div>
52
+ <label className="block text-sm text-gray-400 mb-1">Email</label>
53
+ <input
54
+ type="email"
55
+ value={email}
56
+ onChange={(e) => setEmail(e.target.value)}
57
+ required
58
+ className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
59
+ placeholder="you@example.com"
60
+ />
61
+ </div>
62
+ <div>
63
+ <label className="block text-sm text-gray-400 mb-1">Password</label>
64
+ <input
65
+ type="password"
66
+ value={password}
67
+ onChange={(e) => setPassword(e.target.value)}
68
+ required
69
+ className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500"
70
+ placeholder="••••••••"
71
+ />
72
+ </div>
73
+ <div>
74
+ <label className="block text-sm text-gray-400 mb-2">Role</label>
75
+ <div className="flex gap-2">
76
+ {(['student', 'instructor'] as const).map((r) => (
77
+ <button
78
+ key={r}
79
+ type="button"
80
+ onClick={() => setRole(r)}
81
+ className={`flex-1 py-2 rounded-lg text-sm font-medium transition-colors ${
82
+ role === r
83
+ ? 'bg-indigo-600 text-white'
84
+ : 'bg-gray-800 text-gray-400 hover:bg-gray-700'
85
+ }`}
86
+ >
87
+ {r.charAt(0).toUpperCase() + r.slice(1)}
88
+ </button>
89
+ ))}
90
+ </div>
91
+ </div>
92
+ {role === 'student' && (
93
+ <div>
94
+ <label className="block text-sm text-gray-400 mb-1">Class code</label>
95
+ <input
96
+ type="text"
97
+ value={classCode}
98
+ onChange={(e) => setClassCode(e.target.value.toUpperCase())}
99
+ required
100
+ className="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 text-white placeholder-gray-500 focus:outline-none focus:border-indigo-500 uppercase"
101
+ placeholder="ABC12345"
102
+ />
103
+ </div>
104
+ )}
105
+ {error && <p className="text-red-400 text-sm">{error}</p>}
106
+ <button
107
+ type="submit"
108
+ disabled={loading}
109
+ className="w-full bg-indigo-600 hover:bg-indigo-500 disabled:opacity-50 text-white rounded-lg px-4 py-2 font-medium transition-colors"
110
+ >
111
+ {loading ? '...' : 'Create account'}
112
+ </button>
113
+ </form>
114
+ <p className="text-gray-500 text-sm mt-4 text-center">
115
+ Already have an account?{' '}
116
+ <Link to="/login" className="text-indigo-400 hover:text-indigo-300">
117
+ Sign in
118
+ </Link>
119
+ </p>
120
+ </div>
121
+ </div>
122
+ )
123
  }