迁移后端认证与拆分 Spacetime 客户端
This commit is contained in:
@@ -52,6 +52,10 @@ type AccountModalProps = {
|
||||
expiresInSeconds: number;
|
||||
}>;
|
||||
onChangePhone: (phone: string, code: string) => Promise<void>;
|
||||
onChangePassword: (
|
||||
currentPassword: string,
|
||||
newPassword: string,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
const SETTINGS_SECTIONS: Array<{
|
||||
@@ -285,24 +289,31 @@ export function AccountModal({
|
||||
changePhoneCaptchaChallenge,
|
||||
onSendChangePhoneCode,
|
||||
onChangePhone,
|
||||
onChangePassword,
|
||||
}: AccountModalProps) {
|
||||
const [activeSection, setActiveSection] =
|
||||
useState<PrimarySettingsSection | null>(
|
||||
normalizeSettingsSection(initialSection),
|
||||
);
|
||||
const [isChangePhonePanelOpen, setIsChangePhonePanelOpen] = useState(false);
|
||||
const [isPasswordPanelOpen, setIsPasswordPanelOpen] = useState(false);
|
||||
const [phone, setPhone] = useState('');
|
||||
const [code, setCode] = useState('');
|
||||
const [currentPassword, setCurrentPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [captchaAnswer, setCaptchaAnswer] = useState('');
|
||||
const [changePhoneError, setChangePhoneError] = useState('');
|
||||
const [passwordError, setPasswordError] = useState('');
|
||||
const [changePhoneHint, setChangePhoneHint] = useState('');
|
||||
const [accountNotice, setAccountNotice] = useState('');
|
||||
const [sendingCode, setSendingCode] = useState(false);
|
||||
const [changingPhone, setChangingPhone] = useState(false);
|
||||
const [changingPassword, setChangingPassword] = useState(false);
|
||||
const [cooldownSeconds, setCooldownSeconds] = useState(0);
|
||||
const settingsHomeRef = useRef<HTMLDivElement | null>(null);
|
||||
const sectionTriggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
const changePhoneTriggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
const passwordTriggerRef = useRef<HTMLButtonElement | null>(null);
|
||||
|
||||
const focusAfterNextPaint = useCallback((element: HTMLElement | null) => {
|
||||
if (!element) {
|
||||
@@ -325,6 +336,12 @@ export function AccountModal({
|
||||
setCooldownSeconds(0);
|
||||
}, []);
|
||||
|
||||
const resetPasswordDraft = useCallback(() => {
|
||||
setCurrentPassword('');
|
||||
setNewPassword('');
|
||||
setPasswordError('');
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
@@ -332,11 +349,14 @@ export function AccountModal({
|
||||
|
||||
setActiveSection(normalizeSettingsSection(initialSection));
|
||||
setIsChangePhonePanelOpen(false);
|
||||
setIsPasswordPanelOpen(false);
|
||||
setAccountNotice('');
|
||||
sectionTriggerRef.current = null;
|
||||
changePhoneTriggerRef.current = null;
|
||||
passwordTriggerRef.current = null;
|
||||
resetChangePhoneDraft();
|
||||
}, [initialSection, isOpen, resetChangePhoneDraft]);
|
||||
resetPasswordDraft();
|
||||
}, [initialSection, isOpen, resetChangePhoneDraft, resetPasswordDraft]);
|
||||
|
||||
useEffect(() => {
|
||||
const settingsHome = settingsHomeRef.current;
|
||||
@@ -368,10 +388,12 @@ export function AccountModal({
|
||||
const closeSectionPanel = useCallback(() => {
|
||||
const sectionTrigger = sectionTriggerRef.current;
|
||||
setIsChangePhonePanelOpen(false);
|
||||
setIsPasswordPanelOpen(false);
|
||||
setActiveSection(null);
|
||||
resetChangePhoneDraft();
|
||||
resetPasswordDraft();
|
||||
focusAfterNextPaint(sectionTrigger);
|
||||
}, [focusAfterNextPaint, resetChangePhoneDraft]);
|
||||
}, [focusAfterNextPaint, resetChangePhoneDraft, resetPasswordDraft]);
|
||||
|
||||
const closeChangePhonePanel = useCallback(() => {
|
||||
const changePhoneTrigger = changePhoneTriggerRef.current;
|
||||
@@ -380,6 +402,13 @@ export function AccountModal({
|
||||
focusAfterNextPaint(changePhoneTrigger);
|
||||
}, [focusAfterNextPaint, resetChangePhoneDraft]);
|
||||
|
||||
const closePasswordPanel = useCallback(() => {
|
||||
const passwordTrigger = passwordTriggerRef.current;
|
||||
setIsPasswordPanelOpen(false);
|
||||
resetPasswordDraft();
|
||||
focusAfterNextPaint(passwordTrigger);
|
||||
}, [focusAfterNextPaint, resetPasswordDraft]);
|
||||
|
||||
if (!isOpen) {
|
||||
return null;
|
||||
}
|
||||
@@ -556,6 +585,31 @@ export function AccountModal({
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-base font-semibold text-[var(--platform-text-strong)]">
|
||||
登录密码
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-[var(--platform-text-base)]">
|
||||
在独立面板中设置或修改账号密码。
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="platform-button platform-button--ghost min-h-0 rounded-full px-3 py-1.5 text-[11px]"
|
||||
onClick={(event) => {
|
||||
passwordTriggerRef.current = event.currentTarget;
|
||||
setAccountNotice('');
|
||||
resetPasswordDraft();
|
||||
setIsPasswordPanelOpen(true);
|
||||
}}
|
||||
>
|
||||
修改密码
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="platform-subpanel rounded-2xl px-4 py-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
@@ -893,6 +947,74 @@ export function AccountModal({
|
||||
</div>
|
||||
</OverlayPanel>
|
||||
) : null}
|
||||
|
||||
{isPasswordPanelOpen ? (
|
||||
<OverlayPanel
|
||||
eyebrow="账号安全"
|
||||
title="修改登录密码"
|
||||
description="输入当前密码与新密码。首次设置密码时当前密码可留空。"
|
||||
onBack={closePasswordPanel}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="grid gap-3">
|
||||
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
|
||||
<span>当前密码</span>
|
||||
<input
|
||||
className="platform-input h-11"
|
||||
value={currentPassword}
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
placeholder="首次设置可留空"
|
||||
onChange={(event) => setCurrentPassword(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
<label className="grid gap-2 text-sm text-[var(--platform-text-base)]">
|
||||
<span>新密码</span>
|
||||
<input
|
||||
className="platform-input h-11"
|
||||
value={newPassword}
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
placeholder="设置新密码"
|
||||
onChange={(event) => setNewPassword(event.target.value)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
{passwordError ? (
|
||||
<div className="platform-banner platform-banner--danger text-sm">
|
||||
{passwordError}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={changingPassword || !newPassword.trim()}
|
||||
className="platform-button platform-button--primary h-11 w-full text-sm disabled:cursor-not-allowed disabled:opacity-60"
|
||||
onClick={() => {
|
||||
void (async () => {
|
||||
setChangingPassword(true);
|
||||
setPasswordError('');
|
||||
try {
|
||||
await onChangePassword(currentPassword, newPassword);
|
||||
setAccountNotice('密码已更新。');
|
||||
closePasswordPanel();
|
||||
} catch (error) {
|
||||
setPasswordError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: '修改密码失败,请稍后再试。',
|
||||
);
|
||||
} finally {
|
||||
setChangingPassword(false);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
>
|
||||
{changingPassword ? '提交中...' : '确认修改密码'}
|
||||
</button>
|
||||
</div>
|
||||
</OverlayPanel>
|
||||
) : null}
|
||||
</OverlayPanel>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user