Skip to content

Commit ccf61ee

Browse files
committed
feat: Add landing page and password visibility toggles
1 parent 57f7f34 commit ccf61ee

File tree

4 files changed

+796
-200
lines changed

4 files changed

+796
-200
lines changed

frontend/app/login/page.tsx

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import ClientOnly from "@/components/ClientOnly";
1010
export default function LoginPage() {
1111
const [email, setEmail] = useState("");
1212
const [password, setPassword] = useState("");
13+
const [showPassword, setShowPassword] = useState(false);
1314
const [loading, setLoading] = useState(false);
1415
const [error, setError] = useState<string | null>(null);
1516
const { signIn } = useAuth();
@@ -108,15 +109,62 @@ export default function LoginPage() {
108109
<label className="block text-sm text-gray-400 mb-2">
109110
PASSWORD
110111
</label>
111-
<input
112-
type="password"
113-
value={password}
114-
onChange={(e) => setPassword(e.target.value)}
115-
className="w-full bg-black border border-gray-600 px-4 py-3 focus:border-white focus:outline-none transition-colors font-mono"
116-
placeholder="••••••••"
117-
required
118-
disabled={loading}
119-
/>
112+
<div className="relative">
113+
<input
114+
type={showPassword ? "text" : "password"}
115+
value={password}
116+
onChange={(e) => setPassword(e.target.value)}
117+
className="w-full bg-black border border-gray-600 px-4 py-3 pr-12 focus:border-white focus:outline-none transition-colors font-mono"
118+
placeholder="••••••••"
119+
required
120+
disabled={loading}
121+
/>
122+
<button
123+
type="button"
124+
onClick={() => setShowPassword(!showPassword)}
125+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors"
126+
tabIndex={-1}
127+
>
128+
{showPassword ? (
129+
// Eye slash icon (hidden)
130+
<svg
131+
xmlns="http://www.w3.org/2000/svg"
132+
fill="none"
133+
viewBox="0 0 24 24"
134+
strokeWidth={1.5}
135+
stroke="currentColor"
136+
className="w-5 h-5"
137+
>
138+
<path
139+
strokeLinecap="round"
140+
strokeLinejoin="round"
141+
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
142+
/>
143+
</svg>
144+
) : (
145+
// Eye icon (visible)
146+
<svg
147+
xmlns="http://www.w3.org/2000/svg"
148+
fill="none"
149+
viewBox="0 0 24 24"
150+
strokeWidth={1.5}
151+
stroke="currentColor"
152+
className="w-5 h-5"
153+
>
154+
<path
155+
strokeLinecap="round"
156+
strokeLinejoin="round"
157+
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
158+
/>
159+
<path
160+
strokeLinecap="round"
161+
strokeLinejoin="round"
162+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
163+
/>
164+
</svg>
165+
)}
166+
</button>
167+
</div>
120168
</div>
121169

122170
<button

frontend/app/signup/page.tsx

Lines changed: 111 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export default function SignupPage() {
1111
const [password, setPassword] = useState("");
1212
const [confirmPassword, setConfirmPassword] = useState("");
1313
const [username, setUsername] = useState("");
14+
const [showPassword, setShowPassword] = useState(false);
15+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
1416
const [loading, setLoading] = useState(false);
1517
const [error, setError] = useState<string | null>(null);
1618
const [success, setSuccess] = useState(false);
@@ -169,32 +171,122 @@ export default function SignupPage() {
169171
<label className="block text-sm text-gray-400 mb-2">
170172
PASSWORD
171173
</label>
172-
<input
173-
type="password"
174-
value={password}
175-
onChange={(e) => setPassword(e.target.value)}
176-
className="w-full bg-black border border-gray-600 px-4 py-3 focus:border-white focus:outline-none transition-colors font-mono"
177-
placeholder="••••••••"
178-
required
179-
disabled={loading}
180-
minLength={6}
181-
/>
174+
<div className="relative">
175+
<input
176+
type={showPassword ? "text" : "password"}
177+
value={password}
178+
onChange={(e) => setPassword(e.target.value)}
179+
className="w-full bg-black border border-gray-600 px-4 py-3 pr-12 focus:border-white focus:outline-none transition-colors font-mono"
180+
placeholder="••••••••"
181+
required
182+
disabled={loading}
183+
minLength={6}
184+
/>
185+
<button
186+
type="button"
187+
onClick={() => setShowPassword(!showPassword)}
188+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors"
189+
tabIndex={-1}
190+
>
191+
{showPassword ? (
192+
<svg
193+
xmlns="http://www.w3.org/2000/svg"
194+
fill="none"
195+
viewBox="0 0 24 24"
196+
strokeWidth={1.5}
197+
stroke="currentColor"
198+
className="w-5 h-5"
199+
>
200+
<path
201+
strokeLinecap="round"
202+
strokeLinejoin="round"
203+
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
204+
/>
205+
</svg>
206+
) : (
207+
<svg
208+
xmlns="http://www.w3.org/2000/svg"
209+
fill="none"
210+
viewBox="0 0 24 24"
211+
strokeWidth={1.5}
212+
stroke="currentColor"
213+
className="w-5 h-5"
214+
>
215+
<path
216+
strokeLinecap="round"
217+
strokeLinejoin="round"
218+
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
219+
/>
220+
<path
221+
strokeLinecap="round"
222+
strokeLinejoin="round"
223+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
224+
/>
225+
</svg>
226+
)}
227+
</button>
228+
</div>
182229
<p className="text-xs text-gray-500 mt-1">Minimum 6 characters</p>
183230
</div>
184231

185232
<div>
186233
<label className="block text-sm text-gray-400 mb-2">
187234
CONFIRM_PASSWORD
188235
</label>
189-
<input
190-
type="password"
191-
value={confirmPassword}
192-
onChange={(e) => setConfirmPassword(e.target.value)}
193-
className="w-full bg-black border border-gray-600 px-4 py-3 focus:border-white focus:outline-none transition-colors font-mono"
194-
placeholder="••••••••"
195-
required
196-
disabled={loading}
197-
/>
236+
<div className="relative">
237+
<input
238+
type={showConfirmPassword ? "text" : "password"}
239+
value={confirmPassword}
240+
onChange={(e) => setConfirmPassword(e.target.value)}
241+
className="w-full bg-black border border-gray-600 px-4 py-3 pr-12 focus:border-white focus:outline-none transition-colors font-mono"
242+
placeholder="••••••••"
243+
required
244+
disabled={loading}
245+
/>
246+
<button
247+
type="button"
248+
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
249+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white transition-colors"
250+
tabIndex={-1}
251+
>
252+
{showConfirmPassword ? (
253+
<svg
254+
xmlns="http://www.w3.org/2000/svg"
255+
fill="none"
256+
viewBox="0 0 24 24"
257+
strokeWidth={1.5}
258+
stroke="currentColor"
259+
className="w-5 h-5"
260+
>
261+
<path
262+
strokeLinecap="round"
263+
strokeLinejoin="round"
264+
d="M3.98 8.223A10.477 10.477 0 001.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.45 10.45 0 0112 4.5c4.756 0 8.773 3.162 10.065 7.498a10.523 10.523 0 01-4.293 5.774M6.228 6.228L3 3m3.228 3.228l3.65 3.65m7.894 7.894L21 21m-3.228-3.228l-3.65-3.65m0 0a3 3 0 10-4.243-4.243m4.242 4.242L9.88 9.88"
265+
/>
266+
</svg>
267+
) : (
268+
<svg
269+
xmlns="http://www.w3.org/2000/svg"
270+
fill="none"
271+
viewBox="0 0 24 24"
272+
strokeWidth={1.5}
273+
stroke="currentColor"
274+
className="w-5 h-5"
275+
>
276+
<path
277+
strokeLinecap="round"
278+
strokeLinejoin="round"
279+
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z"
280+
/>
281+
<path
282+
strokeLinecap="round"
283+
strokeLinejoin="round"
284+
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
285+
/>
286+
</svg>
287+
)}
288+
</button>
289+
</div>
198290
</div>
199291

200292
<button

0 commit comments

Comments
 (0)