Add display name support for user management and update related components
All checks were successful
Build & Push Docker Image / build (push) Successful in 6m2s

This commit is contained in:
2026-02-27 16:29:23 +01:00
parent d781022b63
commit 9be9938f02
14 changed files with 165 additions and 63 deletions

View File

@@ -25,7 +25,7 @@ export default function Admin() {
const [newPassword, setNewPassword] = useState('');
const [showCreateUser, setShowCreateUser] = useState(false);
const [creatingUser, setCreatingUser] = useState(false);
const [newUser, setNewUser] = useState({ name: '', email: '', password: '', role: 'user' });
const [newUser, setNewUser] = useState({ name: '', display_name: '', email: '', password: '', role: 'user' });
// Branding state
const [editAppName, setEditAppName] = useState('');
@@ -163,7 +163,7 @@ export default function Admin() {
await api.post('/admin/users', newUser);
toast.success(t('admin.userCreated'));
setShowCreateUser(false);
setNewUser({ name: '', email: '', password: '', role: 'user' });
setNewUser({ name: '', display_name: '', email: '', password: '', role: 'user' });
fetchUsers();
} catch (err) {
toast.error(err.response?.data?.error || t('admin.userCreateFailed'));
@@ -173,7 +173,7 @@ export default function Admin() {
};
const filteredUsers = users.filter(u =>
u.name.toLowerCase().includes(search.toLowerCase()) ||
(u.display_name || u.name).toLowerCase().includes(search.toLowerCase()) ||
u.email.toLowerCase().includes(search.toLowerCase())
);
@@ -371,12 +371,12 @@ export default function Admin() {
className="w-full h-full object-cover"
/>
) : (
u.name[0]?.toUpperCase()
(u.display_name || u.name)[0]?.toUpperCase()
)}
</div>
<div>
<p className="text-sm font-medium text-th-text">{u.name}</p>
<p className="text-xs text-th-text-s">{u.email}</p>
<p className="text-sm font-medium text-th-text">{u.display_name || u.name}</p>
<p className="text-xs text-th-text-s">@{u.name} · {u.email}</p>
</div>
</div>
</td>
@@ -490,7 +490,7 @@ export default function Admin() {
<h3 className="text-lg font-semibold text-th-text mb-4">{t('admin.createUserTitle')}</h3>
<form onSubmit={handleCreateUser} className="space-y-4">
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.name')}</label>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.username')}</label>
<div className="relative">
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
<input
@@ -498,10 +498,24 @@ export default function Admin() {
value={newUser.name}
onChange={e => setNewUser({ ...newUser, name: e.target.value })}
className="input-field pl-11"
placeholder={t('auth.namePlaceholder')}
placeholder={t('auth.usernamePlaceholder')}
required
/>
</div>
<p className="text-xs text-th-text-s mt-1">{t('auth.usernameHint')}</p>
</div>
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.displayName')}</label>
<div className="relative">
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
<input
type="text"
value={newUser.display_name}
onChange={e => setNewUser({ ...newUser, display_name: e.target.value })}
className="input-field pl-11"
placeholder={t('auth.displayNamePlaceholder')}
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.email')}</label>

View File

@@ -7,7 +7,8 @@ import BrandLogo from '../components/BrandLogo';
import toast from 'react-hot-toast';
export default function Register() {
const [name, setName] = useState('');
const [username, setUsername] = useState('');
const [displayName, setDisplayName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
@@ -32,7 +33,7 @@ export default function Register() {
setLoading(true);
try {
const result = await register(name, email, password);
const result = await register(username, displayName, email, password);
if (result?.needsVerification) {
setNeedsVerification(true);
toast.success(t('auth.verificationSent'));
@@ -87,15 +88,31 @@ export default function Register() {
<form onSubmit={handleSubmit} className="space-y-5">
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.name')}</label>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.username')}</label>
<div className="relative">
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
<input
type="text"
value={name}
onChange={e => setName(e.target.value)}
value={username}
onChange={e => setUsername(e.target.value)}
className="input-field pl-11"
placeholder={t('auth.namePlaceholder')}
placeholder={t('auth.usernamePlaceholder')}
required
/>
</div>
<p className="text-xs text-th-text-s mt-1">{t('auth.usernameHint')}</p>
</div>
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.displayName')}</label>
<div className="relative">
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
<input
type="text"
value={displayName}
onChange={e => setDisplayName(e.target.value)}
className="input-field pl-11"
placeholder={t('auth.displayNamePlaceholder')}
required
/>
</div>

View File

@@ -574,11 +574,11 @@ export default function RoomDetail() {
{u.avatar_image ? (
<img src={`${api.defaults.baseURL}/auth/avatar/${u.avatar_image}`} alt="" className="w-full h-full object-cover" />
) : (
u.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
(u.display_name || u.name).split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
)}
</div>
<div className="min-w-0">
<div className="text-sm font-medium text-th-text truncate">{u.name}</div>
<div className="text-sm font-medium text-th-text truncate">{u.display_name || u.name}</div>
<div className="text-xs text-th-text-s truncate">{u.email}</div>
</div>
</button>
@@ -600,11 +600,11 @@ export default function RoomDetail() {
{u.avatar_image ? (
<img src={`${api.defaults.baseURL}/auth/avatar/${u.avatar_image}`} alt="" className="w-full h-full object-cover" />
) : (
u.name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
(u.display_name || u.name).split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2)
)}
</div>
<div className="min-w-0">
<div className="text-sm font-medium text-th-text truncate">{u.name}</div>
<div className="text-sm font-medium text-th-text truncate">{u.display_name || u.name}</div>
<div className="text-xs text-th-text-s truncate">{u.email}</div>
</div>
</div>

View File

@@ -14,6 +14,7 @@ export default function Settings() {
const [profile, setProfile] = useState({
name: user?.name || '',
display_name: user?.display_name || '',
email: user?.email || '',
});
const [passwords, setPasswords] = useState({
@@ -52,6 +53,7 @@ export default function Settings() {
try {
const res = await api.put('/auth/profile', {
name: profile.name,
display_name: profile.display_name,
email: profile.email,
theme,
language,
@@ -190,7 +192,7 @@ export default function Settings() {
className="w-16 h-16 rounded-full flex items-center justify-center text-white text-xl font-bold"
style={{ backgroundColor: user?.avatar_color || '#6366f1' }}
>
{user?.name?.[0]?.toUpperCase() || '?'}
{(user?.display_name || user?.name)?.[0]?.toUpperCase() || '?'}
</div>
)}
<button
@@ -259,7 +261,7 @@ export default function Settings() {
<form onSubmit={handleProfileSave} className="space-y-4">
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.name')}</label>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.username')}</label>
<div className="relative">
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
<input
@@ -270,6 +272,19 @@ export default function Settings() {
required
/>
</div>
<p className="text-xs text-th-text-s mt-1">{t('auth.usernameHint')}</p>
</div>
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.displayName')}</label>
<div className="relative">
<User size={18} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-th-text-s" />
<input
type="text"
value={profile.display_name}
onChange={e => setProfile({ ...profile, display_name: e.target.value })}
className="input-field pl-11"
/>
</div>
</div>
<div>
<label className="block text-sm font-medium text-th-text mb-1.5">{t('auth.email')}</label>