Skip to content
Merged
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
37 changes: 29 additions & 8 deletions frontend/src/components/AuthScreen.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import React, { useState } from 'react';
import { Activity, Lock, Mail, ArrowRight, ShieldCheck, CheckCircle2, AlertCircle, Sun, Moon, Info } from 'lucide-react';
import { api } from '../api';
import PasswordToggleButton from './PasswordToggleButton';

export default function AuthScreen({ onAuthSuccess, sessionExpired = false, currentTheme, onToggleTheme }) {
const [mode, setMode] = useState('login'); // 'login' | 'register'
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(sessionExpired ? 'Your session has expired. Please sign in again.' : null);
const [successMsg, setSuccessMsg] = useState(null);

const switchMode = (newMode) => {
setMode(newMode);
setError(null);
setShowPassword(false);
setShowConfirmPassword(false);
};

const handleSubmit = async (e) => {
e.preventDefault();
setError(null);
Expand Down Expand Up @@ -97,14 +107,14 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr
<button
type="button"
className={`auth-tab-btn ${mode === 'login' ? 'active' : ''}`}
onClick={() => { setMode('login'); setError(null); }}
onClick={() => switchMode('login')}
>
Sign In
</button>
<button
type="button"
className={`auth-tab-btn ${mode === 'register' ? 'active' : ''}`}
onClick={() => { setMode('register'); setError(null); }}
onClick={() => switchMode('register')}
>
Create Account
</button>
Expand Down Expand Up @@ -155,15 +165,20 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr
<Lock size={18} className="input-icon" />
<input
id="auth-password"
type="password"
type={showPassword ? 'text' : 'password'}
required
autoComplete={mode === 'login' ? 'current-password' : 'new-password'}
placeholder="••••••••"
className="auth-input"
className="auth-input has-toggle"
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={loading}
/>
<PasswordToggleButton
isVisible={showPassword}
onToggle={() => setShowPassword((prev) => !prev)}
disabled={loading}
/>
</div>
{mode === 'register' && (
<span className="form-hint">At least 8 characters required.</span>
Expand All @@ -179,15 +194,21 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr
<Lock size={18} className="input-icon" />
<input
id="auth-confirm-password"
type="password"
type={showConfirmPassword ? 'text' : 'password'}
required
autoComplete="new-password"
placeholder="••••••••"
className="auth-input"
className="auth-input has-toggle"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
disabled={loading}
/>
<PasswordToggleButton
isVisible={showConfirmPassword}
onToggle={() => setShowConfirmPassword((prev) => !prev)}
label="confirm password"
disabled={loading}
/>
</div>
</div>
)}
Expand All @@ -210,7 +231,7 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr
<button
type="button"
className="link-btn"
onClick={() => { setMode('register'); setError(null); }}
onClick={() => switchMode('register')}
>
Create one now
</button>
Expand All @@ -221,7 +242,7 @@ export default function AuthScreen({ onAuthSuccess, sessionExpired = false, curr
<button
type="button"
className="link-btn"
onClick={() => { setMode('login'); setError(null); }}
onClick={() => switchMode('login')}
>
Sign in
</button>
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/components/PasswordToggleButton.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { Eye, EyeOff } from 'lucide-react';

export default function PasswordToggleButton({
isVisible,
onToggle,
disabled = false,
label = 'password',
size = 18,
className = '',
}) {
const actionText = isVisible ? `Hide ${label}` : `Show ${label}`;

return (
<button
type="button"
className={`password-toggle-btn ${className}`.trim()}
onClick={onToggle}
aria-label={actionText}
title={actionText}
disabled={disabled}
>
{isVisible ? <EyeOff size={size} /> : <Eye size={size} />}
</button>
);
}

43 changes: 40 additions & 3 deletions frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@
}

/* Base Resets */
*, *::before, *::after {
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
Expand Down Expand Up @@ -657,15 +659,19 @@ body {
transform: translateX(-100%);
width: 240px !important;
}

.app-sidebar.mobile-open {
transform: translateX(0);
}

.app-main {
margin-left: 0 !important;
}

.kpi-grid {
grid-template-columns: 1fr;
}

.content-body {
padding: 16px;
}
Expand All @@ -677,7 +683,9 @@ body {

/* Keyframe for loading spinners */
@keyframes spin {
to { transform: rotate(360deg); }
to {
transform: rotate(360deg);
}
}

.spin-anim {
Expand Down Expand Up @@ -815,6 +823,35 @@ body {
box-shadow: 0 0 0 3px rgba(34, 184, 176, 0.2);
}

.auth-input.has-toggle {
padding-right: 42px;
}

.password-toggle-btn {
position: absolute;
right: 10px;
background: transparent;
border: none;
color: var(--text-subtle);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
border-radius: 6px;
transition: color 0.15s ease, background-color 0.15s ease;
}

.password-toggle-btn:hover:not(:disabled) {
color: var(--text-main);
background-color: rgba(120, 159, 180, 0.15);
}

.password-toggle-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.auth-submit-btn {
width: 100%;
padding: 13px;
Expand Down Expand Up @@ -1384,4 +1421,4 @@ body {
background: var(--bg-card-solid);
border: 1px solid var(--border-glass);
color: var(--text-main);
}
}
Loading