Commit 4481f62d authored by nanahira's avatar nanahira

add vaptcha

parents 2c3bc432 cd0209ef
Pipeline #28198 passed with stages
in 1 minute and 54 seconds
This diff is collapsed.
......@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="index.css"/>
<link rel="icon" href="https://moecube.com/favicon.ico">
<link rel="icon" href="/favicon.ico">
<script src="https://v-cn.vaptcha.com/v3.js"></script>
</head>
<body>
......
import { Form, Input } from 'antd';
import { Button, Form, Input } from 'antd';
import React from 'react';
import { connect } from 'react-redux';
import SubmitButton from './SubmitButton';
......@@ -11,13 +11,15 @@ const formItemLayout = {
class EmailForm extends React.Component {
state = {
countdown: 0,
inCountdown: false, // Whether the countdown is active
};
// static contextTypes = {
// intl: PropTypes.object.isRequired,
// };
onSubmit = (e) => {
const { form, dispatch, user: { id } } = this.props;
if (e) {
e.preventDefault();
}
......@@ -28,17 +30,48 @@ class EmailForm extends React.Component {
const { email, password } = values;
dispatch({ type: 'user/updateEmail', payload: { email, password, user_id: id } });
this.setState({ countdown: 5, inCountdown: true });
this.countdownTimer = setInterval(this.handleCountdown, 1000);
}
});
};
handleCountdown = () => {
// 倒计时减1
this.setState(prevState => ({ countdown: prevState.countdown - 1 }));
if (this.state.countdown === 0) {
// 清除倒计时
clearInterval(this.countdownTimer);
// 设置倒计时状态为false
this.setState({ inCountdown: false });
// 倒计时结束后调用退出
this.handleLogout();
}
};
handleLogout = () => {
clearInterval(this.countdownTimer);
window.localStorage.removeItem('token');
if (window.ygopro) {
window.ygopro.logoutUser('YGOMobile萌卡已登出');
}
const url = new URL(window.location.href);
const redirect = url.searchParams.get('redirect') || '/';
window.location.href = redirect;
};
handleLogoutNow = () => {
this.setState({ countdown: 0 }, () => {
this.handleLogout();
});
};
render() {
const { form, dispatch, user, checkEmail, isEmailExists, messages } = this.props;
const { getFieldDecorator } = form;
const { id, email } = user;
// const { intl: { messages } } = this.context;
const logoutNowText = this.props.messages.LogoutNow;
const loggingOutInText = this.props.messages.LoggingOutIn;
const secondsText = this.props.messages.Seconds;
const ChangeSuccessText = this.props.messages.EmailChangeSuccess;
const emailProps = {
fromItem: {
label: messages.email,
......@@ -74,28 +107,47 @@ class EmailForm extends React.Component {
type: 'password',
},
};
const { inCountdown, countdown } = this.state;
return (
<Form onSubmit={this.onSubmit}>
<FormItem {...emailProps.fromItem}>
{getFieldDecorator('email', { ...emailProps.decorator })(
<Input
{...emailProps.input}
onBlur={() => dispatch({ type: 'auth/checkEmail', payload: { ...form.getFieldsValue(), user_id: id } })}
/>,
)}
</FormItem>
<FormItem {...passwordProps.fromItem}>
{getFieldDecorator('password', { ...passwordProps.decorator })(
<Input {...passwordProps.input} />,
)}
</FormItem>
<FormItem>
<SubmitButton />
</FormItem>
</Form>
<div>
{inCountdown ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100vh' }}>
<div style={{ flex: 9 / 10, textAlign: 'center' }}>
<h3>{ChangeSuccessText}</h3>
<h3>
{loggingOutInText}
<span style={{ color: '#4da9ee', fontSize: '2em' }}>&nbsp;{`${countdown}`}</span>
<span>&nbsp;{secondsText}</span>
</h3>
<br/>
<Button type="link" onClick={this.handleLogoutNow}>
{logoutNowText}
</Button>
</div>
</div>
) : (
<Form onSubmit={this.onSubmit}>
<FormItem {...emailProps.fromItem}>
{getFieldDecorator('email', { ...emailProps.decorator })(
<Input
{...emailProps.input}
onBlur={() => dispatch({ type: 'auth/checkEmail', payload: { ...form.getFieldsValue(), user_id: id } })}
/>,
)}
</FormItem>
<FormItem {...passwordProps.fromItem}>
{getFieldDecorator('password', { ...passwordProps.decorator })(
<Input {...passwordProps.input} />,
)}
</FormItem>
<FormItem>
<SubmitButton />
</FormItem>
</Form>
)}
</div>
);
}
}
......
import { Form, Input } from 'antd';
import { Form, Input, Button } from 'antd';
import React from 'react';
import { connect } from 'react-redux';
import SubmitButton from './SubmitButton';
const FormItem = Form.Item;
const formItemLayout = {
labelCol: { span: 4 },
wrapperCol: { span: 15 },
......@@ -14,7 +12,10 @@ const formItemLayout = {
class EmailForm extends React.Component {
state = {
countdown: 0,
inCountdown: false, // 是否处于倒计时状态
};
onSubmit = (e) => {
const { form, dispatch, user: { id } } = this.props;
......@@ -28,13 +29,47 @@ class EmailForm extends React.Component {
const { new_password, password } = values;
dispatch({ type: 'user/updateAccount', payload: { new_password, password, user_id: id } });
// 设置倒计时状态为5秒
this.setState({ countdown: 5, inCountdown: true });
// 启动倒计时
this.countdownTimer = setInterval(this.handleCountdown, 1000);
}
});
};
handleCountdown = () => {
// 倒计时减1
this.setState(prevState => ({ countdown: prevState.countdown - 1 }));
if (this.state.countdown === 0) {
// 清除倒计时
clearInterval(this.countdownTimer);
// 设置倒计时状态为false
this.setState({ inCountdown: false });
// 倒计时结束后调用退出
this.handleLogout();
}
};
handleLogout = () => {
clearInterval(this.countdownTimer);
window.localStorage.removeItem('token');
if (window.ygopro) {
window.ygopro.logoutUser('YGOMobile萌卡已登出');
}
const url = new URL(window.location.href);
const redirect = url.searchParams.get('redirect') || '/';
window.location.href = redirect;
};
handleLogoutNow = () => {
this.setState({ countdown: 0 }, () => {
this.handleLogout();
});
};
checkPassword = (rule, value, callback) => {
const { form, messages } = this.props;
//const { intl: { messages } } = this.context;
// const { intl: { messages } } = this.context;
if (value && value !== form.getFieldValue('new_password')) {
callback(messages['Incorrect-password.2']);
} else {
......@@ -54,7 +89,10 @@ class EmailForm extends React.Component {
render() {
const { form, messages } = this.props;
const { getFieldDecorator } = form;
const logoutNowText = this.props.messages.LogoutNow;
const loggingOutInText = this.props.messages.LoggingOutIn;
const secondsText = this.props.messages.Seconds;
const ChangeSuccessText = this.props.messages.ChangeSuccess;
const passwordProps = {
fromItem: {
label: messages['old-password'],
......@@ -100,31 +138,50 @@ class EmailForm extends React.Component {
type: 'password',
},
};
const { inCountdown, countdown } = this.state;
return (
<Form onSubmit={this.onSubmit}>
<FormItem {...passwordProps.fromItem} label={messages['old-password']}>
{getFieldDecorator('password')(
<Input {...passwordProps.input} />,
)}
</FormItem>
<FormItem {...passwordProps.fromItem} label={messages['new-password']}>
{getFieldDecorator('new_password', { ...passwordProps.decorator })(
<Input {...passwordProps.input2} />,
)}
</FormItem>
<FormItem {...confirmProps.fromItem}>
{getFieldDecorator('confirm', { ...confirmProps.decorator })(
<Input {...confirmProps.input} />,
)}
</FormItem>
<FormItem>
<SubmitButton />
</FormItem>
</Form>
<div>
{inCountdown ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100vh' }}>
<div style={{ flex: 9 / 10, textAlign: 'center' }}>
<h3>{ChangeSuccessText}</h3>
<h3>
{loggingOutInText}
<span style={{ color: '#4da9ee', fontSize: '2em' }}>&nbsp;{`${countdown}`}</span>
<span>&nbsp;{secondsText}</span>
</h3>
<br/>
<Button type="link" onClick={this.handleLogoutNow}>
{logoutNowText}
</Button>
</div>
</div>
) : (
<Form onSubmit={this.onSubmit}>
<FormItem {...passwordProps.fromItem} label={messages['old-password']}>
{getFieldDecorator('password')(
<Input {...passwordProps.input} />,
)}
</FormItem>
<FormItem {...passwordProps.fromItem} label={messages['new-password']}>
{getFieldDecorator('new_password', { ...passwordProps.decorator })(
<Input {...passwordProps.input2} />,
)}
</FormItem>
<FormItem {...confirmProps.fromItem}>
{getFieldDecorator('confirm', { ...confirmProps.decorator })(
<Input {...confirmProps.input} />,
)}
</FormItem>
<FormItem>
<SubmitButton />
</FormItem>
</Form>
)}
</div>
);
}
}
......@@ -135,6 +192,7 @@ function mapStateToProps(state) {
user: { user },
common: { messages },
} = state;
return {
messages,
user,
......
import { Form, Input } from 'antd';
import { Button, Form, Input } from 'antd';
import React from 'react';
import { connect } from 'react-redux';
import SubmitButton from './SubmitButton';
......@@ -11,7 +11,10 @@ const formItemLayout = {
class EmailForm extends React.Component {
state = {
countdown: 0,
inCountdown: false, // Whether the countdown is active
};
onSubmit = (e) => {
const { form, dispatch, user: { id } } = this.props;
......@@ -21,25 +24,74 @@ class EmailForm extends React.Component {
form.validateFieldsAndScroll((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
const { username, password } = values;
dispatch({ type: 'user/updateAccount', payload: { username, password, user_id: id } });
this.setState({ countdown: 5, inCountdown: true });
this.countdownTimer = setInterval(this.handleCountdown, 1000);
}
});
};
handleCountdown = () => {
// 倒计时减1
this.setState(prevState => ({ countdown: prevState.countdown - 1 }));
if (this.state.countdown === 0) {
// 清除倒计时
clearInterval(this.countdownTimer);
// 设置倒计时状态为false
this.setState({ inCountdown: false });
// 倒计时结束后调用退出
this.handleLogout();
}
};
handleLogout = () => {
clearInterval(this.countdownTimer);
window.localStorage.removeItem('token');
if (window.ygopro) {
window.ygopro.logoutUser('YGOMobile萌卡已登出');
}
const url = new URL(window.location.href);
const redirect = url.searchParams.get('redirect') || '/';
window.location.href = redirect;
};
handleLogoutNow = () => {
this.setState({ countdown: 0 }, () => {
this.handleLogout();
});
};
checkPassword = (rule, value, callback) => {
const { form, messages } = this.props;
// const { intl: { messages } } = this.context;
if (value && value !== form.getFieldValue('new_password')) {
callback(messages['Incorrect-password.2']);
} else {
callback();
}
};
checkConfirm = (rule, value, callback) => {
const form = this.props.form;
if (value) {
form.validateFields(['confirm'], { force: true });
}
callback();
};
render() {
const { form, dispatch, user, checkUsername, isUserNameExists, messages } = this.props;
const { getFieldDecorator } = form;
const { id, username } = user;
const logoutNowText = this.props.messages.LogoutNow;
const loggingOutInText = this.props.messages.LoggingOutIn;
const secondsText = this.props.messages.Seconds;
const temporaryPromptText = this.props.messages.TemporaryPrompt;
const ChangeSuccessText = this.props.messages.ChangeSuccess;
const usernameProps = {
fromItem: {
label: messages.username,
hasFeedback: true,
validateStatus: checkUsername,
extra: isUserNameExists ? 'username exists' : '',
extra: isUserNameExists ? messages.i_username_exists_or_invalid : '',
...formItemLayout,
},
decorator: {
......@@ -70,34 +122,52 @@ class EmailForm extends React.Component {
type: 'password',
},
};
const { inCountdown, countdown } = this.state;
return (
<Form onSubmit={this.onSubmit}>
<FormItem {...usernameProps.fromItem}>
{getFieldDecorator('username', { ...usernameProps.decorator })(
<Input {...usernameProps.input} disabled/>,
)}
{
<div class="alert alert-warning" role="alert">改名已经关闭。改名将在1月,4月,7月和10月的第一周开启。</div>
}
</FormItem>
<FormItem {...passwordProps.fromItem}>
{getFieldDecorator('password', { ...passwordProps.decorator })(
<Input {...passwordProps.input} />,
)}
</FormItem>
<FormItem>
<SubmitButton disabled/>
</FormItem>
</Form>
<div>
{inCountdown ? (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', height: '100vh' }}>
<div style={{ flex: 9 / 10, textAlign: 'center' }}>
<h3>{ChangeSuccessText}</h3>
<h3>
{loggingOutInText}
<span style={{ color: '#4da9ee', fontSize: '2em' }}>&nbsp;{`${countdown}`}</span>
<span>&nbsp;{secondsText}</span>
</h3>
<br/>
<Button type="link" onClick={this.handleLogoutNow}>
{logoutNowText}
</Button>
</div>
</div>
) : (
<Form onSubmit={this.onSubmit}>
<FormItem {...usernameProps.fromItem}>
{getFieldDecorator('username', { ...usernameProps.decorator })(
<Input {...usernameProps.input} />,
)}
{
<div className="alert alert-warning" role="alert">{temporaryPromptText}</div>
}
</FormItem>
<FormItem {...passwordProps.fromItem}>
{getFieldDecorator('password', { ...passwordProps.decorator })(
<Input {...passwordProps.input} />,
)}
</FormItem>
<FormItem>
<SubmitButton/>
</FormItem>
</Form>
)}
</div>
);
}
}
function mapStateToProps(state) {
const {
user: { user },
......
......@@ -11,6 +11,9 @@ function mutatedUserAvatar(avatar) {
if (!avatar) {
return avatar;
}
if (avatar.includes('default_avatar')) {
return defaultAvatar;
}
return `https://sapi.moecube.com:444/avatar/url/${avatar.split('/').pop()}/40/avatar.png`;
}
......
......@@ -24,6 +24,9 @@ export default {
signOut(state) {
console.log('sign out');
localStorage.removeItem('token');
if (window.ygopro) {
window.ygopro.logoutUser("YGOMobile萌卡登出");
}
location.href = '/';
return state;
},
......
......@@ -214,6 +214,9 @@ export default {
}
if (pathname === '/logout') {
localStorage.removeItem('token');
if (window.ygopro) {
window.ygopro.logoutUser("YGOMobile 萌卡登出");
}
location.href = query.redirect;
}
});
......
......@@ -9,6 +9,8 @@ import Format from '../components/Format';
import logo from '../assets/MoeCube.png';
import UserPanel from '../components/UserPanel';
import './Index.less';
const languageMap = {
'zh-CN': '中文',
'en-US': 'English',
......@@ -151,10 +153,10 @@ function Index({ children, messages, dispatch, client, language }) {
);
return (
<div style={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: '100%' }}>
<DocumentTitle title={messages.title || 'Moe Cube'}/>
<DocumentTitle title={messages.title || 'MoeCube'}/>
{client !== 'electron' &&
<Header style={{ display: 'flex', alignItems: 'center' }}>
<Header style={{ display: 'flex', alignItems: 'center' }} className="mc-header">
<Link to="/" style={{ marginTop: '20px' }}>
<img alt="logo" src={logo} style={{ width: '140px', height: '44px' }}/>
</Link>
......@@ -182,7 +184,7 @@ function Index({ children, messages, dispatch, client, language }) {
</Menu.Item>
</Menu>
<div style={{ position: 'absolute', right: '40px' }}>
<div className="mc-userpanel">
<UserPanel />
</div>
</Header>
......@@ -202,7 +204,7 @@ function Index({ children, messages, dispatch, client, language }) {
{languageMap[language]} <Icon type="down" className="flag"/>
</a>
</Dropdown></div>
<div>© MoeCube 2017-2022 all right reserved.</div>
<div>© MoeCube 2017-2023 all right reserved.</div>
</Footer>
</div>
);
......
.particles {
position: fixed;
top: 0;
......@@ -7,3 +6,20 @@
width: 100%;
height: 100%;
}
:global {
.mc-userpanel {
position: absolute;
right: 40px;
}
@media (max-width: 768px) {
.mc-header {
padding: 0 10px;
}
.mc-userpanel {
right: 10px;
}
}
}
\ No newline at end of file
......@@ -4,6 +4,9 @@ import React from 'react';
class Logout extends React.Component {
componentDidMount() {
window.localStorage.removeItem('token');
if (window.ygopro) {
window.ygopro.logoutUser("YGOMobile萌卡已登出");
}
const url = new URL(window.location.href);
const redirect = url.searchParams.get('redirect') || '/';
window.location.href = redirect;
......
......@@ -107,28 +107,31 @@ class Profiles extends React.Component {
<Form onSubmit={this.onUpdateSubmit}>
<FormItem style={{ display: 'flex', justifyContent: 'center' }}>
<div style={{ display: isUpload ? 'flex' : 'none', flexDirection: 'column'}}>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<Cropper
ref={(cropper) => {
this.cropper = cropper;
}}
src={imageUrl || defaultAvatar}
className="cropper-image"
style={{ height: '20vw', width: '20vw' }}
style={{ height: '300px', width: '300px', display: isUpload ? 'block' : 'none' }}
aspectRatio={1 / 1}
autoCropArea={1}
guides
/>
<br/>
<Button type="primary" onClick={this.handleUpload}>
<Icon type="upload"/> <Format id="upload"/>
<img
alt="avatar"
style={{ height: '300px', width: '300px', display: !isUpload ? 'block' : 'none' }}
src={avatar || imageUrl || defaultAvatar}
/>
<Button type="primary" onClick={this.handleUpload} style={{ display: isUpload ? 'inline-block' : 'none', marginTop: '2em' }}>
<Icon type="check"/> <Format id="Upload-Avatar"/>
</Button>
</div>
<div style={{ display: !isUpload ? 'flex' : 'none', flexDirection: 'column' }}>
<img alt="avatar" style={{ height: '20vw', width: '20vw' }} src={avatar || imageUrl || defaultAvatar}/>
<Button style={{ padding: '4px 0' }}>
<Button type="default" style={{ marginTop: '2em' }}>
<label style={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Icon type="plus"/><Format id="Change-Avatar"/>
<Icon type="plus" style={{ marginRight: '0.5em' }} />
{(!isUpload) && <Format id="Change-Avatar"/>}
{isUpload && <Format id="Reselect-Avatar"/>}
<input
type="file" onChange={this.onGetFile} ref={(file) => {
this.file = file;
......@@ -143,6 +146,9 @@ class Profiles extends React.Component {
{getFieldDecorator('name', { ...nameProps.decorator })(
<Input {...nameProps.input} />,
)}
{
<div className="alert alert-warning" role="alert"><Format id="Nickname-Hint"/></div>
}
</FormItem>
<FormItem>
......
......@@ -74,7 +74,7 @@ class Register extends React.Component {
const usernameProps = {
hasFeedback: true,
validateStatus: checkUsername,
extra: isUserNameExists ? 'username exists or invalid ' : '',
extra: isUserNameExists ? messages.i_username_exists : '',
};
const usernameInputProps = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment