fix: render admin password reset as modal
This commit is contained in:
@@ -31,6 +31,17 @@ await runTest('admin user management exposes reset password without hiding it be
|
||||
assert.match(source, /onClick=\{\(\) => startResetPassword\(editingUser\)\}/);
|
||||
});
|
||||
|
||||
await runTest('admin reset password form is rendered as an overlay dialog', () => {
|
||||
const source = read('src/components/admin/user-management-tab.tsx');
|
||||
const resetSection = source.slice(source.indexOf('{resetPwUser && ('), source.indexOf('{editingUser && ('));
|
||||
|
||||
assert.match(resetSection, /fixed inset-0 z-50/);
|
||||
assert.match(resetSection, /max-h-\[90vh\] overflow-y-auto/);
|
||||
assert.doesNotMatch(resetSection, /\{resetPwUser && \(\s*<Card className="border-primary\/30">/);
|
||||
assert.match(source, /setRechargeUser\(null\)/);
|
||||
assert.match(source, /setShowAddForm\(false\)/);
|
||||
});
|
||||
|
||||
await runTest('admin password reset upserts auth credentials instead of silently updating zero rows', () => {
|
||||
const source = read('src/lib/admin-users-service.ts');
|
||||
|
||||
|
||||
@@ -221,6 +221,9 @@ export default function UserManagementTab() {
|
||||
|
||||
const startEdit = (user: ManagedUser) => {
|
||||
setEditingUser(user);
|
||||
setResetPwUser(null);
|
||||
setRechargeUser(null);
|
||||
setShowAddForm(false);
|
||||
setEditRole(user.role); setEditTier(user.membershipTier);
|
||||
setEditEmail(user.email || '');
|
||||
setEditCredits(String(user.creditsBalance)); setEditQuota(String(user.dailyQuotaLimit));
|
||||
@@ -232,6 +235,8 @@ export default function UserManagementTab() {
|
||||
setResetPwUser(user);
|
||||
setNewPassword('');
|
||||
setEditingUser(null);
|
||||
setRechargeUser(null);
|
||||
setShowAddForm(false);
|
||||
};
|
||||
|
||||
const handleSaveEdit = async () => {
|
||||
@@ -289,6 +294,9 @@ export default function UserManagementTab() {
|
||||
setRechargeAmount('');
|
||||
setRechargeReason('管理员手动充值');
|
||||
setRechargeMode('add');
|
||||
setEditingUser(null);
|
||||
setResetPwUser(null);
|
||||
setShowAddForm(false);
|
||||
};
|
||||
|
||||
const handleRecharge = async () => {
|
||||
@@ -545,7 +553,16 @@ export default function UserManagementTab() {
|
||||
</Button>
|
||||
</div>
|
||||
{activeView === 'users' && (
|
||||
<Button size="sm" className="gap-1.5" onClick={() => setShowAddForm(true)}>
|
||||
<Button
|
||||
size="sm"
|
||||
className="gap-1.5"
|
||||
onClick={() => {
|
||||
setShowAddForm(true);
|
||||
setEditingUser(null);
|
||||
setResetPwUser(null);
|
||||
setRechargeUser(null);
|
||||
}}
|
||||
>
|
||||
<Plus className="h-4 w-4" />添加用户
|
||||
</Button>
|
||||
)}
|
||||
@@ -847,39 +864,43 @@ export default function UserManagementTab() {
|
||||
|
||||
{/* Reset Password Form */}
|
||||
{resetPwUser && (
|
||||
<Card className="border-primary/30">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<KeyRound className="h-5 w-5 text-primary" />
|
||||
重置密码 - {resetPwUser.nickname}
|
||||
</CardTitle>
|
||||
<CardDescription>{resetPwUser.email}</CardDescription>
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 px-4 py-6">
|
||||
<Card className="w-full max-w-lg border-primary/30 shadow-xl max-h-[90vh] overflow-y-auto">
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<KeyRound className="h-5 w-5 text-primary" />
|
||||
重置密码 - {resetPwUser.nickname}
|
||||
</CardTitle>
|
||||
<CardDescription>{resetPwUser.email}</CardDescription>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="h-8 w-8 p-0" onClick={() => { setResetPwUser(null); setNewPassword(''); }}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => { setResetPwUser(null); setNewPassword(''); }}>✕</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>新密码</Label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="输入新密码(至少6位)"
|
||||
value={newPassword}
|
||||
onChange={e => setNewPassword(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">重置后请通知用户使用新密码登录</p>
|
||||
</div>
|
||||
<div className="flex gap-3 justify-end pt-2">
|
||||
<Button variant="outline" onClick={() => { setResetPwUser(null); setNewPassword(''); }}>取消</Button>
|
||||
<Button className="gap-1.5" onClick={handleResetPassword} disabled={resetPwLoading || newPassword.length < 6}>
|
||||
<KeyRound className="h-4 w-4" />
|
||||
{resetPwLoading ? '重置中...' : '确认重置'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<Label>新密码</Label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="输入新密码(至少6位)"
|
||||
value={newPassword}
|
||||
onChange={e => setNewPassword(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">重置后请通知用户使用新密码登录</p>
|
||||
</div>
|
||||
<div className="flex gap-3 justify-end pt-2">
|
||||
<Button variant="outline" onClick={() => { setResetPwUser(null); setNewPassword(''); }}>取消</Button>
|
||||
<Button className="gap-1.5" onClick={handleResetPassword} disabled={resetPwLoading || newPassword.length < 6}>
|
||||
<KeyRound className="h-4 w-4" />
|
||||
{resetPwLoading ? '重置中...' : '确认重置'}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Edit User Form */}
|
||||
|
||||
Reference in New Issue
Block a user