feat(admin): create users by mobile number instead of email

Admin user management now creates accounts with a required mobile
number and an optional contact email, matching mobile-only auth.
Admin-created accounts are pre-verified (phoneVerified) so the user
can sign in by password immediately. The user list and search now
include phone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
keyhan
2026-06-17 12:34:43 +03:30
parent 37f64435f6
commit a3b7e9055c
5 changed files with 63 additions and 15 deletions
@@ -31,6 +31,7 @@ export default function AdminUsersPage() {
const [pwdModalUser, setPwdModalUser] = useState<AdminUser | null>(null);
const [pwdModalPassword, setPwdModalPassword] = useState('');
const [form, setForm] = useState({
phone: '',
email: '',
password: '',
firstName: '',
@@ -50,7 +51,7 @@ export default function AdminUsersPage() {
queryClient.invalidateQueries({ queryKey: ['admin-users'] });
notify.success(u.createdSuccess);
setShowForm(false);
setForm({ email: '', password: '', firstName: '', lastName: '', role: 'user' });
setForm({ phone: '', email: '', password: '', firstName: '', lastName: '', role: 'user' });
},
onError: (err: any) => {
notify.error(err, u.createFailed);
@@ -141,10 +142,22 @@ export default function AdminUsersPage() {
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">{u.email}</label>
<label className="block text-sm font-medium text-gray-700 mb-1">{u.phone}</label>
<input
className="input-field"
type="tel"
dir="ltr"
placeholder={u.phonePlaceholder}
value={form.phone}
onChange={(e) => setForm({ ...form, phone: e.target.value })}
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">{u.emailOptional}</label>
<input
className="input-field"
type="email"
dir="ltr"
placeholder={u.emailPlaceholder}
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
@@ -179,7 +192,7 @@ export default function AdminUsersPage() {
</div>
<button
onClick={() => createUser.mutate(form)}
disabled={!form.email || !form.password || !form.firstName || !form.lastName || createUser.isPending}
disabled={!form.phone || !form.password || !form.firstName || !form.lastName || createUser.isPending}
className="btn-primary disabled:opacity-50"
>
{createUser.isPending ? <><Clock className="w-4 h-4 inline animate-spin" /> {u.creating}</> : u.createUser}
@@ -224,6 +237,7 @@ export default function AdminUsersPage() {
<thead className="bg-gray-50/80">
<tr>
<th className="px-6 py-3.5 text-left rtl:text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">{u.colUser}</th>
<th className="px-6 py-3.5 text-left rtl:text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">{u.colPhone}</th>
<th className="px-6 py-3.5 text-left rtl:text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">{u.colEmail}</th>
<th className="px-6 py-3.5 text-left rtl:text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">{u.colRole}</th>
<th className="px-6 py-3.5 text-left rtl:text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">{u.colStatus}</th>
@@ -238,7 +252,8 @@ export default function AdminUsersPage() {
<td className="px-6 py-4 text-sm font-medium text-gray-900">
{user.firstName} {user.lastName}
</td>
<td className="px-6 py-4 text-sm text-gray-600">{user.email}</td>
<td className="px-6 py-4 text-sm text-gray-600" dir="ltr">{user.phone || '—'}</td>
<td className="px-6 py-4 text-sm text-gray-600" dir="ltr">{user.email || '—'}</td>
<td className="px-6 py-4">
{isAdmin ? (
<Select
@@ -306,7 +321,7 @@ export default function AdminUsersPage() {
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold text-gray-900">{user.firstName} {user.lastName}</h3>
<p className="text-sm text-gray-500">{user.email}</p>
<p className="text-sm text-gray-500" dir="ltr">{user.phone || user.email || '—'}</p>
</div>
<span className={`badge ${user.isActive ? 'badge-green' : 'badge-red'}`}>
{user.isActive ? u.active : u.inactive}
@@ -375,7 +390,7 @@ export default function AdminUsersPage() {
<strong>
{pwdModalUser.firstName} {pwdModalUser.lastName}
</strong>{' '}
({pwdModalUser.email})
(<span dir="ltr">{pwdModalUser.phone || pwdModalUser.email}</span>)
</p>
<input
type="password"