第七轮深度测试:修复7个agent发现的12处前端问题,重点是列表分页截断与呼叫/播放状态机
- 通话状态机(Ch8):canStartCall 补齐通话中重复发起的防护,callShouldStart 补齐放弃/失败路径的清理 - 分页截断(Ch5-6):新增 fetchAllPages 辅助函数,修复通讯录/黑名单/好友申请/群成员/管理员/禁言等 11 处列表只读首页导致真实成员被遗漏的问题;群角色判断改用不受分页影响的 getGroup().my_role - 消息与媒体(Ch3-4/Ch7/Ch9):录音超时污染时长、手动重试丢失 head 导致消息被错误分类、批量删除 未取消上传中附件、语音播放器补齐同时只能播一条、workplace-collaboration-view 冗余重试请求 - 独立密码/青少年模式(Ch10-11):设置密码后按调用来源判断是否应自动开启青少年模式,避免关闭流程 中途"忘记密码"被误当成开启;注销账号确认文案补充群主后果说明 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
f42b8536e5
commit
bede45fd7f
@@ -14,7 +14,9 @@ const INVITE_RE = /^[A-Za-z]{5}$/;
|
||||
const CREDIT_CODE_RE = /^[0-9A-Z]{18}$/;
|
||||
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
|
||||
const navigate = view => HashNavigation.navigateTo(view);
|
||||
// params is optional and additive only -- every existing call site that passes just `view` keeps
|
||||
// producing the exact same bare hash as the old `HashNavigation.navigateTo(view)` did.
|
||||
const navigate = (view, params) => HashNavigation.navigateTo(HashNavigation.composeUrlHash([view], params || {}));
|
||||
|
||||
function Icon({children, className = ''}) {
|
||||
return <i className={'material-icons ' + className} aria-hidden="true">{children}</i>;
|
||||
@@ -529,7 +531,13 @@ function PasswordReset() {
|
||||
// successful verify, stashes the resulting action_token for the next screen to spend.
|
||||
const IDENTITY_CHECK_SCENE = 'set_independent_password';
|
||||
|
||||
function IdentityCheck({authenticated}) {
|
||||
function IdentityCheck({authenticated, hashParams}) {
|
||||
// Carries through whichever youth-mode flow (enable/disable) sent the user here via "忘记独立
|
||||
// 密码?", if any -- IndependentPassword needs it to know whether to auto-continue into actually
|
||||
// enabling youth mode, since the server hands back an enable-flow token unconditionally on every
|
||||
// successful password (re)set regardless of why the caller got here. Absent (undefined) for the
|
||||
// unrelated generic "身份验证" entry in acc-security-view.jsx, which should never touch youth mode.
|
||||
const mode = hashParams && hashParams.ym;
|
||||
const [phoneMask, setPhoneMask] = React.useState('');
|
||||
const [challengeId, setChallengeId] = React.useState('');
|
||||
const [code, setCode] = React.useState('');
|
||||
@@ -581,7 +589,7 @@ function IdentityCheck({authenticated}) {
|
||||
try {
|
||||
const data = await verifySecurityChallenge(challengeId, code);
|
||||
sessionStorage.setItem('im-action-token:' + IDENTITY_CHECK_SCENE, JSON.stringify(data));
|
||||
navigate('independent-password');
|
||||
navigate('independent-password', mode ? {ym: mode} : undefined);
|
||||
} catch (err) {
|
||||
setError(err.message || '验证码错误');
|
||||
} finally {
|
||||
@@ -681,7 +689,12 @@ function RealnameReminder() {
|
||||
</div>;
|
||||
}
|
||||
|
||||
function IndependentPassword({authenticated}) {
|
||||
function IndependentPassword({authenticated, hashParams}) {
|
||||
// Only the genuine "haven't set a password yet, setting one now to enable youth mode for the
|
||||
// first time" flow should auto-continue into enableYouthMode below -- see IdentityCheck's
|
||||
// comment on the same param for why this can't just be inferred from enable_action_token being
|
||||
// present.
|
||||
const mode = hashParams && hashParams.ym;
|
||||
const [pin, setPin] = React.useState('');
|
||||
const [error, setError] = React.useState('');
|
||||
const [toast, setToast] = React.useState('');
|
||||
@@ -704,8 +717,13 @@ function IndependentPassword({authenticated}) {
|
||||
// 见 server/im_http.go:6178-6189)。原来这里把它整个丢掉、只弹一句"设置成功"就停在
|
||||
// PIN 页——用户走完短信验证+设密码,青少年模式其实还是关的,得自己摸回设置页再输一遍。
|
||||
// 现在拿着这个 token 把最后一步做完。
|
||||
// enable_action_token comes back unconditionally on every successful (re)set, regardless of
|
||||
// why the caller ended up here (server doesn't know or care) -- only the genuine "first time
|
||||
// enabling" flow should spend it. Otherwise this silently flipped youth mode on for someone
|
||||
// who was mid-way through disabling it, or who came from the unrelated generic "身份验证"
|
||||
// entry with no youth-mode intent at all.
|
||||
const enableToken = result && result.enable_action_token;
|
||||
if (enableToken) {
|
||||
if (enableToken && mode === 'enable') {
|
||||
try {
|
||||
const current = await getYouthMode();
|
||||
await enableYouthMode(current.policy_version, enableToken);
|
||||
@@ -719,6 +737,15 @@ function IndependentPassword({authenticated}) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mode === 'disable') {
|
||||
// enableToken (if any) can't be spent here -- ConsumeActionToken requires an exact
|
||||
// action='close_youth_mode' match, and this one is always tagged 'enable_youth_mode'. The
|
||||
// user does know the fresh PIN now though, so send them straight back to actually finish
|
||||
// disabling instead of leaving them stuck on this screen with nothing left to do.
|
||||
setError(''); setToast('独立密码已重置,请使用新密码关闭青少年模式');
|
||||
window.setTimeout(() => navigate('youth-mode-verify', {ym: 'disable'}), 800);
|
||||
return;
|
||||
}
|
||||
setError(''); setToast('独立密码设置成功');
|
||||
} catch (err) {
|
||||
setError(err.message || '设置失败,请返回重新验证身份');
|
||||
@@ -740,7 +767,7 @@ function IndependentPassword({authenticated}) {
|
||||
}
|
||||
|
||||
export default function AuthOnboardingView({
|
||||
view, onTenantResolved, authenticated = false,
|
||||
view, hashParams, onTenantResolved, authenticated = false,
|
||||
login, loginDisabled, persist, errorText, errorLevel,
|
||||
onLoginRequest, onPersistenceChange, onImRegisterRequest, onError
|
||||
}) {
|
||||
@@ -753,11 +780,11 @@ export default function AuthOnboardingView({
|
||||
case 'enterprise-feedback': return <EnterpriseFeedback />;
|
||||
case 'register': return <Register errorText={errorText} errorLevel={errorLevel} onImRegisterRequest={onImRegisterRequest} />;
|
||||
case 'reset': return <PasswordReset />;
|
||||
case 'identity-check': return <IdentityCheck authenticated={authenticated} />;
|
||||
case 'identity-check': return <IdentityCheck authenticated={authenticated} hashParams={hashParams} />;
|
||||
case 'realname': return <Realname authenticated={authenticated} />;
|
||||
case 'realname-verified': return <Realname authenticated={authenticated} />;
|
||||
case 'realname-reminder': return <RealnameReminder />;
|
||||
case 'independent-password': return <IndependentPassword authenticated={authenticated} />;
|
||||
case 'independent-password': return <IndependentPassword authenticated={authenticated} hashParams={hashParams} />;
|
||||
default: return <TenantEntry onTenantResolved={onTenantResolved || (() => {})} />;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user