All Tests Work! Password reset implemented. Users list sorted.
This commit is contained in:
@@ -4,7 +4,7 @@ import { User, Language, ExerciseDef, ExerciseType, BodyWeightRecord } from '../
|
||||
import { createUser, changePassword, updateUserProfile, getCurrentUserProfile, getUsers, deleteUser, toggleBlockUser, adminResetPassword, getMe } from '../services/auth';
|
||||
import { getExercises, saveExercise } from '../services/storage';
|
||||
import { getWeightHistory, logWeight } from '../services/weight';
|
||||
import { User as UserIcon, LogOut, Save, Shield, UserPlus, Lock, Calendar, Ruler, Scale, PersonStanding, Globe, ChevronDown, ChevronUp, Trash2, Ban, KeyRound, Dumbbell, Archive, ArchiveRestore, Pencil, Plus } from 'lucide-react';
|
||||
import { User as UserIcon, LogOut, Save, Shield, UserPlus, Lock, Calendar, Ruler, Scale, PersonStanding, Globe, ChevronDown, ChevronUp, Trash2, Ban, KeyRound, Dumbbell, Archive, ArchiveRestore, Pencil, Plus, RefreshCcw } from 'lucide-react';
|
||||
import ExerciseModal from './ExerciseModal';
|
||||
import FilledInput from './FilledInput';
|
||||
import { t } from '../services/i18n';
|
||||
@@ -202,12 +202,16 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
await refreshUserList();
|
||||
};
|
||||
|
||||
const handleAdminResetPass = (uid: string) => {
|
||||
const handleAdminResetPass = async (uid: string) => {
|
||||
const pass = adminPassResetInput[uid];
|
||||
if (pass && pass.length >= 4) {
|
||||
adminResetPassword(uid, pass);
|
||||
alert(t('pass_reset', lang));
|
||||
setAdminPassResetInput({ ...adminPassResetInput, [uid]: '' });
|
||||
const res = await adminResetPassword(uid, pass);
|
||||
if (res.success) {
|
||||
alert(t('pass_reset', lang) || 'Password reset successfully');
|
||||
setAdminPassResetInput({ ...adminPassResetInput, [uid]: '' });
|
||||
} else {
|
||||
alert(res.error || 'Failed to reset password');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -274,6 +278,7 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
<div className="bg-surface-container-high rounded-t-lg border-b border-outline-variant px-3 py-2">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Scale size={10} /> {t('weight_kg', lang)}</label>
|
||||
<input
|
||||
data-testid="profile-weight-input"
|
||||
type="number"
|
||||
step="0.1"
|
||||
value={weight}
|
||||
@@ -283,15 +288,15 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
</div>
|
||||
<div className="bg-surface-container-high rounded-t-lg border-b border-outline-variant px-3 py-2">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Ruler size={10} /> {t('height', lang)}</label>
|
||||
<input type="number" value={height} onChange={(e) => setHeight(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none" />
|
||||
<input data-testid="profile-height-input" type="number" value={height} onChange={(e) => setHeight(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none" />
|
||||
</div>
|
||||
<div className="bg-surface-container-high rounded-t-lg border-b border-outline-variant px-3 py-2">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><Calendar size={10} /> {t('birth_date', lang)}</label>
|
||||
<input type="date" value={birthDate} onChange={(e) => setBirthDate(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none text-sm py-1" />
|
||||
<input data-testid="profile-birth-date" type="date" value={birthDate} onChange={(e) => setBirthDate(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none text-sm py-1" />
|
||||
</div>
|
||||
<div className="bg-surface-container-high rounded-t-lg border-b border-outline-variant px-3 py-2">
|
||||
<label className="text-[10px] text-on-surface-variant flex gap-1 items-center"><PersonStanding size={10} /> {t('gender', lang)}</label>
|
||||
<select value={gender} onChange={(e) => setGender(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none text-sm py-1 bg-surface-container-high">
|
||||
<select data-testid="profile-gender" value={gender} onChange={(e) => setGender(e.target.value)} className="w-full bg-transparent text-on-surface focus:outline-none text-sm py-1 bg-surface-container-high">
|
||||
<option value="MALE">{t('male', lang)}</option>
|
||||
<option value="FEMALE">{t('female', lang)}</option>
|
||||
<option value="OTHER">{t('other', lang)}</option>
|
||||
@@ -507,18 +512,32 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
|
||||
{/* User List */}
|
||||
<div className="border-t border-outline-variant pt-4">
|
||||
<button
|
||||
<div
|
||||
role="button"
|
||||
aria-expanded={showUserList}
|
||||
onClick={() => setShowUserList(!showUserList)}
|
||||
className="w-full flex justify-between items-center text-sm font-medium text-on-surface"
|
||||
className="w-full flex justify-between items-center text-sm font-medium text-on-surface cursor-pointer select-none"
|
||||
>
|
||||
<span>{t('admin_users_list', lang)} ({allUsers.length})</span>
|
||||
{showUserList ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
refreshUserList();
|
||||
}}
|
||||
className="p-1 hover:bg-surface-container-high rounded-full transition-colors text-on-surface-variant hover:text-primary"
|
||||
title="Refresh List"
|
||||
>
|
||||
<RefreshCcw size={14} />
|
||||
</button>
|
||||
{showUserList ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showUserList && (
|
||||
<div className="mt-4 space-y-4">
|
||||
<div className="mt-4 space-y-4" data-testid="user-list">
|
||||
{allUsers.map(u => (
|
||||
<div key={u.id} className="bg-surface-container-high p-3 rounded-lg space-y-3">
|
||||
<div key={u.id} className="bg-surface-container-high p-3 rounded-lg space-y-3" data-testid="user-row">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="overflow-hidden">
|
||||
<div className="font-medium text-sm text-on-surface truncate">{u.email}</div>
|
||||
@@ -534,6 +553,7 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
onClick={() => handleAdminBlockUser(u.id, !u.isBlocked)}
|
||||
className={`p-2 rounded-full ${u.isBlocked ? 'bg-primary/20 text-primary' : 'text-on-surface-variant hover:bg-white/10'}`}
|
||||
title={u.isBlocked ? t('unblock', lang) : t('block', lang)}
|
||||
aria-label={u.isBlocked ? t('unblock', lang) : t('block', lang)}
|
||||
>
|
||||
<Ban size={16} />
|
||||
</button>
|
||||
@@ -541,6 +561,7 @@ const Profile: React.FC<ProfileProps> = ({ user, onLogout, lang, onLanguageChang
|
||||
onClick={() => handleAdminDeleteUser(u.id)}
|
||||
className="p-2 text-on-surface-variant hover:text-error hover:bg-error/10 rounded-full"
|
||||
title={t('delete', lang)}
|
||||
aria-label={t('delete', lang)}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user