Merge branch 'feat/ui-0.7.0' into 'test'

chore: Add image capcha code when registering, Dynamically insert custom.css

See merge request opensource/answer!358
This commit is contained in:
贾海涛(龙笛) 2022-12-17 17:56:09 +00:00
commit 1c68d4be53
5 changed files with 178 additions and 114 deletions

View File

@ -5,7 +5,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="%PUBLIC_URL%/custom.css" />
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -30,6 +30,7 @@ const Layout: FC = () => {
<title>{pageTitle}</title> <title>{pageTitle}</title>
{keywords && <meta name="keywords" content={keywords} />} {keywords && <meta name="keywords" content={keywords} />}
{description && <meta name="description" content={description} />} {description && <meta name="description" content={description} />}
<link rel="stylesheet" href={`${process.env.PUBLIC_URL}/custom.css`} />
</Helmet> </Helmet>
<CustomizeTheme /> <CustomizeTheme />
<SWRConfig <SWRConfig

View File

@ -122,6 +122,7 @@ const Personal: FC = () => {
<h5 className="mb-3">{t('stats')}</h5> <h5 className="mb-3">{t('stats')}</h5>
{userInfo?.info && ( {userInfo?.info && (
<> <>
<FormatTime time={1671290521} preFix={t('last_login')} />
<div className="text-secondary"> <div className="text-secondary">
<FormatTime <FormatTime
time={userInfo.info.created_at} time={userInfo.info.created_at}

View File

@ -1,10 +1,17 @@
import React, { FormEvent, MouseEvent, useState } from 'react'; import React, { FormEvent, MouseEvent, useEffect, useState } from 'react';
import { Form, Button, Col } from 'react-bootstrap'; import { Form, Button, Col } from 'react-bootstrap';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import type { FormDataType } from '@/common/interface'; import { PicAuthCodeModal } from '@/components/Modal';
import { register, useLegalTos, useLegalPrivacy } from '@/services'; import { ImgCodeRes, LoginReqParams } from '@/common/interface';
import type { FormDataType, RegisterReqParams } from '@/common/interface';
import {
register,
getRegisterCaptcha,
useLegalTos,
useLegalPrivacy,
} from '@/services';
import userStore from '@/stores/userInfo'; import userStore from '@/stores/userInfo';
import { handleFormError } from '@/utils'; import { handleFormError } from '@/utils';
@ -38,6 +45,17 @@ const Index: React.FC<Props> = ({ callback }) => {
}); });
const updateUser = userStore((state) => state.update); const updateUser = userStore((state) => state.update);
const [imgCode, setImgCode] = useState<ImgCodeRes>({
captcha_id: '',
captcha_img: '',
verify: false,
});
const [showModal, setModalState] = useState(false);
const getImgCode = () => {
getRegisterCaptcha().then((res) => {
setImgCode(res);
});
};
const handleChange = (params: FormDataType) => { const handleChange = (params: FormDataType) => {
setFormData({ ...formData, ...params }); setFormData({ ...formData, ...params });
}; };
@ -104,17 +122,29 @@ const Index: React.FC<Props> = ({ callback }) => {
} }
}; };
const handleSubmit = async (event: FormEvent) => { const handleRegister = (event?: any) => {
event.preventDefault(); if (event) {
event.stopPropagation(); event.preventDefault();
if (!checkValidated()) {
return;
} }
register({ const params: LoginReqParams = {
e_mail: formData.e_mail.value,
pass: formData.pass.value,
};
if (imgCode.verify) {
params.captcha_code = formData.captcha_code.value;
params.captcha_id = imgCode.captcha_id;
}
const reqParams: RegisterReqParams = {
name: formData.name.value, name: formData.name.value,
e_mail: formData.e_mail.value, e_mail: formData.e_mail.value,
pass: formData.pass.value, pass: formData.pass.value,
}) };
if (imgCode.verify) {
reqParams.captcha_code = formData.captcha_code.value;
reqParams.captcha_id = imgCode.captcha_id;
}
register(reqParams)
.then((res) => { .then((res) => {
updateUser(res); updateUser(res);
callback(); callback();
@ -127,113 +157,141 @@ const Index: React.FC<Props> = ({ callback }) => {
}); });
}; };
const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
event.stopPropagation();
if (!checkValidated()) {
return;
}
if (imgCode.verify) {
setModalState(true);
return;
}
handleRegister();
};
useEffect(() => {
getImgCode();
}, []);
return ( return (
<Col className="mx-auto" md={3}> <>
<Form noValidate onSubmit={handleSubmit} autoComplete="off"> <Col className="mx-auto" md={3}>
<Form.Group controlId="name" className="mb-3"> <Form noValidate onSubmit={handleSubmit} autoComplete="off">
<Form.Label>{t('name.label')}</Form.Label> <Form.Group controlId="name" className="mb-3">
<Form.Control <Form.Label>{t('name.label')}</Form.Label>
autoComplete="off" <Form.Control
required autoComplete="off"
type="text" required
isInvalid={formData.name.isInvalid} type="text"
value={formData.name.value} isInvalid={formData.name.isInvalid}
onChange={(e) => value={formData.name.value}
handleChange({ onChange={(e) =>
name: { handleChange({
value: e.target.value, name: {
isInvalid: false, value: e.target.value,
errorMsg: '', isInvalid: false,
}, errorMsg: '',
}) },
} })
/> }
<Form.Control.Feedback type="invalid"> />
{formData.name.errorMsg} <Form.Control.Feedback type="invalid">
</Form.Control.Feedback> {formData.name.errorMsg}
</Form.Group> </Form.Control.Feedback>
<Form.Group controlId="email" className="mb-3"> </Form.Group>
<Form.Label>{t('email.label')}</Form.Label> <Form.Group controlId="email" className="mb-3">
<Form.Control <Form.Label>{t('email.label')}</Form.Label>
autoComplete="off" <Form.Control
required autoComplete="off"
type="e_mail" required
isInvalid={formData.e_mail.isInvalid} type="e_mail"
value={formData.e_mail.value} isInvalid={formData.e_mail.isInvalid}
onChange={(e) => value={formData.e_mail.value}
handleChange({ onChange={(e) =>
e_mail: { handleChange({
value: e.target.value, e_mail: {
isInvalid: false, value: e.target.value,
errorMsg: '', isInvalid: false,
}, errorMsg: '',
}) },
} })
/> }
<Form.Control.Feedback type="invalid"> />
{formData.e_mail.errorMsg} <Form.Control.Feedback type="invalid">
</Form.Control.Feedback> {formData.e_mail.errorMsg}
</Form.Group> </Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="password" className="mb-3"> <Form.Group controlId="password" className="mb-3">
<Form.Label>{t('password.label')}</Form.Label> <Form.Label>{t('password.label')}</Form.Label>
<Form.Control <Form.Control
autoComplete="off" autoComplete="off"
required required
type="password" type="password"
maxLength={32} maxLength={32}
isInvalid={formData.pass.isInvalid} isInvalid={formData.pass.isInvalid}
value={formData.pass.value} value={formData.pass.value}
onChange={(e) => onChange={(e) =>
handleChange({ handleChange({
pass: { pass: {
value: e.target.value, value: e.target.value,
isInvalid: false, isInvalid: false,
errorMsg: '', errorMsg: '',
}, },
}) })
} }
/> />
<Form.Control.Feedback type="invalid"> <Form.Control.Feedback type="invalid">
{formData.pass.errorMsg} {formData.pass.errorMsg}
</Form.Control.Feedback> </Form.Control.Feedback>
</Form.Group> </Form.Group>
<div className="d-grid"> <div className="d-grid">
<Button variant="primary" type="submit"> <Button variant="primary" type="submit">
{t('signup', { keyPrefix: 'btns' })} {t('signup', { keyPrefix: 'btns' })}
</Button> </Button>
</div>
</Form>
<div className="text-center fs-14 mt-3">
<Trans i18nKey="login.agreements" ns="translation">
By registering, you agree to the
<Link
to="/privacy"
onClick={(evt) => {
argumentClick(evt, 'privacy');
}}
target="_blank">
privacy policy
</Link>
and
<Link
to="/tos"
onClick={(evt) => {
argumentClick(evt, 'tos');
}}
target="_blank">
terms of service
</Link>
.
</Trans>
</div> </div>
</Form> <div className="text-center mt-5">
<div className="text-center fs-14 mt-3"> <Trans i18nKey="login.info_login" ns="translation">
<Trans i18nKey="login.agreements" ns="translation"> Already have an account? <Link to="/users/login">Log in</Link>
By registering, you agree to the </Trans>
<Link </div>
to="/privacy" </Col>
onClick={(evt) => { <PicAuthCodeModal
argumentClick(evt, 'privacy'); visible={showModal}
}} data={{
target="_blank"> captcha: formData.captcha_code,
privacy policy imgCode,
</Link> }}
and handleCaptcha={handleChange}
<Link clickSubmit={handleRegister}
to="/tos" refreshImgCode={getImgCode}
onClick={(evt) => { onClose={() => setModalState(false)}
argumentClick(evt, 'tos'); />
}} </>
target="_blank">
terms of service
</Link>
.
</Trans>
</div>
<div className="text-center mt-5">
<Trans i18nKey="login.info_login" ns="translation">
Already have an account? <Link to="/users/login">Log in</Link>
</Trans>
</div>
</Col>
); );
}; };

View File

@ -96,6 +96,11 @@ export const register = (params: Type.RegisterReqParams) => {
return request.post<any>('/answer/api/v1/user/register/email', params); return request.post<any>('/answer/api/v1/user/register/email', params);
}; };
export const getRegisterCaptcha = () => {
const apiUrl = '/answer/api/v1/user/register/captcha';
return request.get(apiUrl);
};
export const logout = () => { export const logout = () => {
return request.get('/answer/api/v1/user/logout'); return request.get('/answer/api/v1/user/logout');
}; };