fix: fix unicode in roles, add password confirmation field

- RolesTab: wrap \u00b7 in JS expression {'\u00b7'} so JSX renders the middle dot correctly instead of literal backslash-u sequence
- UsersTab: add confirm password field with mismatch validation, hint text for password policy, and reset on cancel/success
- UserManagement.module.css: add .hintText style for password policy hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-09 18:46:30 +02:00
parent 7ec56f3bd0
commit 39687bc8a9
3 changed files with 31 additions and 9 deletions

View File

@@ -179,7 +179,7 @@ export default function RolesTab({ highlightId, onHighlightConsumed }: { highlig
)}
</div>
<div className={styles.entityMeta}>
{role.description || '\u2014'} \u00b7{' '}
{role.description || '\u2014'} {'\u00b7'}{' '}
{getAssignmentCount(role)} assignments
</div>
<div className={styles.entityTags}>

View File

@@ -156,3 +156,8 @@
color: var(--error);
font-size: 12px;
}
.hintText {
font-size: 12px;
color: var(--text-muted);
}

View File

@@ -67,8 +67,11 @@ export default function UsersTab({ highlightId, onHighlightConsumed }: { highlig
const [newDisplay, setNewDisplay] = useState('');
const [newEmail, setNewEmail] = useState('');
const [newPassword, setNewPassword] = useState('');
const [newPasswordConfirm, setNewPasswordConfirm] = useState('');
const [newProvider, setNewProvider] = useState<'local' | 'oidc'>('local');
const passwordMismatch = newPassword.length > 0 && newPasswordConfirm.length > 0 && newPassword !== newPasswordConfirm;
// Password reset state
const [resettingPassword, setResettingPassword] = useState(false);
const [newPw, setNewPw] = useState('');
@@ -145,6 +148,7 @@ export default function UsersTab({ highlightId, onHighlightConsumed }: { highlig
setNewDisplay('');
setNewEmail('');
setNewPassword('');
setNewPasswordConfirm('');
setNewProvider('local');
},
onError: (err: unknown) => {
@@ -241,12 +245,24 @@ export default function UsersTab({ highlightId, onHighlightConsumed }: { highlig
onChange={(e) => setNewEmail(e.target.value)}
/>
{newProvider === 'local' && (
<Input
placeholder="Password *"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<>
<Input
placeholder="Password *"
type="password"
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
/>
<Input
placeholder="Confirm Password *"
type="password"
value={newPasswordConfirm}
onChange={(e) => setNewPasswordConfirm(e.target.value)}
/>
{passwordMismatch && (
<span className={styles.errorText}>Passwords do not match</span>
)}
<span className={styles.hintText}>Min 12 chars, 3 of 4: uppercase, lowercase, number, special</span>
</>
)}
{newProvider === 'oidc' && (
<InfoCallout variant="amber">
@@ -258,7 +274,7 @@ export default function UsersTab({ highlightId, onHighlightConsumed }: { highlig
<Button
size="sm"
variant="ghost"
onClick={() => setCreating(false)}
onClick={() => { setCreating(false); setNewPasswordConfirm(''); }}
>
Cancel
</Button>
@@ -270,7 +286,8 @@ export default function UsersTab({ highlightId, onHighlightConsumed }: { highlig
disabled={
!newUsername.trim() ||
(newProvider === 'local' && !newPassword.trim()) ||
duplicateUsername
duplicateUsername ||
passwordMismatch
}
>
Create