INSIGHT-MVP/packages/frontend/src/profile/ProfilePage.module.css
Thomas Reitz 779d90ca43 feat: add user profile page with 2FA management and password change
Backend:
- POST /auth/2fa/setup - generate TOTP secret + QR code (temp Redis storage)
- POST /auth/2fa/enable - verify TOTP code and activate 2FA
- POST /auth/2fa/disable - deactivate 2FA (requires password)
- PATCH /users/me - update own profile (firstName, lastName)
- POST /users/me/change-password - change own password

Frontend:
- New ProfilePage with 3 sections: personal info, password, 2FA
- QR code display for Authenticator app setup
- Clickable user info in sidebar navigates to /profile
- AuthContext extended with twoFactorEnabled + refreshUser

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:24:44 +01:00

206 lines
3.6 KiB
CSS

.section {
background: var(--color-bg-card);
border-radius: var(--radius-md);
padding: 1.5rem;
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-border);
margin-bottom: 1.5rem;
}
.sectionTitle {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 1.25rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--color-border);
}
.form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.field {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.field label {
font-size: 0.875rem;
font-weight: 500;
color: var(--color-text);
}
.field input {
padding: 0.625rem 0.75rem;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
font-size: 0.9375rem;
transition: border-color 0.15s;
}
.field input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-primary-light);
}
.field input:disabled {
background: var(--color-bg);
color: var(--color-text-muted);
}
.field small {
color: var(--color-text-muted);
font-size: 0.75rem;
}
.fieldRow {
display: flex;
gap: 1rem;
}
.fieldRow .field {
flex: 1;
}
.button {
padding: 0.625rem 1.25rem;
background: var(--color-primary);
color: white;
border: none;
border-radius: var(--radius-sm);
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
align-self: flex-start;
}
.button:hover:not(:disabled) {
background: var(--color-primary-hover);
}
.button:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.buttonSecondary {
padding: 0.625rem 1.25rem;
background: transparent;
color: var(--color-text-secondary);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s;
}
.buttonSecondary:hover {
background: var(--color-bg);
}
.buttonDanger {
padding: 0.625rem 1.25rem;
background: var(--color-error);
color: white;
border: none;
border-radius: var(--radius-sm);
font-size: 0.875rem;
font-weight: 600;
cursor: pointer;
transition: background 0.15s;
align-self: flex-start;
}
.buttonDanger:hover:not(:disabled) {
background: #b91c1c;
}
.buttonDanger:disabled {
opacity: 0.7;
cursor: not-allowed;
}
.buttonRow {
display: flex;
gap: 0.75rem;
align-items: center;
}
.success {
background: #f0fdf4;
color: var(--color-success);
padding: 0.75rem;
border-radius: var(--radius-sm);
font-size: 0.875rem;
border: 1px solid #bbf7d0;
}
.error {
background: #fef2f2;
color: var(--color-error);
padding: 0.75rem;
border-radius: var(--radius-sm);
font-size: 0.875rem;
border: 1px solid #fecaca;
}
.tfaStatus {
display: flex;
align-items: center;
padding: 0.75rem 0;
font-size: 0.9375rem;
margin-bottom: 1rem;
}
.tfaSetup {
margin-top: 1rem;
}
.tfaInstructions {
color: var(--color-text-secondary);
font-size: 0.875rem;
margin-bottom: 1rem;
}
.qrContainer {
display: flex;
justify-content: center;
padding: 1.5rem;
background: white;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
margin-bottom: 1rem;
}
.qrContainer img {
width: 200px;
height: 200px;
}
.manualSecret {
display: flex;
flex-direction: column;
gap: 0.375rem;
margin-bottom: 1.25rem;
}
.manualSecret label {
font-size: 0.8125rem;
color: var(--color-text-muted);
}
.manualSecret code {
background: var(--color-bg);
padding: 0.5rem 0.75rem;
border-radius: var(--radius-sm);
font-size: 0.875rem;
letter-spacing: 2px;
word-break: break-all;
border: 1px solid var(--color-border);
}