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:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user