mirror of
https://github.com/labring/sealos.git
synced 2026-09-24 15:46:19 +08:00
fix(desktop): email/phone binding/unbinding ux improvments (#5717)
* fix(desktop): remove cftoken verif for email/phone binding Signed-off-by: Nixieboluo <me@sagirii.me> * fix(desktop): turnstile widget refresh causes unintended sms resends Signed-off-by: Nixieboluo <me@sagirii.me> * fix(desktop): captcha timer starts before code was sent Signed-off-by: Nixieboluo <me@sagirii.me> * feat(desktop): clear input field and error state when resending sms Signed-off-by: Nixieboluo <me@sagirii.me> * fix(desktop): page state not reset on account settings modal close Signed-off-by: Nixieboluo <me@sagirii.me> * fix(desktop): incorrect title on email/phone change/binding/unbinding modal Signed-off-by: Nixieboluo <me@sagirii.me> * chore(desktop): clean up filterCf Signed-off-by: Nixieboluo <me@sagirii.me> * chore(desktop): minor i18n phrase casing fix Signed-off-by: Nixieboluo <me@sagirii.me> --------- Signed-off-by: Nixieboluo <me@sagirii.me>
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@kubernetes/client-node": "^0.18.1",
|
||||
"@marsidev/react-turnstile": "^0.5.3",
|
||||
"@marsidev/react-turnstile": "^1.1.0",
|
||||
"@prisma/client": "^5.10.2",
|
||||
"@sealos/driver": "workspace:^",
|
||||
"@sealos/ui": "workspace:^",
|
||||
@@ -104,4 +104,4 @@
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"prettier": "^2.8.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
"billing": "Billing",
|
||||
"bind": "Link",
|
||||
"bind_success": "Binding successful",
|
||||
"bindemail": "Bind Email",
|
||||
"bindphone": "Bind Phone",
|
||||
"bonus": "Bonus",
|
||||
"bound": "Bound",
|
||||
"business_license": "business license",
|
||||
@@ -40,7 +42,7 @@
|
||||
"captcha_init_failed": "Failed to initialize CAPTCHA",
|
||||
"change": "Change",
|
||||
"change_binding": "Change Binding",
|
||||
"changeemail": "Modify email",
|
||||
"changeemail": "Modify Email",
|
||||
"changepassword": "Change Password",
|
||||
"changephone": "Change Phone",
|
||||
"charge": "Charge",
|
||||
@@ -284,6 +286,8 @@
|
||||
"transAmt_not_match": "The verification amount does not match. Please note that the verification amount is in cents. For example, if the received amount is 0.23 yuan, enter 23.",
|
||||
"unbind": "Unbind",
|
||||
"unbind_success": "Unbinding successfully",
|
||||
"unbindemail": "Unbind Email",
|
||||
"unbindphone": "Unbind Phone",
|
||||
"unbound": "Not Linked",
|
||||
"under_active_development": "Under active development 🚧",
|
||||
"unread": "Unread",
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"register": "Register",
|
||||
"remember_me": "Remember me",
|
||||
"request_new_link": "Resend it",
|
||||
"sending_code": "Sending verification code...",
|
||||
"settings": "Settings",
|
||||
"show_me": "Show me",
|
||||
"sign_in": "Sign in",
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
"billing": "费用中心",
|
||||
"bind": "绑定",
|
||||
"bind_success": "绑定成功",
|
||||
"bindemail": "绑定电子邮箱",
|
||||
"bindphone": "绑定手机号",
|
||||
"bonus": "赠",
|
||||
"bound": "已绑定",
|
||||
"business_license": "营业执照",
|
||||
@@ -276,6 +278,8 @@
|
||||
"transAmt_not_match": "验证金额不匹配,注意验证金额单位是分,例如收到的打款金额为 0.23 元 则输入23。",
|
||||
"unbind": "解绑",
|
||||
"unbind_success": "解绑成功",
|
||||
"unbindemail": "解绑电子邮箱",
|
||||
"unbindphone": "解绑手机号",
|
||||
"unbound": "未绑定",
|
||||
"under_active_development": "正在积极开发中 🚧",
|
||||
"unread": "未读",
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
"register": "注册",
|
||||
"remember_me": "记住我",
|
||||
"request_new_link": "重新发送验证码",
|
||||
"sending_code": "正在发送验证码...",
|
||||
"settings": "设置",
|
||||
"show_me": "展示",
|
||||
"sign_in": "登录",
|
||||
@@ -130,4 +131,4 @@
|
||||
"worspace_heading_description": "计算资源、应用数据和团队权限一体化",
|
||||
"you_may_invite_memebers_later": "稍后可邀请成员共同协作",
|
||||
"your_current_plan": "当前套餐"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,17 +85,6 @@ export default function AccountCenter(props: AccountCenterProps) {
|
||||
infoData.refetch();
|
||||
};
|
||||
|
||||
const modalTitle = useMemo(() => {
|
||||
if (pageState === PageState.INDEX) return t('common:account_settings');
|
||||
else if (pageState === PageState.PASSWORD) return t('common:changepassword');
|
||||
else if (Object.values(PhoneState).includes(pageState as PhoneState))
|
||||
return t('common:changephone');
|
||||
else if (Object.values(EmailState).includes(pageState as EmailState))
|
||||
return t('common:changeemail');
|
||||
else if (pageState === PageState.REALNAME_AUTH) return t('common:realName_verification');
|
||||
else return '';
|
||||
}, [t, pageState]);
|
||||
|
||||
const infoData = useQuery({
|
||||
queryFn: UserInfo,
|
||||
queryKey: [session?.token, 'UserInfo'],
|
||||
@@ -146,6 +135,19 @@ export default function AccountCenter(props: AccountCenterProps) {
|
||||
return state;
|
||||
}, [infoData.data?.oauthProvider]);
|
||||
|
||||
const modalTitle = useMemo(() => {
|
||||
if (pageState === PageState.INDEX) return t('common:account_settings');
|
||||
else if (pageState === PageState.PASSWORD) return t('common:changepassword');
|
||||
else if (pageState === PageState.EMAIL_BIND) return t('common:bindemail');
|
||||
else if (pageState === PageState.EMAIL_UNBIND) return t('common:unbindemail');
|
||||
else if (pageState === PageState.EMAIL_CHANGE_BIND) return t('common:changeemail');
|
||||
else if (pageState === PageState.PHONE_BIND) return t('common:bindphone');
|
||||
else if (pageState === PageState.PHONE_UNBIND) return t('common:unbindphone');
|
||||
else if (pageState === PageState.PHONE_CHANGE_BIND) return t('common:changephone');
|
||||
else if (pageState === PageState.REALNAME_AUTH) return t('common:realName_verification');
|
||||
else return '';
|
||||
}, [t, pageState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{children ? (
|
||||
@@ -172,7 +174,14 @@ export default function AccountCenter(props: AccountCenterProps) {
|
||||
icon={<SettingIcon boxSize={'16px'} fill={'rgba(255, 255, 255, 0.7)'} />}
|
||||
/>
|
||||
)}
|
||||
<Modal isOpen={isOpen} onClose={onClose} isCentered>
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={() => {
|
||||
resetPageState();
|
||||
onClose();
|
||||
}}
|
||||
isCentered
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent
|
||||
borderRadius={'12px'}
|
||||
|
||||
@@ -37,6 +37,7 @@ export default function EmailCheckComponent() {
|
||||
const { signupData, clearSignupData, startTime, updateStartTime, setStartTime } =
|
||||
useSignupStore();
|
||||
const { setToken } = useSessionStore();
|
||||
const [pinValue, setPinValue] = useState('');
|
||||
useEffect(() => {
|
||||
if (!signupData) {
|
||||
router.push('/signin');
|
||||
@@ -106,7 +107,6 @@ export default function EmailCheckComponent() {
|
||||
const sendCode = async (cfToken?: string) => {
|
||||
setIsLoading(true);
|
||||
const oldTime = startTime;
|
||||
updateStartTime();
|
||||
setCanResend(false);
|
||||
|
||||
try {
|
||||
@@ -120,6 +120,8 @@ export default function EmailCheckComponent() {
|
||||
});
|
||||
if (result.code !== 200) {
|
||||
throw Error(result.message);
|
||||
} else {
|
||||
updateStartTime();
|
||||
}
|
||||
} catch (error) {
|
||||
// rollout
|
||||
@@ -142,6 +144,11 @@ export default function EmailCheckComponent() {
|
||||
const onSubmit = async (force = false) => {
|
||||
if ((!canResend || isLoading) && !force) return;
|
||||
|
||||
// Clear error state
|
||||
verifyMutation.reset();
|
||||
// Clear input field
|
||||
setPinValue('');
|
||||
|
||||
if (authConfig?.turnstile.enabled) {
|
||||
turnstileRef.current?.reset();
|
||||
} else {
|
||||
@@ -182,7 +189,9 @@ export default function EmailCheckComponent() {
|
||||
{!!authConfig?.turnstile.enabled && (
|
||||
<Turnstile
|
||||
options={{
|
||||
size: 'normal'
|
||||
size: 'normal',
|
||||
refreshExpired: 'never',
|
||||
refreshTimeout: 'never'
|
||||
}}
|
||||
ref={turnstileRef}
|
||||
siteKey={authConfig?.turnstile.cloudflare.siteKey}
|
||||
@@ -202,6 +211,8 @@ export default function EmailCheckComponent() {
|
||||
placeholder=""
|
||||
focusBorderColor="#18181B"
|
||||
autoFocus
|
||||
value={pinValue}
|
||||
onChange={setPinValue}
|
||||
isDisabled={verifyMutation.isLoading}
|
||||
onComplete={(value) => {
|
||||
console.log('Verification code:', value);
|
||||
@@ -221,8 +232,28 @@ export default function EmailCheckComponent() {
|
||||
</PinInput>
|
||||
</FormControl>
|
||||
|
||||
{isLoading && (
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px'
|
||||
}}
|
||||
>
|
||||
{t('v2:sending_code')}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{verifyMutation.isLoading ? (
|
||||
<Text>{t('v2:verifying')}</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px'
|
||||
}}
|
||||
>
|
||||
{t('v2:verifying')}
|
||||
</Text>
|
||||
) : (
|
||||
<Flex>
|
||||
{verifyMutation.isError && (
|
||||
|
||||
@@ -38,6 +38,7 @@ export default function PhoneCheckComponent() {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { captchaIsLoaded } = useScriptStore();
|
||||
const { signupData, clearSignupData, startTime, updateStartTime } = useSignupStore();
|
||||
const [pinValue, setPinValue] = useState('');
|
||||
const { setToken } = useSessionStore();
|
||||
|
||||
const getRemainTime = () => 60000 - new Date().getTime() + startTime;
|
||||
@@ -185,6 +186,11 @@ export default function PhoneCheckComponent() {
|
||||
const onSubmit = async (force = false) => {
|
||||
if ((!canResend || isLoading) && !force) return;
|
||||
|
||||
// Clear error state
|
||||
verifyMutation.reset();
|
||||
// Clear input field
|
||||
setPinValue('');
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (!signupData || signupData.providerType !== 'PHONE') {
|
||||
@@ -269,6 +275,8 @@ export default function PhoneCheckComponent() {
|
||||
placeholder=""
|
||||
focusBorderColor="#18181B"
|
||||
autoFocus
|
||||
value={pinValue}
|
||||
onChange={setPinValue}
|
||||
isDisabled={verifyMutation.isLoading}
|
||||
onComplete={(value) => {
|
||||
verifyMutation.mutate({ code: value, id: signupData?.providerId || '' });
|
||||
@@ -287,8 +295,28 @@ export default function PhoneCheckComponent() {
|
||||
</PinInput>
|
||||
</FormControl>
|
||||
|
||||
{sendCodeMutation.isLoading && (
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px'
|
||||
}}
|
||||
>
|
||||
{t('v2:sending_code')}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{verifyMutation.isLoading ? (
|
||||
<Text>{t('v2:verifying')}</Text>
|
||||
<Text
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px'
|
||||
}}
|
||||
>
|
||||
{t('v2:verifying')}
|
||||
</Text>
|
||||
) : (
|
||||
<Flex>
|
||||
{verifyMutation.isError && (
|
||||
|
||||
@@ -2,20 +2,18 @@ import { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { filterAccessToken } from '@/services/backend/middleware/access';
|
||||
import { enableEmailSms } from '@/services/enable';
|
||||
import { ErrorHandler } from '@/services/backend/middleware/error';
|
||||
import { filterCf, filterEmailParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { filterEmailParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { sendEmailCodeSvc } from '@/services/backend/svc/sms';
|
||||
|
||||
export default ErrorHandler(async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (!enableEmailSms()) {
|
||||
throw new Error('SMS is not enabled');
|
||||
}
|
||||
await filterCf(req, res, async () => {
|
||||
await filterAccessToken(req, res, () =>
|
||||
filterEmailParams(req, res, ({ email }) =>
|
||||
sendSmsCodeGuard({ id: email, smsType: 'email_bind' })(req, res, () =>
|
||||
sendEmailCodeSvc(email, 'email_bind')(res)
|
||||
)
|
||||
await filterAccessToken(req, res, () =>
|
||||
filterEmailParams(req, res, ({ email }) =>
|
||||
sendSmsCodeGuard({ id: email, smsType: 'email_bind' })(req, res, () =>
|
||||
sendEmailCodeSvc(email, 'email_bind')(res)
|
||||
)
|
||||
);
|
||||
});
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { filterAccessToken } from '@/services/backend/middleware/access';
|
||||
import { ErrorHandler } from '@/services/backend/middleware/error';
|
||||
import { unbindEmailGuard } from '@/services/backend/middleware/oauth';
|
||||
import { filterCf, filterEmailParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { filterEmailParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { sendEmailCodeSvc } from '@/services/backend/svc/sms';
|
||||
import { enableEmailSms } from '@/services/enable';
|
||||
import { NextApiRequest, NextApiResponse } from 'next';
|
||||
@@ -11,14 +11,12 @@ export default ErrorHandler(async function handler(req: NextApiRequest, res: Nex
|
||||
throw new Error('SMS is not enabled');
|
||||
}
|
||||
await filterAccessToken(req, res, ({ userUid }) =>
|
||||
filterCf(req, res, async () =>
|
||||
filterEmailParams(req, res, ({ email }) =>
|
||||
unbindEmailGuard(email, userUid)(res, () =>
|
||||
sendSmsCodeGuard({
|
||||
id: email,
|
||||
smsType: 'email_unbind'
|
||||
})(req, res, () => sendEmailCodeSvc(email, 'email_unbind')(res))
|
||||
)
|
||||
filterEmailParams(req, res, ({ email }) =>
|
||||
unbindEmailGuard(email, userUid)(res, () =>
|
||||
sendSmsCodeGuard({
|
||||
id: email,
|
||||
smsType: 'email_unbind'
|
||||
})(req, res, () => sendEmailCodeSvc(email, 'email_unbind')(res))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { filterAccessToken } from '@/services/backend/middleware/access';
|
||||
import { ErrorHandler } from '@/services/backend/middleware/error';
|
||||
import { filterCf, filterPhoneParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { filterPhoneParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { cnVersionMiddleware } from '@/services/backend/middleware/version';
|
||||
import { sendPhoneCodeSvc } from '@/services/backend/svc/sms';
|
||||
import { enablePhoneSms } from '@/services/enable';
|
||||
@@ -11,15 +11,13 @@ export default ErrorHandler(async function handler(req: NextApiRequest, res: Nex
|
||||
if (!enablePhoneSms()) {
|
||||
throw new Error('SMS is not enabled');
|
||||
}
|
||||
await filterCf(req, res, async () => {
|
||||
await filterAccessToken(req, res, () =>
|
||||
filterPhoneParams(req, res, ({ phoneNumbers: phone }) =>
|
||||
sendSmsCodeGuard({
|
||||
id: phone,
|
||||
smsType: 'phone_bind'
|
||||
})(req, res, () => sendPhoneCodeSvc(phone, 'phone_bind')(res))
|
||||
)
|
||||
);
|
||||
});
|
||||
await filterAccessToken(req, res, () =>
|
||||
filterPhoneParams(req, res, ({ phoneNumbers: phone }) =>
|
||||
sendSmsCodeGuard({
|
||||
id: phone,
|
||||
smsType: 'phone_bind'
|
||||
})(req, res, () => sendPhoneCodeSvc(phone, 'phone_bind')(res))
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,6 @@ export default ErrorHandler(async function handler(req: NextApiRequest, res: Nex
|
||||
if (!enablePhoneSms()) {
|
||||
throw new Error('SMS is not enabled');
|
||||
}
|
||||
// await filterCf(req, res, async () => {
|
||||
await filterCaptcha(req, res, () =>
|
||||
filterPhoneParams(req, res, ({ phoneNumbers: phone }) =>
|
||||
sendSmsCodeGuard({
|
||||
@@ -23,6 +22,5 @@ export default ErrorHandler(async function handler(req: NextApiRequest, res: Nex
|
||||
})(req, res, () => sendPhoneCodeSvc(phone, 'phone_login')(res))
|
||||
)
|
||||
);
|
||||
// });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { filterAccessToken } from '@/services/backend/middleware/access';
|
||||
import { ErrorHandler } from '@/services/backend/middleware/error';
|
||||
import { unbindPhoneGuard } from '@/services/backend/middleware/oauth';
|
||||
import { filterCf, filterPhoneParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { filterPhoneParams, sendSmsCodeGuard } from '@/services/backend/middleware/sms';
|
||||
import { cnVersionMiddleware } from '@/services/backend/middleware/version';
|
||||
import { sendPhoneCodeSvc } from '@/services/backend/svc/sms';
|
||||
import { enablePhoneSms } from '@/services/enable';
|
||||
@@ -13,23 +13,21 @@ export default ErrorHandler(async function handler(req: NextApiRequest, res: Nex
|
||||
throw new Error('SMS is not enabled');
|
||||
}
|
||||
await filterAccessToken(req, res, async ({ userUid }) => {
|
||||
await filterCf(req, res, async () => {
|
||||
await filterPhoneParams(
|
||||
req,
|
||||
res,
|
||||
async ({ phoneNumbers }) =>
|
||||
await unbindPhoneGuard(phoneNumbers, userUid)(
|
||||
res,
|
||||
async () =>
|
||||
await sendSmsCodeGuard({
|
||||
id: phoneNumbers,
|
||||
smsType: 'phone_unbind'
|
||||
})(req, res, async () => {
|
||||
await sendPhoneCodeSvc(phoneNumbers, 'phone_unbind')(res);
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
await filterPhoneParams(
|
||||
req,
|
||||
res,
|
||||
async ({ phoneNumbers }) =>
|
||||
await unbindPhoneGuard(phoneNumbers, userUid)(
|
||||
res,
|
||||
async () =>
|
||||
await sendSmsCodeGuard({
|
||||
id: phoneNumbers,
|
||||
smsType: 'phone_unbind'
|
||||
})(req, res, async () => {
|
||||
await sendPhoneCodeSvc(phoneNumbers, 'phone_unbind')(res);
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+6
-6
@@ -103,8 +103,8 @@ importers:
|
||||
specifier: ^0.18.1
|
||||
version: 0.18.1
|
||||
'@marsidev/react-turnstile':
|
||||
specifier: ^0.5.3
|
||||
version: 0.5.3(react-dom@18.2.0)(react@18.2.0)
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.0(react-dom@18.2.0)(react@18.2.0)
|
||||
'@prisma/client':
|
||||
specifier: ^5.10.2
|
||||
version: 5.10.2(prisma@5.10.2)
|
||||
@@ -8383,11 +8383,11 @@ packages:
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
dev: false
|
||||
|
||||
/@marsidev/react-turnstile@0.5.3(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-lx3p2/56esPt8Ksr37K8uhPt/K4Mg8xaIfCV8MPKmE/1X4aHesRqZok1+L1ySQwsdWoEe5+KJOhBXka8lFBwNg==}
|
||||
/@marsidev/react-turnstile@1.1.0(react-dom@18.2.0)(react@18.2.0):
|
||||
resolution: {integrity: sha512-X7bP9ZYutDd+E+klPYF+/BJHqEyyVkN4KKmZcNRr84zs3DcMoftlMAuoKqNSnqg0HE7NQ1844+TLFSJoztCdSA==}
|
||||
peerDependencies:
|
||||
react: '>=16.8.0'
|
||||
react-dom: '>=16.8.0'
|
||||
react: ^17.0.2 || ^18.0.0 || ^19.0
|
||||
react-dom: ^17.0.2 || ^18.0.0 || ^19.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
react-dom: 18.2.0(react@18.2.0)
|
||||
|
||||
Reference in New Issue
Block a user