diff --git a/frontend/src/App.css b/frontend/src/App.css
index 7043abd..ace719b 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -567,6 +567,47 @@
.rule--pass .rule-icon { color: var(--green); }
.rule--fail .rule-icon { color: #d1d5db; }
+/* ─── Password Toggle Button ────────────────────────────── */
+.password-input-wrapper {
+ position: relative;
+ width: 100%;
+}
+
+.password-input-wrapper .field-input {
+ padding-right: 42px; /* Ensure input text doesn't overlap the eye button */
+}
+
+.password-toggle-btn {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ padding: 4px;
+ cursor: pointer;
+ color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.15s, opacity 0.15s;
+ border-radius: 4px;
+}
+
+.password-toggle-btn:hover:not(:disabled) {
+ color: var(--text);
+}
+
+.password-toggle-btn:focus-visible {
+ outline: 2px solid var(--green);
+ outline-offset: -2px;
+}
+
+.password-toggle-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
/* ─── Responsive ─────────────────────────────────────────── */
@media (max-width: 600px) {
.header { padding: 20px 16px; }
diff --git a/frontend/src/pages/LoginPage.jsx b/frontend/src/pages/LoginPage.jsx
index e373db1..e62683c 100644
--- a/frontend/src/pages/LoginPage.jsx
+++ b/frontend/src/pages/LoginPage.jsx
@@ -7,6 +7,7 @@ const isValidEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim());
export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
const [errors, setErrors] = useState({});
const [apiError, setApiError] = useState('');
const [loading, setLoading] = useState(false);
@@ -82,16 +83,39 @@ export default function LoginPage() {
-
{
- setPassword(e.target.value);
- setTouched(p => ({ ...p, password: true }));
- clear('password');
- }}
- disabled={loading}
- autoComplete="new-password"
- />
+
+
{
+ setPassword(e.target.value);
+ setTouched(p => ({ ...p, password: true }));
+ clear('password');
+ }}
+ disabled={loading}
+ autoComplete="new-password"
+ />
+
+
{errors.password &&
✗ {errors.password}
}
{/* Strength bar — shown as soon as user starts typing */}
@@ -179,16 +204,39 @@ export default function RegisterPage() {
{/* Confirm password */}
-
{ setConfirm(e.target.value); clear('confirm'); }}
- disabled={loading}
- autoComplete="new-password"
- />
+
+
{ setConfirm(e.target.value); clear('confirm'); }}
+ disabled={loading}
+ autoComplete="new-password"
+ />
+
+
{errors.confirm &&
✗ {errors.confirm}
}
diff --git a/todo-frontend/src/App.css b/todo-frontend/src/App.css
index 7043abd..ace719b 100644
--- a/todo-frontend/src/App.css
+++ b/todo-frontend/src/App.css
@@ -567,6 +567,47 @@
.rule--pass .rule-icon { color: var(--green); }
.rule--fail .rule-icon { color: #d1d5db; }
+/* ─── Password Toggle Button ────────────────────────────── */
+.password-input-wrapper {
+ position: relative;
+ width: 100%;
+}
+
+.password-input-wrapper .field-input {
+ padding-right: 42px; /* Ensure input text doesn't overlap the eye button */
+}
+
+.password-toggle-btn {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: none;
+ border: none;
+ padding: 4px;
+ cursor: pointer;
+ color: var(--text-muted);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: color 0.15s, opacity 0.15s;
+ border-radius: 4px;
+}
+
+.password-toggle-btn:hover:not(:disabled) {
+ color: var(--text);
+}
+
+.password-toggle-btn:focus-visible {
+ outline: 2px solid var(--green);
+ outline-offset: -2px;
+}
+
+.password-toggle-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
/* ─── Responsive ─────────────────────────────────────────── */
@media (max-width: 600px) {
.header { padding: 20px 16px; }
diff --git a/todo-frontend/src/pages/LoginPage.jsx b/todo-frontend/src/pages/LoginPage.jsx
index ccbaf7b..921331b 100644
--- a/todo-frontend/src/pages/LoginPage.jsx
+++ b/todo-frontend/src/pages/LoginPage.jsx
@@ -7,6 +7,7 @@ const isValidEmail = (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v.trim());
export default function LoginPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
const [errors, setErrors] = useState({});
const [apiError, setApiError] = useState('');
const [loading, setLoading] = useState(false);
@@ -82,16 +83,39 @@ export default function LoginPage() {
-
{ setPassword(e.target.value); clear('password'); }}
- disabled={loading}
- autoComplete="current-password"
- />
+
+
{ setPassword(e.target.value); clear('password'); }}
+ disabled={loading}
+ autoComplete="current-password"
+ />
+
+
{errors.password &&
✗ {errors.password}
}
diff --git a/todo-frontend/src/pages/RegisterPage.jsx b/todo-frontend/src/pages/RegisterPage.jsx
index ddb36df..1a59027 100644
--- a/todo-frontend/src/pages/RegisterPage.jsx
+++ b/todo-frontend/src/pages/RegisterPage.jsx
@@ -24,6 +24,8 @@ export default function RegisterPage() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirm, setConfirm] = useState('');
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirm, setShowConfirm] = useState(false);
const [errors, setErrors] = useState({});
const [apiError, setApiError] = useState('');
const [loading, setLoading] = useState(false);
@@ -129,20 +131,43 @@ export default function RegisterPage() {
{/* Password */}
-
{
- setPassword(e.target.value);
- setTouched(p => ({ ...p, password: true }));
- clear('password');
- }}
- disabled={loading}
- autoComplete="new-password"
- />
+
+
{
+ setPassword(e.target.value);
+ setTouched(p => ({ ...p, password: true }));
+ clear('password');
+ }}
+ disabled={loading}
+ autoComplete="new-password"
+ />
+
+
{errors.password &&
✗ {errors.password}
}
{/* Strength bar — shown as soon as user starts typing */}
@@ -179,16 +204,39 @@ export default function RegisterPage() {
{/* Confirm password */}
-
{ setConfirm(e.target.value); clear('confirm'); }}
- disabled={loading}
- autoComplete="new-password"
- />
+
+
{ setConfirm(e.target.value); clear('confirm'); }}
+ disabled={loading}
+ autoComplete="new-password"
+ />
+
+
{errors.confirm &&
✗ {errors.confirm}
}