Why: Approved redesign phase 2 (IA): normal users run projects entirely through conversation; system capabilities (user management, agent profiles, plugin management, plugin-declared workspaces) belong behind a dedicated admin entry, not in the user-facing sidebar or settings. The sidebar workspace strip is removed; artifacts stay reachable via the right-rail tab and conversation chips. Evidence: npx tsc -b clean; npm test -> 38 passed (design-guard ratchet still green). Full combined run recorded in the Dev task card. Impact: new AdminZone full-screen view with a declarative section registry (users/profiles/plugins/plugin-workspaces via UIRenderer); sections moved out of SettingsModal (now personal preferences only) and AdminUsersModal (now password change only); store ui.adminUsersOpen renamed adminZoneOpen; workspaceEntries narrowed by role so plugin entries no longer take Cmd1-9 for regular users; theme toggle moved into the account menu. Refs: Agent-System-Dev task feature/ui-tokens-v3 (phase 2).
65 lines
2.9 KiB
TypeScript
65 lines
2.9 KiB
TypeScript
// 管理员用户管理已迁「系统管理」分区(components/adminSections.tsx UsersPanel,
|
||
// 重设计方案 阶段②)。本文件只保留登录者主动改密弹窗(与强制改密共用 API)。
|
||
import { useState } from 'react'
|
||
import { api } from '../api'
|
||
import { store, useStore } from '../store'
|
||
import { Icon } from '@ui'
|
||
|
||
function Shell({ title, onClose, children }: { title: string; onClose: () => void; children: React.ReactNode }) {
|
||
return (
|
||
<div className="overlay" onClick={e => { if (e.target === e.currentTarget) onClose() }}>
|
||
<div className="modal ad-card" role="dialog" aria-label={title} style={{ maxWidth: 640 }}>
|
||
<div className="modal-header">
|
||
<b>{title}</b>
|
||
<span className="spacer" />
|
||
<button className="ad-mini" onClick={onClose} aria-label="关闭"><Icon name="x" size={13} /></button>
|
||
</div>
|
||
<div className="ad-body">{children}</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
/** 主动改密(账户菜单入口):当前密码 + 新密码 + 确认。 */
|
||
export function ChangePasswordModal({ onClose }: { onClose: () => void }) {
|
||
const me = useStore(s => s.me)
|
||
const [current, setCurrent] = useState('')
|
||
const [next, setNext] = useState('')
|
||
const [confirm, setConfirm] = useState('')
|
||
const [err, setErr] = useState('')
|
||
const [ok, setOk] = useState(false)
|
||
const [busy, setBusy] = useState(false)
|
||
|
||
const submit = async () => {
|
||
setErr('')
|
||
if (next !== confirm) { setErr('两次输入的新密码不一致'); return }
|
||
if (next.length < 8) { setErr('新密码至少 8 位'); return }
|
||
setBusy(true)
|
||
try {
|
||
await api.changePassword(current, next)
|
||
setOk(true)
|
||
store.toast('密码已更新')
|
||
setTimeout(onClose, 900)
|
||
} catch (e) {
|
||
setErr(String(e).includes('401') ? '当前密码不正确' : String(e))
|
||
} finally { setBusy(false) }
|
||
}
|
||
|
||
return (
|
||
<Shell title="修改密码" onClose={onClose}>
|
||
{ok ? <div className="ad-notice">密码已更新,其他设备的会话已注销。</div> : (
|
||
<div className="ad-changepw">
|
||
<label className="lg-label">当前密码({me?.owner ?? ''})</label>
|
||
<input className="lg-input" type="password" value={current} onChange={e => setCurrent(e.target.value)} autoFocus />
|
||
<label className="lg-label">新密码(≥8 位)</label>
|
||
<input className="lg-input" type="password" value={next} onChange={e => setNext(e.target.value)} />
|
||
<label className="lg-label">确认新密码</label>
|
||
<input className="lg-input" type="password" value={confirm} onChange={e => setConfirm(e.target.value)} />
|
||
{err && <div className="lg-err"><Icon name="alert" size={14} />{err}</div>}
|
||
<button className="lg-submit" disabled={busy || !current || !next || !confirm} onClick={submit}>{busy ? '提交中…' : '更新密码'}</button>
|
||
</div>
|
||
)}
|
||
</Shell>
|
||
)
|
||
}
|