现象:从 account-settings-view.jsx"账户设置"、info-view.jsx,以及第2章系统消息列表"前往
安全设置"跳转按钮都能到达 acc-security-view.jsx 这个"安全"页(并非废弃页面,是与
personal-settings-view.jsx"设置"页并存的独立入口),点击"登出"没有任何二次确认,单击
图标就会立即退出登录,且没有任何撤销机会。
根因:该文件里"登出"按钮的 onClick 直接调用 this.props.onLogout(),而同文件里紧挨着的
"删除帐户"按钮走的是 onShowAlert 弹确认框(handleDeleteAccount)。此前 46a98782 那一轮
已经把 personal-settings-view.jsx"设置"页的登出从原生 window.confirm() 改成了自己的
ConfirmSheet/LogoutConfirm,但漏改了这个仍然可达的旧版"安全"页入口。
修复:新增 handleLogoutRequest,复用本文件已有的 onShowAlert 机制弹确认框,文案
("退出登录"/"确定要退出当前登录吗?")与 personal-settings-view.jsx 的 LogoutConfirm
保持一致,不新造一套措辞。
验证:本地登录后访问 #security,点击"登出"确认弹出对话框(标题/正文/按钮文案核对无误);
点击"取消"保持登录态且仍停留在安全页;点击"好"确认后正确清除 auth-token 并退回登录/租户
判断逻辑接管的空 hash。
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
210 lines
8.9 KiB
React
210 lines
8.9 KiB
React
// Edit account parameters.
|
||
import React from 'react';
|
||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||
|
||
import CheckBox from '../widgets/checkbox.jsx';
|
||
import InPlaceEdit from '../widgets/in-place-edit.jsx';
|
||
import PermissionsEditor from '../widgets/permissions-editor.jsx';
|
||
|
||
const messages = defineMessages({
|
||
delete_account: {
|
||
id: 'delete_account',
|
||
defaultMessage: 'Delete account',
|
||
description: 'Title for delete account warning'
|
||
},
|
||
delete_account_warning: {
|
||
id: 'delete_account_warning',
|
||
defaultMessage: 'Are you sure you want to delete your account? It cannot be undone.',
|
||
description: 'Warning message when deleting an account'
|
||
}
|
||
});
|
||
|
||
class AccSecurityView extends React.Component {
|
||
constructor(props) {
|
||
super(props);
|
||
|
||
const me = this.props.tinode.getMeTopic();
|
||
let blockedCount = 0;
|
||
me.contacts((c) => {
|
||
if (c.acs && !c.acs.isJoiner()) {
|
||
blockedCount ++;
|
||
}
|
||
});
|
||
const defacs = me.getDefaultAccess();
|
||
this.state = {
|
||
auth: defacs ? defacs.auth : null,
|
||
anon: defacs ? defacs.anon : null,
|
||
showPermissionEditorFor: undefined,
|
||
blockedCount: blockedCount,
|
||
};
|
||
|
||
this.handlePasswordUpdate = this.handlePasswordUpdate.bind(this);
|
||
this.handleLaunchPermissionsEditor = this.handleLaunchPermissionsEditor.bind(this);
|
||
this.handleHidePermissionsEditor = this.handleHidePermissionsEditor.bind(this);
|
||
this.handlePermissionsChanged = this.handlePermissionsChanged.bind(this);
|
||
this.handleDeleteAccount = this.handleDeleteAccount.bind(this);
|
||
this.handleLogoutRequest = this.handleLogoutRequest.bind(this);
|
||
this.handleCheckboxClick = this.handleCheckboxClick.bind(this);
|
||
}
|
||
|
||
handlePasswordUpdate(pwd) {
|
||
this.setState({password: pwd});
|
||
this.props.onUpdatePassword(pwd);
|
||
}
|
||
|
||
handleLaunchPermissionsEditor(which) {
|
||
this.setState({
|
||
showPermissionEditorFor: which,
|
||
editedPermissions: this.state[which]
|
||
});
|
||
}
|
||
|
||
handleHidePermissionsEditor() {
|
||
this.setState({showPermissionEditorFor: undefined});
|
||
}
|
||
|
||
handlePermissionsChanged(perm) {
|
||
let defacs = {};
|
||
defacs[this.state.showPermissionEditorFor] = perm;
|
||
this.props.onUpdateAccountDesc('me', undefined, undefined, defacs);
|
||
|
||
let newState = {showPermissionEditorFor: undefined};
|
||
newState[this.state.showPermissionEditorFor] = perm;
|
||
this.setState(newState);
|
||
}
|
||
|
||
handleCheckboxClick(what, checked) {
|
||
this.props.onToggleIncognitoMode(checked);
|
||
}
|
||
|
||
handleDeleteAccount(e) {
|
||
e.preventDefault();
|
||
const {formatMessage} = this.props.intl;
|
||
this.props.onShowAlert(
|
||
formatMessage(messages.delete_account), // title
|
||
formatMessage(messages.delete_account_warning), // content
|
||
(_ => this.props.onDeleteAccount()), // onConfirm
|
||
null, // use default text "OK"
|
||
true, // Show Reject button
|
||
null // use default text "Cancel"
|
||
);
|
||
}
|
||
|
||
// "退出登录"这个按钮原来是 onClick 里直接 `this.props.onLogout()`,没有任何二次确认——同一个文件里
|
||
// 紧挨着的"删除帐户"按钮反而是走 onShowAlert 弹确认框的(见上面 handleDeleteAccount)。本页
|
||
// ('security' 路由)并不是废弃页面:account-settings-view.jsx、info-view.jsx 的"安全"入口,以及
|
||
// 第2章系统消息列表里"前往安全设置"跳转按钮(message-center-view.jsx)都会真的落到这个页面,
|
||
// 跟 personal-settings-view.jsx 的"设置"页(那边的退出登录已经在别的改造里换成了自己的
|
||
// ConfirmSheet/LogoutConfirm)是两条并存的独立路径——用户从系统消息点"前往安全设置"进来,单击一下
|
||
// 图标就会被立即退出登录,且没有任何撤销机会。改成跟"删除帐户"一样走 onShowAlert,文案与
|
||
// personal-settings-view.jsx 的 LogoutConfirm 保持一致,不新造一套措辞。
|
||
handleLogoutRequest(e) {
|
||
e.preventDefault();
|
||
this.props.onShowAlert(
|
||
'退出登录',
|
||
'确定要退出当前登录吗?',
|
||
(_ => this.props.onLogout()),
|
||
null, // use default text "OK"
|
||
true, // Show Reject button
|
||
null // use default text "Cancel"
|
||
);
|
||
}
|
||
|
||
render() {
|
||
return (
|
||
<>{this.state.showPermissionEditorFor ?
|
||
<PermissionsEditor
|
||
mode={this.state.editedPermissions}
|
||
skip="O"
|
||
onSubmit={this.handlePermissionsChanged}
|
||
onCancel={this.handleHidePermissionsEditor} />
|
||
:
|
||
<div className="scrollable-panel">
|
||
<div className="panel-form-column">
|
||
<label className="small">
|
||
<FormattedMessage id="label_password" defaultMessage="Password"
|
||
description="Label for password editing" />
|
||
</label>
|
||
<div>
|
||
<FormattedMessage id="password_unchanged_prompt" defaultMessage="Unchanged"
|
||
description="Message in editor while password is unchanged">{
|
||
(password_unchanged) => <InPlaceEdit
|
||
placeholder={password_unchanged}
|
||
type="password"
|
||
onFinished={this.handlePasswordUpdate} />
|
||
}</FormattedMessage>
|
||
</div>
|
||
</div>
|
||
<div className="hr" />
|
||
<div className="panel-form-row">
|
||
<label htmlFor="incognito-mode">
|
||
<FormattedMessage id="label_incognito_mode" defaultMessage="Incognito mode:"
|
||
description="Label for incognito mode toggle" />
|
||
</label>
|
||
<CheckBox name="incognito" id="incognito-mode"
|
||
checked={this.props.incognitoMode} onChange={this.handleCheckboxClick} />
|
||
</div>
|
||
<div className="panel-form-row">
|
||
<span className="small gray">
|
||
<FormattedMessage id="incognito_mode_description" defaultMessage="Hide your online status and typing indicators."
|
||
description="Description for incognito mode toggle" />
|
||
</span>
|
||
</div>
|
||
<div className="hr" />
|
||
<div className="panel-form-column security-product-links">
|
||
<button type="button" className="flat-button" onClick={() => this.props.onNavigate('realname')}>
|
||
<i className="material-icons">verified_user</i><span>实名认证</span><i className="material-icons trailing">chevron_right</i>
|
||
</button>
|
||
<button type="button" className="flat-button" onClick={() => this.props.onNavigate('identity-check')}>
|
||
<i className="material-icons">phonelink_lock</i><span>身份验证</span><i className="material-icons trailing">chevron_right</i>
|
||
</button>
|
||
<button type="button" className="flat-button" onClick={() => this.props.onNavigate('independent-password')}>
|
||
<i className="material-icons">password</i><span>设置独立密码</span><i className="material-icons trailing">chevron_right</i>
|
||
</button>
|
||
</div>
|
||
<div className="hr" />
|
||
<div className="panel-form-column">
|
||
<a href="#" className="danger flat-button" onClick={this.handleLogoutRequest}>
|
||
<i className="material-icons">exit_to_app</i> <FormattedMessage id="button_logout"
|
||
defaultMessage="Logout" description="Button [Logout]" />
|
||
</a>
|
||
<a href="#" className="danger flat-button" onClick={(e) => {this.handleDeleteAccount(e);}}>
|
||
<i className="material-icons">delete</i> <FormattedMessage id="button_delete_account"
|
||
defaultMessage="Delete account" description="Button [Delete account]" />
|
||
</a>
|
||
</div>
|
||
<div className="hr" />
|
||
<div className="panel-form-column">
|
||
<div>
|
||
<label className="small">
|
||
<FormattedMessage id="label_default_access_mode" defaultMessage="Default access mode:"
|
||
description="Label for default access mode" />
|
||
</label>
|
||
</div>
|
||
<div className="quoted">
|
||
<div>Auth: <tt className="clickable"
|
||
onClick={this.handleLaunchPermissionsEditor.bind(this, 'auth')}>{this.state.auth}</tt></div>
|
||
<div>Anon: <tt className="clickable"
|
||
onClick={this.handleLaunchPermissionsEditor.bind(this, 'anon')}>{this.state.anon}</tt></div>
|
||
</div>
|
||
</div>
|
||
{this.state.blockedCount > 0 ?
|
||
<>
|
||
<div className="hr" />
|
||
<div className="panel-form-row">
|
||
<i className="material-icons">block</i>
|
||
<a href="#" className="gray" onClick={(e) => {e.preventDefault(); this.props.onShowBlocked();}}>
|
||
<FormattedMessage id="blocked_contacts_link" defaultMessage="Blocked contacts ({count})"
|
||
values={{ count: this.state.blockedCount }} description="Blocked contacts link" />
|
||
</a>
|
||
</div>
|
||
</>
|
||
: null }
|
||
</div>
|
||
}</>
|
||
);
|
||
}
|
||
};
|
||
|
||
export default injectIntl(AccSecurityView);
|