LogInForm, Rankings, ChangeMail, NewPasswordForm, DeleteAccount, UserArea and ChangeName from class to hook
create clickOutside hooks and use those in context menus fix window naviagions
This commit is contained in:
parent
a60242617d
commit
e80e5737ea
|
@ -220,10 +220,17 @@ export function requestPasswordChange(newPassword, password) {
|
|||
|
||||
export async function requestResendVerify() {
|
||||
return makeAPIGETRequest(
|
||||
'./api/auth/resend_verify',
|
||||
'api/auth/resend_verify',
|
||||
);
|
||||
}
|
||||
|
||||
export async function requestLogOut() {
|
||||
const ret = makeAPIGETRequest(
|
||||
'api/auth/logout',
|
||||
);
|
||||
return !ret.errors;
|
||||
}
|
||||
|
||||
export function requestNameChange(name) {
|
||||
return makeAPIPOSTRequest(
|
||||
'api/auth/change_name',
|
||||
|
|
|
@ -604,6 +604,15 @@ export function showUserAreaModal(): Action {
|
|||
);
|
||||
}
|
||||
|
||||
export function changeWindowType(windowId, windowType, args = null) {
|
||||
return {
|
||||
type: 'CHANGE_WINDOW_TYPE',
|
||||
windowId,
|
||||
windowType,
|
||||
args,
|
||||
};
|
||||
}
|
||||
|
||||
export function showRegisterModal(): Action {
|
||||
return showModal(
|
||||
'REGISTER',
|
||||
|
|
|
@ -94,6 +94,11 @@ export type Action =
|
|||
| { type: 'RESTORE_WINDOW' }
|
||||
| { type: 'MOVE_WINDOW', windowId: number, xDiff: number, yDiff: number }
|
||||
| { type: 'RESIZE_WINDOW', windowId: number, xDiff: number, yDiff: number }
|
||||
| { type: 'CHANGE_WINDOW_TYPE',
|
||||
windowId: number,
|
||||
windowType: number,
|
||||
args: Object,
|
||||
}
|
||||
| { type: 'BLOCK_USER', userId: number, userName: string }
|
||||
| { type: 'UNBLOCK_USER', userId: number, userName: string }
|
||||
| { type: 'SET_BLOCKING_DM', blockDm: boolean }
|
||||
|
|
|
@ -195,7 +195,7 @@ function Admintools({
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<p style={{ textAlign: 'center', paddingLeft: '5%', paddingRight: '5%' }}>
|
||||
<div style={{ textAlign: 'center', paddingLeft: '5%', paddingRight: '5%' }}>
|
||||
{resp && (
|
||||
<div style={{
|
||||
borderStyle: 'solid',
|
||||
|
@ -593,7 +593,7 @@ function Admintools({
|
|||
<br />
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import {
|
||||
validateEMail, validatePassword,
|
||||
} from '../utils/validation';
|
||||
|
@ -21,96 +22,78 @@ function validate(email, password) {
|
|||
return errors;
|
||||
}
|
||||
|
||||
class ChangeMail extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
password: '',
|
||||
email: '',
|
||||
submitting: false,
|
||||
success: false,
|
||||
const ChangeMail = ({ done }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [errors, setErrors] = useState([]);
|
||||
|
||||
errors: [],
|
||||
};
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const { email, password, submitting } = this.state;
|
||||
if (submitting) return;
|
||||
|
||||
const errors = validate(email, password);
|
||||
|
||||
this.setState({ errors });
|
||||
if (errors.length > 0) return;
|
||||
this.setState({ submitting: true });
|
||||
|
||||
const { errors: resperrors } = await requestMailChange(email, password);
|
||||
if (resperrors) {
|
||||
this.setState({
|
||||
errors: resperrors,
|
||||
submitting: false,
|
||||
});
|
||||
const handleSubmit = async () => {
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { success } = this.state;
|
||||
const { done } = this.props;
|
||||
if (success) {
|
||||
return (
|
||||
<div className="inarea">
|
||||
<p
|
||||
className="modalmessage"
|
||||
>
|
||||
{t`Changed Mail successfully. We sent you a verification mail, \
|
||||
please verify your new mail address.`}
|
||||
</p>
|
||||
<button type="button" onClick={done}>Close</button>
|
||||
</div>
|
||||
);
|
||||
const valErrors = validate(email, password);
|
||||
if (valErrors.length > 0) {
|
||||
setErrors(valErrors);
|
||||
return;
|
||||
}
|
||||
const {
|
||||
errors, password, email, submitting,
|
||||
} = this.state;
|
||||
|
||||
setSubmitting(true);
|
||||
const { errors: respErrors } = await requestMailChange(email, password);
|
||||
setSubmitting(false);
|
||||
if (respErrors) {
|
||||
setErrors(respErrors);
|
||||
return;
|
||||
}
|
||||
setSuccess(true);
|
||||
};
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="inarea">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage">
|
||||
<span>{t`Error`}</span>:
|
||||
{error}
|
||||
</p>
|
||||
))}
|
||||
<input
|
||||
value={password}
|
||||
onChange={(evt) => this.setState({ password: evt.target.value })}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
value={email}
|
||||
onChange={(evt) => this.setState({ email: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`New Mail`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Save`}
|
||||
</button>
|
||||
<button type="button" onClick={done}>{t`Cancel`}</button>
|
||||
</form>
|
||||
<p
|
||||
className="modalmessage"
|
||||
>
|
||||
{t`Changed Mail successfully. We sent you a verification mail, \
|
||||
please verify your new mail address.`}
|
||||
</p>
|
||||
<button type="button" onClick={done}>Close</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangeMail;
|
||||
return (
|
||||
<div className="inarea">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage">
|
||||
<span>{t`Error`}</span>:
|
||||
{error}
|
||||
</p>
|
||||
))}
|
||||
<input
|
||||
value={password}
|
||||
onChange={(evt) => setPassword(evt.target.value)}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/>
|
||||
<br />
|
||||
<input
|
||||
value={email}
|
||||
onChange={(evt) => setEmail(evt.target.value)}
|
||||
type="text"
|
||||
placeholder={t`New Mail`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Save`}
|
||||
</button>
|
||||
<button type="button" onClick={done}>{t`Cancel`}</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ChangeMail);
|
||||
|
|
|
@ -3,10 +3,13 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { t } from 'ttag';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { validateName } from '../utils/validation';
|
||||
import { requestNameChange } from '../actions/fetch';
|
||||
import { setName } from '../actions';
|
||||
|
||||
|
||||
function validate(name) {
|
||||
|
@ -18,69 +21,56 @@ function validate(name) {
|
|||
return errors;
|
||||
}
|
||||
|
||||
class ChangeName extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
name: '',
|
||||
submitting: false,
|
||||
const ChangeName = ({ done }) => {
|
||||
const [name, setStName] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [errors, setErrors] = useState([]);
|
||||
|
||||
errors: [],
|
||||
};
|
||||
const dispatch = useDispatch();
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const { name, submitting } = this.state;
|
||||
if (submitting) return;
|
||||
|
||||
const errors = validate(name);
|
||||
|
||||
this.setState({ errors });
|
||||
if (errors.length > 0) return;
|
||||
this.setState({ submitting: true });
|
||||
|
||||
const { errors: resperrors } = await requestNameChange(name);
|
||||
if (resperrors) {
|
||||
this.setState({
|
||||
errors: resperrors,
|
||||
submitting: false,
|
||||
});
|
||||
const handleSubmit = async () => {
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
const { setName, done } = this.props;
|
||||
setName(name);
|
||||
|
||||
const valErrors = validate(name);
|
||||
if (valErrors.length > 0) {
|
||||
setErrors(valErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
const { errors: respErrors } = await requestNameChange(name);
|
||||
setSubmitting(false);
|
||||
if (respErrors) {
|
||||
setErrors(respErrors);
|
||||
return;
|
||||
}
|
||||
dispatch(setName(name));
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { errors, name, submitting } = this.state;
|
||||
const { done } = this.props;
|
||||
return (
|
||||
<div className="inarea">
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage">
|
||||
<span>{t`Error`}</span>: {error}</p>
|
||||
))}
|
||||
<input
|
||||
value={name}
|
||||
onChange={(evt) => this.setState({ name: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`New Username`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Save`}
|
||||
</button>
|
||||
<button type="button" onClick={done}>{t`Cancel`}</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="inarea">
|
||||
<form onSubmit={handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage">
|
||||
<span>{t`Error`}</span>: {error}</p>
|
||||
))}
|
||||
<input
|
||||
value={name}
|
||||
onChange={(evt) => setStName(evt.target.value)}
|
||||
type="text"
|
||||
placeholder={t`New Username`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Save`}
|
||||
</button>
|
||||
<button type="button" onClick={done}>{t`Cancel`}</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChangeName;
|
||||
export default React.memo(ChangeName);
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
|
||||
import React, { useState } from 'react';
|
||||
import { t } from 'ttag';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
|
||||
import { setMailreg } from '../actions';
|
||||
import { validatePassword } from '../utils/validation';
|
||||
import { requestPasswordChange } from '../actions/fetch';
|
||||
|
||||
|
@ -25,7 +28,7 @@ function validate(mailreg, password, newPassword, confirmPassword) {
|
|||
return errors;
|
||||
}
|
||||
|
||||
const ChangePassword = ({ mailreg, done, cancel }) => {
|
||||
const ChangePassword = ({ done }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
|
@ -33,6 +36,9 @@ const ChangePassword = ({ mailreg, done, cancel }) => {
|
|||
const [submitting, setSubmitting] = useState(false);
|
||||
const [errors, setErrors] = useState([]);
|
||||
|
||||
const mailreg = useSelector((state) => state.user.mailreg);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div className="inarea">
|
||||
|
@ -66,6 +72,7 @@ const ChangePassword = ({ mailreg, done, cancel }) => {
|
|||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
dispatch(setMailreg(true));
|
||||
setSuccess(true);
|
||||
}}
|
||||
>
|
||||
|
@ -104,7 +111,7 @@ const ChangePassword = ({ mailreg, done, cancel }) => {
|
|||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cancel}
|
||||
onClick={done}
|
||||
>
|
||||
{t`Cancel`}
|
||||
</button>
|
||||
|
|
|
@ -270,8 +270,8 @@ function Converter({
|
|||
);
|
||||
|
||||
return (
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<p className="modalcotext">{t`Choose Canvas`}:
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div className="modalcotext">{t`Choose Canvas`}:
|
||||
<select
|
||||
onChange={(e) => {
|
||||
const sel = e.target;
|
||||
|
@ -296,9 +296,9 @@ function Converter({
|
|||
))
|
||||
}
|
||||
</select>
|
||||
</p>
|
||||
</div>
|
||||
<h3 className="modaltitle">{t`Palette Download`}</h3>
|
||||
<p className="modalcotext">
|
||||
<div className="modalcotext">
|
||||
{jt`Palette for ${gimpLink}`}:
|
||||
<button
|
||||
type="button"
|
||||
|
@ -319,7 +319,7 @@ function Converter({
|
|||
<p>
|
||||
{jt`Credit for the Palette of the Moon goes to ${starhouseLink}.`}
|
||||
</p>
|
||||
</p>
|
||||
</div>
|
||||
<h3 className="modaltitle">{t`Image Converter`}</h3>
|
||||
<p className="modalcotext">{t`Convert an image to canvas colors`}</p>
|
||||
<input
|
||||
|
@ -611,7 +611,7 @@ function Converter({
|
|||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import { validatePassword } from '../utils/validation';
|
||||
|
@ -20,76 +20,55 @@ function validate(password) {
|
|||
return errors;
|
||||
}
|
||||
|
||||
class DeleteAccount extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
password: '',
|
||||
submitting: false,
|
||||
const DeleteAccount = ({ done }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [errors, setErrors] = useState([]);
|
||||
|
||||
errors: [],
|
||||
};
|
||||
const dispatch = useDispatch();
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const { password, submitting } = this.state;
|
||||
if (submitting) return;
|
||||
|
||||
const errors = validate(password);
|
||||
|
||||
this.setState({ errors });
|
||||
if (errors.length > 0) return;
|
||||
this.setState({ submitting: true });
|
||||
|
||||
const { errors: resperrors } = await requestDeleteAccount(password);
|
||||
if (resperrors) {
|
||||
this.setState({
|
||||
errors: resperrors,
|
||||
submitting: false,
|
||||
});
|
||||
const handleSubmit = async () => {
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
const { logout } = this.props;
|
||||
logout();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { errors, password, submitting } = this.state;
|
||||
const { done } = this.props;
|
||||
return (
|
||||
<div className="inarea" style={{ backgroundColor: '#ff6666' }}>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage"><span>{t`Error`}</span>
|
||||
: {error}</p>
|
||||
))}
|
||||
<input
|
||||
value={password}
|
||||
onChange={(evt) => this.setState({ password: evt.target.value })}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Yes, Delete My Account!`}
|
||||
</button>
|
||||
<button type="button" onClick={done}>{t`Cancel`}</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
const valErrors = validate(password);
|
||||
if (valErrors.length > 0) {
|
||||
setErrors(valErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
async logout() {
|
||||
dispatch(logoutUser());
|
||||
},
|
||||
setSubmitting(true);
|
||||
const { errors: respErrors } = await requestDeleteAccount(password);
|
||||
setSubmitting(false);
|
||||
if (respErrors) {
|
||||
setErrors(respErrors);
|
||||
return;
|
||||
}
|
||||
dispatch(logoutUser());
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(DeleteAccount);
|
||||
return (
|
||||
<div className="inarea" style={{ backgroundColor: '#ff6666' }}>
|
||||
<form onSubmit={handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage"><span>{t`Error`}</span>
|
||||
: {error}</p>
|
||||
))}
|
||||
<input
|
||||
value={password}
|
||||
onChange={(evt) => setPassword(evt.target.value)}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Yes, Delete My Account!`}
|
||||
</button>
|
||||
<button type="button" onClick={done}>{t`Cancel`}</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(DeleteAccount);
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* @flow
|
||||
*/
|
||||
import React from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import LogInForm from './LogInForm';
|
||||
import { changeWindowType } from '../actions';
|
||||
|
||||
const logoStyle = {
|
||||
marginRight: 5,
|
||||
};
|
||||
|
||||
const LogInArea = ({ windowId }) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<p className="modaltext">
|
||||
{t`Login to access more features and stats.`}
|
||||
</p><br />
|
||||
<h2>{t`Login with Name or Mail:`}</h2>
|
||||
<LogInForm />
|
||||
<p
|
||||
className="modallink"
|
||||
onClick={() => dispatch(changeWindowType(windowId, 'FORGOT_PASSWORD'))}
|
||||
role="presentation"
|
||||
>
|
||||
{t`I forgot my Password.`}</p>
|
||||
<h2>{t`or login with:`}</h2>
|
||||
<a href="./api/auth/discord">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/discordlogo.svg`}
|
||||
alt="Discord"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/google">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/googlelogo.svg`}
|
||||
alt="Google"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/facebook">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/facebooklogo.svg`}
|
||||
alt="Facebook"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/vk">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/vklogo.svg`}
|
||||
alt="VK"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/reddit">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/redditlogo.svg`}
|
||||
alt="Reddit"
|
||||
/>
|
||||
</a>
|
||||
<h2>{t`or register here:`}</h2>
|
||||
<button
|
||||
type="button"
|
||||
onClick={
|
||||
() => dispatch(changeWindowType(windowId, 'REGISTER'))
|
||||
}
|
||||
>
|
||||
{t`Register`}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(LogInArea);
|
|
@ -2,8 +2,8 @@
|
|||
* LogIn Form
|
||||
* @flow
|
||||
*/
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import {
|
||||
|
@ -31,86 +31,64 @@ const inputStyles = {
|
|||
maxWidth: '35em',
|
||||
};
|
||||
|
||||
class LogInForm extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
nameoremail: '',
|
||||
password: '',
|
||||
submitting: false,
|
||||
const LogInForm = () => {
|
||||
const [nameoremail, setNameOrEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [errors, setErrors] = useState([]);
|
||||
|
||||
errors: [],
|
||||
};
|
||||
const dispatch = useDispatch();
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
const handleSubmit = async () => {
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
const valErrors = validate(nameoremail, password);
|
||||
if (valErrors.length > 0) {
|
||||
setErrors(valErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
const { nameoremail, password, submitting } = this.state;
|
||||
const { login } = this.props;
|
||||
if (submitting) return;
|
||||
|
||||
const errors = validate(nameoremail, password);
|
||||
|
||||
this.setState({ errors });
|
||||
if (errors.length > 0) return;
|
||||
|
||||
this.setState({ submitting: true });
|
||||
const { errors: resperrors, me } = await requestLogin(
|
||||
setSubmitting(true);
|
||||
const { errors: respErrors, me } = await requestLogin(
|
||||
nameoremail,
|
||||
password,
|
||||
);
|
||||
if (resperrors) {
|
||||
this.setState({
|
||||
errors: resperrors,
|
||||
submitting: false,
|
||||
});
|
||||
setSubmitting(false);
|
||||
if (respErrors) {
|
||||
setErrors(respErrors);
|
||||
return;
|
||||
}
|
||||
login(me);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
errors, nameoremail, password, submitting,
|
||||
} = this.state;
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error}><span>{t`Error`}</span>: {error}</p>
|
||||
))}
|
||||
<input
|
||||
value={nameoremail}
|
||||
style={inputStyles}
|
||||
onChange={(evt) => this.setState({ nameoremail: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`Name or Email`}
|
||||
/><br />
|
||||
<input
|
||||
value={password}
|
||||
style={inputStyles}
|
||||
onChange={(evt) => this.setState({ password: evt.target.value })}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/>
|
||||
<p>
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`LogIn`}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
login(me) {
|
||||
dispatch(loginUser(me));
|
||||
},
|
||||
dispatch(loginUser(me));
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(LogInForm);
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error}><span>{t`Error`}</span>: {error}</p>
|
||||
))}
|
||||
<input
|
||||
value={nameoremail}
|
||||
style={inputStyles}
|
||||
onChange={(evt) => setNameOrEmail(evt.target.value)}
|
||||
type="text"
|
||||
placeholder={t`Name or Email`}
|
||||
/><br />
|
||||
<input
|
||||
value={password}
|
||||
style={inputStyles}
|
||||
onChange={(evt) => setPassword(evt.target.value)}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/>
|
||||
<p>
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`LogIn`}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(LogInForm);
|
||||
|
|
|
@ -11,13 +11,6 @@ import HelpButton from './buttons/HelpButton';
|
|||
import SettingsButton from './buttons/SettingsButton';
|
||||
import LogInButton from './buttons/LogInButton';
|
||||
import DownloadButton from './buttons/DownloadButton';
|
||||
/*
|
||||
* removed MinecraftButton cause it didn't get used in over a year
|
||||
* also CSS rule got removed
|
||||
* and MinecraftModal from ModalRoot
|
||||
* and MinecraftTPButton from App
|
||||
* (support for it will be otherwise still kept)
|
||||
*/
|
||||
|
||||
function Menu({
|
||||
menuOpen,
|
||||
|
|
|
@ -44,7 +44,7 @@ const ModalRoot = () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
const Content = COMPONENTS[windowType || 'NONE'];
|
||||
const Content = COMPONENTS[windowType];
|
||||
|
||||
return (
|
||||
(render || open)
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* Form for requesting password-reset mail
|
||||
* @flow
|
||||
*/
|
||||
import React from 'react';
|
||||
import { t } from 'ttag';
|
||||
import { validateEMail } from '../utils/validation';
|
||||
import { requestNewPassword } from '../actions/fetch';
|
||||
|
||||
function validate(email) {
|
||||
const errors = [];
|
||||
const mailerror = validateEMail(email);
|
||||
if (mailerror) errors.push(mailerror);
|
||||
return errors;
|
||||
}
|
||||
|
||||
const inputStyles = {
|
||||
display: 'inline-block',
|
||||
width: '100%',
|
||||
maxWidth: '35em',
|
||||
};
|
||||
|
||||
class NewPasswordForm extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
email: '',
|
||||
submitting: false,
|
||||
success: false,
|
||||
|
||||
errors: [],
|
||||
};
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const { email, submitting } = this.state;
|
||||
if (submitting) return;
|
||||
|
||||
const errors = validate(email);
|
||||
|
||||
this.setState({ errors });
|
||||
if (errors.length > 0) return;
|
||||
|
||||
this.setState({ submitting: true });
|
||||
const { errors: resperrors } = await requestNewPassword(email);
|
||||
if (resperrors) {
|
||||
this.setState({
|
||||
errors: resperrors,
|
||||
submitting: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
success: true,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { success } = this.state;
|
||||
const { back } = this.props;
|
||||
if (success) {
|
||||
return (
|
||||
<div>
|
||||
<p className="modalmessage">
|
||||
{t`Sent you a mail with instructions to reset your password.`}
|
||||
</p>
|
||||
<button type="button" onClick={back}>Back</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const { errors, email, submitting } = this.state;
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error}><span>{t`Error`}</span>: {error}</p>
|
||||
))}
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={email}
|
||||
onChange={(evt) => this.setState({ email: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`Email`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Submit`}
|
||||
</button>
|
||||
<button type="button" onClick={back}>{t`Cancel`}</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default NewPasswordForm;
|
|
@ -5,52 +5,42 @@
|
|||
|
||||
/* eslint-disable max-len */
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import TotalRankings from './TotalRankings';
|
||||
import DailyRankings from './DailyRankings';
|
||||
|
||||
|
||||
class Rankings extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
orderDaily: false,
|
||||
};
|
||||
}
|
||||
const Rankings = () => {
|
||||
const [orderDaily, setOrderDaily] = useState(false);
|
||||
|
||||
render() {
|
||||
const { orderDaily } = this.state;
|
||||
return (
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className={
|
||||
(!orderDaily) ? 'modallinkselected' : 'modallink'
|
||||
}
|
||||
onClick={() => {
|
||||
this.setState({ orderDaily: false });
|
||||
}}
|
||||
>{t`Total`}</span> |
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className={
|
||||
(orderDaily) ? 'modallinkselected' : 'modallink'
|
||||
}
|
||||
onClick={() => { this.setState({ orderDaily: true }); }}
|
||||
>{t`Daily`}</span>
|
||||
</p>
|
||||
{(orderDaily) ? <DailyRankings /> : <TotalRankings />}
|
||||
<p className="modaltext">
|
||||
{t`Ranking updates every 5 min. Daily rankings get reset at midnight UTC.`}
|
||||
</p>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className={
|
||||
(!orderDaily) ? 'modallinkselected' : 'modallink'
|
||||
}
|
||||
onClick={() => setOrderDaily(false)}
|
||||
>{t`Total`}</span> |
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className={
|
||||
(orderDaily) ? 'modallinkselected' : 'modallink'
|
||||
}
|
||||
onClick={() => setOrderDaily(true)}
|
||||
>{t`Daily`}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
{(orderDaily) ? <DailyRankings /> : <TotalRankings />}
|
||||
<p className="modaltext">
|
||||
{t`Ranking updates every 5 min. Daily rankings get reset at midnight UTC.`}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Rankings;
|
||||
export default React.memo(Rankings);
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
* SignUp Form to register new user by mail
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
import {
|
||||
validateEMail, validateName, validatePassword,
|
||||
} from '../utils/validation';
|
||||
import { requestRegistration } from '../actions/fetch';
|
||||
|
||||
import { showUserAreaModal, loginUser } from '../actions';
|
||||
|
||||
|
||||
function validate(name, email, password, confirmPassword) {
|
||||
const errors = [];
|
||||
const mailerror = validateEMail(email);
|
||||
if (mailerror) errors.push(mailerror);
|
||||
const nameerror = validateName(name);
|
||||
if (nameerror) errors.push(nameerror);
|
||||
const passworderror = validatePassword(password);
|
||||
if (passworderror) errors.push(passworderror);
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
errors.push('Passwords do not match');
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
const inputStyles = {
|
||||
display: 'inline-block',
|
||||
width: '100%',
|
||||
maxWidth: '35em',
|
||||
};
|
||||
|
||||
class SignUpForm extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
submitting: false,
|
||||
|
||||
errors: [],
|
||||
};
|
||||
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
async handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const {
|
||||
name, email, password, confirmPassword, submitting,
|
||||
} = this.state;
|
||||
if (submitting) return;
|
||||
|
||||
const errors = validate(name, email, password, confirmPassword);
|
||||
|
||||
this.setState({ errors });
|
||||
if (errors.length > 0) return;
|
||||
|
||||
this.setState({ submitting: true });
|
||||
const { errors: resperrors, me } = await requestRegistration(
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
);
|
||||
if (resperrors) {
|
||||
this.setState({
|
||||
errors: resperrors,
|
||||
submitting: false,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { login, userarea } = this.props;
|
||||
login(me);
|
||||
userarea();
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
errors,
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
confirmPassword,
|
||||
submitting,
|
||||
} = this.state;
|
||||
const {
|
||||
back,
|
||||
} = this.props;
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage"><span>{t`Error`}</span>
|
||||
: {error}</p>
|
||||
))}
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={name}
|
||||
autoComplete="username"
|
||||
onChange={(evt) => this.setState({ name: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`Name`}
|
||||
/><br />
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={email}
|
||||
autoComplete="email"
|
||||
onChange={(evt) => this.setState({ email: evt.target.value })}
|
||||
type="text"
|
||||
placeholder={t`Email`}
|
||||
/><br />
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={password}
|
||||
autoComplete="new-password"
|
||||
onChange={(evt) => this.setState({ password: evt.target.value })}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/><br />
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={confirmPassword}
|
||||
autoComplete="new-password"
|
||||
onChange={(evt) => this.setState({
|
||||
confirmPassword: evt.target.value,
|
||||
})}
|
||||
type="password"
|
||||
placeholder={t`Confirm Password`}
|
||||
/><br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Submit`}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={back}
|
||||
>
|
||||
{t`Cancel`}
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
login(me) {
|
||||
dispatch(loginUser(me));
|
||||
},
|
||||
userarea() {
|
||||
dispatch(showUserAreaModal());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(SignUpForm);
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import {
|
||||
|
@ -13,105 +13,83 @@ import {
|
|||
} from '../actions';
|
||||
import MdToggleButtonHover from './MdToggleButtonHover';
|
||||
|
||||
const SocialSettings = ({
|
||||
blocked,
|
||||
fetching,
|
||||
blockDm,
|
||||
setBlockDm,
|
||||
unblock,
|
||||
done,
|
||||
}) => (
|
||||
<div className="inarea">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
margin: 10,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
const SocialSettings = ({ done }) => {
|
||||
const blocked = useSelector((state) => state.chat.blocked);
|
||||
const blockDm = useSelector((state) => state.user.blockDm);
|
||||
const fetching = useSelector((state) => state.fetching.fetchingApi);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<div className="inarea">
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'nowrap',
|
||||
margin: 10,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
flex: 'auto',
|
||||
textAlign: 'left',
|
||||
}}
|
||||
className="modaltitle"
|
||||
>
|
||||
{t`Block all Private Messages`}
|
||||
</span>
|
||||
<MdToggleButtonHover
|
||||
value={blockDm}
|
||||
onToggle={() => {
|
||||
if (!fetching) {
|
||||
dispatch(setBlockingDm(!blockDm));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="modaldivider" />
|
||||
<p
|
||||
style={{
|
||||
flex: 'auto',
|
||||
textAlign: 'left',
|
||||
marginLeft: 10,
|
||||
}}
|
||||
className="modaltitle"
|
||||
>
|
||||
{t`Block all Private Messages`}
|
||||
</span>
|
||||
<MdToggleButtonHover
|
||||
value={blockDm}
|
||||
onToggle={() => {
|
||||
if (!fetching) {
|
||||
setBlockDm(!blockDm);
|
||||
>{t`Unblock Users`}</p>
|
||||
{
|
||||
(blocked.length) ? (
|
||||
<span
|
||||
className="unblocklist"
|
||||
>
|
||||
{
|
||||
blocked.map((bl) => (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
if (!fetching) {
|
||||
dispatch(setUserBlock(bl[0], bl[1], false));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{`⦸ ${bl[1]}`}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="modaldivider" />
|
||||
<p
|
||||
style={{
|
||||
textAlign: 'left',
|
||||
marginLeft: 10,
|
||||
}}
|
||||
className="modaltitle"
|
||||
>{t`Unblock Users`}</p>
|
||||
{
|
||||
(blocked.length) ? (
|
||||
<span
|
||||
className="unblocklist"
|
||||
>
|
||||
{
|
||||
blocked.map((bl) => (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
if (!fetching) {
|
||||
unblock(bl[0], bl[1]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{`⦸ ${bl[1]}`}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</span>
|
||||
)
|
||||
: (
|
||||
<p className="modaltext">{t`You have no users blocked`}</p>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
<div className="modaldivider" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={done}
|
||||
style={{ margin: 10 }}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
: (
|
||||
<p className="modaltext">{t`You have no users blocked`}</p>
|
||||
)
|
||||
}
|
||||
<div className="modaldivider" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={done}
|
||||
style={{ margin: 10 }}
|
||||
>
|
||||
Done
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const { blocked } = state.chat;
|
||||
const { blockDm } = state.user;
|
||||
const { fetchingApi: fetching } = state.fetching;
|
||||
return {
|
||||
blocked,
|
||||
blockDm,
|
||||
fetching,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
setBlockDm(block) {
|
||||
dispatch(setBlockingDm(block));
|
||||
},
|
||||
unblock(userId, userName) {
|
||||
dispatch(setUserBlock(userId, userName, false));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SocialSettings);
|
||||
export default React.memo(SocialSettings);
|
||||
|
|
|
@ -1,227 +0,0 @@
|
|||
/*
|
||||
* Menu to change user credentials
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import type { State } from '../reducers';
|
||||
|
||||
import UserMessages from './UserMessages';
|
||||
import ChangePassword from './ChangePassword';
|
||||
import ChangeName from './ChangeName';
|
||||
import ChangeMail from './ChangeMail';
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
import SocialSettings from './SocialSettings';
|
||||
import { logoutUser } from '../actions';
|
||||
|
||||
import { numberToString } from '../core/utils';
|
||||
|
||||
const Stat = ({ text, value, rank }) => (
|
||||
<p>
|
||||
<span className="stattext">{(rank) ? `${text}: #` : `${text}: `}</span>
|
||||
|
||||
<span className="statvalue">{numberToString(value)}</span>
|
||||
</p>
|
||||
);
|
||||
|
||||
class UserArea extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
// that should be an ENUM tbh
|
||||
changeNameExtended: false,
|
||||
changeMailExtended: false,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: false,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
stats, name, logout, mailreg, setMailreg, setName,
|
||||
} = this.props;
|
||||
const {
|
||||
changeNameExtended,
|
||||
changeMailExtended,
|
||||
changePasswdExtended,
|
||||
deleteAccountExtended,
|
||||
socialSettingsExtended,
|
||||
} = this.state;
|
||||
return (
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<UserMessages />
|
||||
<Stat
|
||||
text={t`Todays Placed Pixels`}
|
||||
value={stats.dailyTotalPixels}
|
||||
/>
|
||||
<Stat
|
||||
text={t`Daily Rank`}
|
||||
value={stats.dailyRanking}
|
||||
rank
|
||||
/>
|
||||
<Stat
|
||||
text={t`Placed Pixels`}
|
||||
value={stats.totalPixels}
|
||||
/>
|
||||
<Stat
|
||||
text={t`Total Rank`}
|
||||
value={stats.ranking}
|
||||
rank
|
||||
/>
|
||||
<p className="modaltext">
|
||||
<p>{t`Your name is: ${name}`}</p>(
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={logout}
|
||||
> {t`Log out`}</span> |
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => this.setState({
|
||||
changeNameExtended: true,
|
||||
changeMailExtended: false,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> {t`Change Username`}</span> |
|
||||
{(mailreg)
|
||||
&& (
|
||||
<span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => this.setState({
|
||||
changeNameExtended: false,
|
||||
changeMailExtended: true,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> {t`Change Mail`}</span> |
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => this.setState({
|
||||
changeNameExtended: false,
|
||||
changeMailExtended: false,
|
||||
changePasswdExtended: true,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> {t`Change Password`}</span> |
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => this.setState({
|
||||
changeNameExtended: false,
|
||||
changeMailExtended: false,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: true,
|
||||
socialSettingsExtended: false,
|
||||
})}
|
||||
> {t`Delete Account`}</span> )
|
||||
<br />(
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => this.setState({
|
||||
changeNameExtended: false,
|
||||
changeMailExtended: false,
|
||||
changePasswdExtended: false,
|
||||
deleteAccountExtended: false,
|
||||
socialSettingsExtended: true,
|
||||
})}
|
||||
> {t`Social Settings`}</span> )
|
||||
</p>
|
||||
<p className="modaltext" />
|
||||
{(changePasswdExtended)
|
||||
&& (
|
||||
<ChangePassword
|
||||
mailreg={mailreg}
|
||||
done={() => {
|
||||
setMailreg(true);
|
||||
this.setState({ changePasswdExtended: false });
|
||||
}}
|
||||
cancel={() => { this.setState({ changePasswdExtended: false }); }}
|
||||
/>
|
||||
)}
|
||||
{(changeNameExtended)
|
||||
&& (
|
||||
<ChangeName
|
||||
setName={setName}
|
||||
done={() => { this.setState({ changeNameExtended: false }); }}
|
||||
/>
|
||||
)}
|
||||
{(changeMailExtended)
|
||||
&& (
|
||||
<ChangeMail
|
||||
done={() => { this.setState({ changeMailExtended: false }); }}
|
||||
/>
|
||||
)}
|
||||
{(deleteAccountExtended)
|
||||
&& (
|
||||
<DeleteAccount
|
||||
done={() => { this.setState({ deleteAccountExtended: false }); }}
|
||||
/>
|
||||
)}
|
||||
{(socialSettingsExtended)
|
||||
&& (
|
||||
<SocialSettings
|
||||
done={() => { this.setState({ socialSettingsExtended: false }); }}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const {
|
||||
name,
|
||||
mailreg,
|
||||
} = state.user;
|
||||
const {
|
||||
totalPixels,
|
||||
dailyTotalPixels,
|
||||
ranking,
|
||||
dailyRanking,
|
||||
} = state.ranks;
|
||||
const stats = {
|
||||
totalPixels,
|
||||
dailyTotalPixels,
|
||||
ranking,
|
||||
dailyRanking,
|
||||
};
|
||||
|
||||
return { name, mailreg, stats };
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
async logout() {
|
||||
const response = await fetch(
|
||||
'./api/auth/logout',
|
||||
{ credentials: 'include' },
|
||||
);
|
||||
if (response.ok) {
|
||||
dispatch(logoutUser());
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserArea);
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* Menu to change user credentials
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import UserMessages from './UserMessages';
|
||||
import ChangePassword from './ChangePassword';
|
||||
import ChangeName from './ChangeName';
|
||||
import ChangeMail from './ChangeMail';
|
||||
import DeleteAccount from './DeleteAccount';
|
||||
import SocialSettings from './SocialSettings';
|
||||
import { logoutUser } from '../actions';
|
||||
import { requestLogOut } from '../actions/fetch';
|
||||
|
||||
import { numberToString } from '../core/utils';
|
||||
|
||||
const AREAS = {
|
||||
CHANGE_NAME: ChangeName,
|
||||
CHANGE_MAIL: ChangeMail,
|
||||
CHANGE_PASSWORD: ChangePassword,
|
||||
DELETE_ACCOUNT: DeleteAccount,
|
||||
SOCIAL_SETTINGS: SocialSettings,
|
||||
};
|
||||
|
||||
const Stat = ({ text, value, rank }) => (
|
||||
<p>
|
||||
<span className="stattext">{(rank) ? `${text}: #` : `${text}: `}</span>
|
||||
|
||||
<span className="statvalue">{numberToString(value)}</span>
|
||||
</p>
|
||||
);
|
||||
|
||||
const UserAreaContent = () => {
|
||||
const [area, setArea] = useState('NONE');
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const logout = useCallback(async () => {
|
||||
const ret = await requestLogOut();
|
||||
if (ret) {
|
||||
dispatch(logoutUser());
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
const mailreg = useSelector((state) => state.user.mailreg);
|
||||
const name = useSelector((state) => state.user.name);
|
||||
const stats = useSelector((state) => ({
|
||||
totalPixels: state.ranks.totalPixels,
|
||||
dailyTotalPixels: state.ranks.dailyTotalPixels,
|
||||
ranking: state.ranks.ranking,
|
||||
dailyRanking: state.ranks.dailyRanking,
|
||||
}), shallowEqual);
|
||||
|
||||
const Area = AREAS[area];
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<UserMessages />
|
||||
<Stat
|
||||
text={t`Todays Placed Pixels`}
|
||||
value={stats.dailyTotalPixels}
|
||||
/>
|
||||
<Stat
|
||||
text={t`Daily Rank`}
|
||||
value={stats.dailyRanking}
|
||||
rank
|
||||
/>
|
||||
<Stat
|
||||
text={t`Placed Pixels`}
|
||||
value={stats.totalPixels}
|
||||
/>
|
||||
<Stat
|
||||
text={t`Total Rank`}
|
||||
value={stats.ranking}
|
||||
rank
|
||||
/>
|
||||
<div className="modaltext">
|
||||
<p>{t`Your name is: ${name}`}</p>(
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={logout}
|
||||
> {t`Log out`}</span> |
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => setArea('CHANGE_NAME')}
|
||||
> {t`Change Username`}</span> |
|
||||
{(mailreg)
|
||||
&& (
|
||||
<span>
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => setArea('CHANGE_MAIL')}
|
||||
> {t`Change Mail`}</span> |
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => setArea('CHANGE_PASSWORD')}
|
||||
> {t`Change Password`}</span> |
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => setArea('DELETE_ACCOUNT')}
|
||||
> {t`Delete Account`}</span> )
|
||||
<br />(
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
className="modallink"
|
||||
onClick={() => setArea('SOCIAL_SETTINGS')}
|
||||
> {t`Social Settings`}</span> )
|
||||
</div>
|
||||
{(Area) && <Area done={() => setArea(null)} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserAreaContent);
|
|
@ -4,16 +4,13 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { MdFileDownload } from 'react-icons/md';
|
||||
import fileDownload from 'js-file-download';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import { getRenderer } from '../../ui/renderer';
|
||||
|
||||
import type { State } from '../../reducers';
|
||||
|
||||
|
||||
/**
|
||||
* https://jsfiddle.net/AbdiasSoftware/7PRNN/
|
||||
*/
|
||||
|
@ -29,23 +26,21 @@ function download(view) {
|
|||
}
|
||||
|
||||
|
||||
const DownloadButton = ({ view }) => (
|
||||
<div
|
||||
id="downloadbutton"
|
||||
className="actionbuttons"
|
||||
role="button"
|
||||
title={t`Make Screenshot`}
|
||||
tabIndex={0}
|
||||
onClick={() => download(view)}
|
||||
>
|
||||
<MdFileDownload />
|
||||
</div>
|
||||
);
|
||||
const DownloadButton = () => {
|
||||
const view = useSelector((state) => state.canvas.view);
|
||||
|
||||
// TODO optimize
|
||||
function mapStateToProps(state: State) {
|
||||
const { view } = state.canvas;
|
||||
return { view };
|
||||
}
|
||||
return (
|
||||
<div
|
||||
id="downloadbutton"
|
||||
className="actionbuttons"
|
||||
role="button"
|
||||
title={t`Make Screenshot`}
|
||||
tabIndex={0}
|
||||
onClick={() => download(view)}
|
||||
>
|
||||
<MdFileDownload />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(DownloadButton);
|
||||
export default React.memo(DownloadButton);
|
||||
|
|
|
@ -4,12 +4,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useSelector, shallowEqual } from 'react-redux';
|
||||
import { Md3DRotation } from 'react-icons/md';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import type { State } from '../../reducers';
|
||||
|
||||
|
||||
/**
|
||||
* https://jsfiddle.net/AbdiasSoftware/7PRNN/
|
||||
|
@ -21,28 +19,26 @@ function globe(canvasId, canvasIdent, canvasSize, view) {
|
|||
}
|
||||
|
||||
|
||||
const GlobeButton = ({
|
||||
canvasId, canvasIdent, canvasSize, view,
|
||||
}) => (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
id="globebutton"
|
||||
title={t`Globe View`}
|
||||
className="actionbuttons"
|
||||
onClick={() => globe(canvasId, canvasIdent, canvasSize, view)}
|
||||
>
|
||||
<Md3DRotation />
|
||||
</div>
|
||||
);
|
||||
const GlobeButton = () => {
|
||||
const [canvasId, canvasIdent, canvasSize, view] = useSelector((state) => [
|
||||
state.canvas.canvasId,
|
||||
state.canvas.canvasIdent,
|
||||
state.canvas.canvasSize,
|
||||
state.canvas.view,
|
||||
], shallowEqual);
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const {
|
||||
canvasId, canvasIdent, canvasSize, view,
|
||||
} = state.canvas;
|
||||
return {
|
||||
canvasId, canvasIdent, canvasSize, view,
|
||||
};
|
||||
}
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
id="globebutton"
|
||||
title={t`Globe View`}
|
||||
className="actionbuttons"
|
||||
onClick={() => globe(canvasId, canvasIdent, canvasSize, view)}
|
||||
>
|
||||
<Md3DRotation />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(GlobeButton);
|
||||
export default React.memo(GlobeButton);
|
||||
|
|
|
@ -5,44 +5,36 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useSelector, useDispatch, shallowEqual } from 'react-redux';
|
||||
import { MdPalette } from 'react-icons/md';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import { toggleOpenPalette } from '../../actions';
|
||||
|
||||
const PalselButton = ({
|
||||
palette, onToggle, selectedColor, paletteOpen,
|
||||
}) => (
|
||||
<div
|
||||
id="palselbutton"
|
||||
className={`actionbuttons ${(paletteOpen) ? '' : 'pressed'}`}
|
||||
style={{
|
||||
color: palette.isDark(selectedColor) ? 'white' : 'black',
|
||||
backgroundColor: palette.colors[selectedColor],
|
||||
}}
|
||||
role="button"
|
||||
title={(paletteOpen) ? t`Close Palette` : t`Open Palette`}
|
||||
tabIndex={0}
|
||||
onClick={onToggle}
|
||||
>
|
||||
<MdPalette />
|
||||
</div>
|
||||
);
|
||||
const PalselButton = () => {
|
||||
const paletteOpen = useSelector((state) => state.gui.paletteOpen);
|
||||
const [palette, selectedColor] = useSelector((state) => [
|
||||
state.canvas.palette,
|
||||
state.canvas.selectedColor,
|
||||
], shallowEqual);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
// TODO simplify...
|
||||
function mapStateToProps(state: State) {
|
||||
const { paletteOpen } = state.gui;
|
||||
const { palette, selectedColor } = state.canvas;
|
||||
return { palette, selectedColor, paletteOpen };
|
||||
}
|
||||
return (
|
||||
<div
|
||||
id="palselbutton"
|
||||
className={`actionbuttons ${(paletteOpen) ? '' : 'pressed'}`}
|
||||
style={{
|
||||
color: palette.isDark(selectedColor) ? 'white' : 'black',
|
||||
backgroundColor: palette.colors[selectedColor],
|
||||
}}
|
||||
role="button"
|
||||
title={(paletteOpen) ? t`Close Palette` : t`Open Palette`}
|
||||
tabIndex={0}
|
||||
onClick={() => dispatch(toggleOpenPalette())}
|
||||
>
|
||||
<MdPalette />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
onToggle() {
|
||||
dispatch(toggleOpenPalette());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PalselButton);
|
||||
export default React.memo(PalselButton);
|
||||
|
|
|
@ -3,49 +3,31 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useRef, useEffect,
|
||||
} from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useRef, useCallback } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import {
|
||||
useClickOutside,
|
||||
} from '../hooks/clickOutside';
|
||||
import {
|
||||
hideContextMenu,
|
||||
setLeaveChannel,
|
||||
muteChatChannel,
|
||||
unmuteChatChannel,
|
||||
} from '../../actions';
|
||||
import type { State } from '../../reducers';
|
||||
|
||||
const ChannelContextMenu = ({
|
||||
xPos,
|
||||
yPos,
|
||||
cid,
|
||||
channels,
|
||||
leave,
|
||||
muteArr,
|
||||
mute,
|
||||
unmute,
|
||||
close,
|
||||
}) => {
|
||||
const ChannelContextMenu = () => {
|
||||
const wrapperRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
||||
event.stopPropagation();
|
||||
close();
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
};
|
||||
}, [wrapperRef]);
|
||||
const channels = useSelector((state) => state.chat.channels);
|
||||
const muteArr = useSelector((state) => state.chatRead.mute);
|
||||
const { xPos, yPos, args } = useSelector((state) => state.contextMenu);
|
||||
const { cid } = args;
|
||||
const dispatch = useDispatch();
|
||||
const close = useCallback(() => dispatch(hideContextMenu()), [dispatch]);
|
||||
|
||||
useClickOutside([wrapperRef], close);
|
||||
|
||||
const isMuted = muteArr.includes(cid);
|
||||
|
||||
|
@ -62,9 +44,9 @@ const ChannelContextMenu = ({
|
|||
role="button"
|
||||
onClick={() => {
|
||||
if (isMuted) {
|
||||
unmute(cid);
|
||||
dispatch(unmuteChatChannel(cid));
|
||||
} else {
|
||||
mute(cid);
|
||||
dispatch(muteChatChannel(cid));
|
||||
}
|
||||
}}
|
||||
tabIndex={0}
|
||||
|
@ -77,7 +59,7 @@ const ChannelContextMenu = ({
|
|||
<div
|
||||
role="button"
|
||||
onClick={() => {
|
||||
leave(cid);
|
||||
dispatch(setLeaveChannel(cid));
|
||||
close();
|
||||
}}
|
||||
tabIndex={0}
|
||||
|
@ -89,43 +71,4 @@ const ChannelContextMenu = ({
|
|||
);
|
||||
};
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const {
|
||||
xPos,
|
||||
yPos,
|
||||
args,
|
||||
} = state.contextMenu;
|
||||
const {
|
||||
channels,
|
||||
} = state.chat;
|
||||
const {
|
||||
cid,
|
||||
} = args;
|
||||
const { mute: muteArr } = state.chatRead;
|
||||
return {
|
||||
xPos,
|
||||
yPos,
|
||||
cid,
|
||||
channels,
|
||||
muteArr,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
close() {
|
||||
dispatch(hideContextMenu());
|
||||
},
|
||||
leave(cid) {
|
||||
dispatch(setLeaveChannel(cid));
|
||||
},
|
||||
mute(cid) {
|
||||
dispatch(muteChatChannel(cid));
|
||||
},
|
||||
unmute(cid) {
|
||||
dispatch(unmuteChatChannel(cid));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ChannelContextMenu);
|
||||
export default React.memo(ChannelContextMenu);
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
*/
|
||||
|
||||
import React, {
|
||||
useRef, useState, useEffect, useCallback, useLayoutEffect,
|
||||
useRef, useState, useEffect, useCallback,
|
||||
} from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { MdChat } from 'react-icons/md';
|
||||
import { FaUserFriends } from 'react-icons/fa';
|
||||
|
||||
import { useConditionalClickOutside } from '../hooks/clickOutside';
|
||||
|
||||
const ChannelDropDown = ({
|
||||
setChatChannel,
|
||||
chatChannel,
|
||||
|
@ -37,35 +39,18 @@ const ChannelDropDown = ({
|
|||
setOffset(buttonRef.current.clientHeight);
|
||||
}, [buttonRef]);
|
||||
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
if (wrapperRef.current
|
||||
&& !wrapperRef.current.contains(event.target)
|
||||
&& !buttonRef.current.contains(event.target)
|
||||
) {
|
||||
event.stopPropagation();
|
||||
setShow(false);
|
||||
}
|
||||
}, []);
|
||||
useConditionalClickOutside(
|
||||
[wrapperRef],
|
||||
show,
|
||||
useCallback(() => setShow(false), []),
|
||||
);
|
||||
|
||||
const handleWindowResize = useCallback(() => {
|
||||
setShow(false);
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
if (channels[chatChannel]) {
|
||||
const chType = (channels[chatChannel][1] === 1) ? 1 : 0;
|
||||
setType(chType);
|
||||
}
|
||||
document.addEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
} else {
|
||||
document.removeEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
}
|
||||
}, [show]);
|
||||
|
||||
|
@ -133,7 +118,7 @@ const ChannelDropDown = ({
|
|||
ref={buttonRef}
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
onClick={() => setShow(!show)}
|
||||
onClick={() => setShow(true)}
|
||||
className={`channelbtn${(show) ? ' selected' : ''}`}
|
||||
>
|
||||
{(unreadAny && chatNotify && !show) && (
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useRef, useEffect,
|
||||
} from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import {
|
||||
useClickOutside,
|
||||
} from '../hooks/clickOutside';
|
||||
import {
|
||||
hideContextMenu,
|
||||
addToChatInputMessage,
|
||||
|
@ -29,22 +30,7 @@ const UserContextMenu = () => {
|
|||
const dispatch = useDispatch();
|
||||
const close = () => dispatch(hideContextMenu());
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
|
||||
event.stopPropagation();
|
||||
close();
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
};
|
||||
}, [wrapperRef]);
|
||||
useClickOutside([wrapperRef], close);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* @flex
|
||||
*
|
||||
* detect click outside
|
||||
*/
|
||||
|
||||
import { useEffect, useLayoutEffect, useCallback } from 'react';
|
||||
|
||||
/*
|
||||
* Keeps listening to outside clicks or window resize
|
||||
* as long as active is true
|
||||
* @param insideRefs references to elements that are considered inside
|
||||
* @param calback function that gets fired on click outside
|
||||
* @param active boolean if we should listen or not
|
||||
*/
|
||||
export function useConditionalClickOutside(insideRefs, active, callback) {
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
if (insideRefs.every((ref) => !ref.current
|
||||
|| !ref.current.contains(event.target))) {
|
||||
event.stopPropagation();
|
||||
callback();
|
||||
}
|
||||
}, [callback]);
|
||||
|
||||
const handleWindowResize = useCallback(() => {
|
||||
callback();
|
||||
}, [callback]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (active) {
|
||||
document.addEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
} else {
|
||||
document.removeEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
}
|
||||
}, [active, callback]);
|
||||
}
|
||||
|
||||
/*
|
||||
* listen to click outside or window resize
|
||||
* @param insideRefs references to elements that are considered inside
|
||||
* @param callback function that gets fired on click outside
|
||||
*/
|
||||
export function useClickOutside(insideRefs, callback) {
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (insideRefs.every((ref) => !ref.current
|
||||
|| !ref.current.contains(event.target))) {
|
||||
event.stopPropagation();
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
const handleWindowResize = () => {
|
||||
callback();
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
return () => {
|
||||
document.removeEventListener('click', handleClickOutside, {
|
||||
capture: true,
|
||||
});
|
||||
window.removeEventListener('resize', handleWindowResize);
|
||||
};
|
||||
}, [callback]);
|
||||
}
|
|
@ -4,62 +4,48 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import CanvasItem from '../CanvasItem';
|
||||
import { showArchiveModal } from '../../actions';
|
||||
|
||||
import type { State } from '../../reducers';
|
||||
import { changeWindowType } from '../../actions';
|
||||
|
||||
|
||||
const CanvasSelect = ({
|
||||
canvases,
|
||||
showHiddenCanvases,
|
||||
showArchive,
|
||||
}) => (
|
||||
<p style={{
|
||||
textAlign: 'center',
|
||||
paddingLeft: '5%',
|
||||
paddingRight: '5%',
|
||||
paddingTop: 20,
|
||||
}}
|
||||
>
|
||||
<p className="modaltext">
|
||||
{t`Select the canvas you want to use. \
|
||||
Every canvas is unique and has different palettes, cooldown and requirements. \
|
||||
Archive of closed canvases can be accessed here:`}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="modallink"
|
||||
onClick={showArchive}
|
||||
>{t`Archive`}</span>)
|
||||
const CanvasSelect = ({ windowId }) => {
|
||||
const [canvases, showHiddenCanvases] = useSelector((state) => [
|
||||
state.canvas.canvases,
|
||||
state.canvas.showHiddenCanvases,
|
||||
], shallowEqual);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
return (
|
||||
<p style={{
|
||||
textAlign: 'center',
|
||||
paddingLeft: '5%',
|
||||
paddingRight: '5%',
|
||||
paddingTop: 20,
|
||||
}}
|
||||
>
|
||||
<p className="modaltext">
|
||||
{t`Select the canvas you want to use. \
|
||||
Every canvas is unique and has different palettes, cooldown and requirements. \
|
||||
Archive of closed canvases can be accessed here:`}
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="modallink"
|
||||
onClick={() => dispatch(changeWindowType(windowId, 'ARCHIVE'))}
|
||||
>{t`Archive`}</span>)
|
||||
</p>
|
||||
{
|
||||
Object.keys(canvases).map((canvasId) => (
|
||||
(canvases[canvasId].hid && !showHiddenCanvases)
|
||||
? null
|
||||
: <CanvasItem canvasId={canvasId} canvas={canvases[canvasId]} />
|
||||
))
|
||||
}
|
||||
</p>
|
||||
{
|
||||
Object.keys(canvases).map((canvasId) => (
|
||||
(canvases[canvasId].hid && !showHiddenCanvases)
|
||||
? null
|
||||
: <CanvasItem canvasId={canvasId} canvas={canvases[canvasId]} />
|
||||
))
|
||||
}
|
||||
</p>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
showArchive() {
|
||||
dispatch(showArchiveModal());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const {
|
||||
canvases,
|
||||
showHiddenCanvases,
|
||||
} = state.canvas;
|
||||
return { canvases, showHiddenCanvases };
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CanvasSelect);
|
||||
export default React.memo(CanvasSelect);
|
||||
|
|
|
@ -173,7 +173,7 @@ const Chat = ({
|
|||
}
|
||||
</ul>
|
||||
{(ownName) ? (
|
||||
<div classNam="chatinput">
|
||||
<div className="chatinput">
|
||||
<form
|
||||
onSubmit={(e) => handleSubmit(e)}
|
||||
style={{ display: 'flex', flexDirection: 'row' }}
|
||||
|
|
|
@ -1,35 +1,94 @@
|
|||
/**
|
||||
*
|
||||
/*
|
||||
* Form for requesting password-reset mail
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import { showUserAreaModal } from '../../actions';
|
||||
import NewPasswordForm from '../NewPasswordForm';
|
||||
import { changeWindowType } from '../../actions';
|
||||
import { validateEMail } from '../../utils/validation';
|
||||
import { requestNewPassword } from '../../actions/fetch';
|
||||
|
||||
const ForgotPassword = ({ login }) => (
|
||||
<p style={{ paddingLeft: '5%', paddingRight: '5%' }}>
|
||||
<p className="modaltext">
|
||||
{t`Enter your mail address and we will send you a new password:`}
|
||||
</p><br />
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<NewPasswordForm back={login} />
|
||||
<p>{t`Consider joining us on Guilded:`}
|
||||
<a href="./guilded" target="_blank">pixelplanet.fun/guilded</a>
|
||||
</p>
|
||||
</p>
|
||||
</p>
|
||||
);
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
login() {
|
||||
dispatch(showUserAreaModal());
|
||||
},
|
||||
};
|
||||
function validate(email) {
|
||||
const errors = [];
|
||||
const mailerror = validateEMail(email);
|
||||
if (mailerror) errors.push(mailerror);
|
||||
return errors;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ForgotPassword);
|
||||
const inputStyles = {
|
||||
display: 'inline-block',
|
||||
width: '100%',
|
||||
maxWidth: '35em',
|
||||
};
|
||||
|
||||
const ForgotPassword = ({ windowId }) => {
|
||||
const [email, setEmail] = useState('');
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [errors, setErrors] = useState([]);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const back = () => dispatch(changeWindowType(windowId, 'USERAREA'));
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valErrors = validate(email);
|
||||
if (valErrors.length > 0) {
|
||||
setErrors(valErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
const { errors: respErrors } = await requestNewPassword(email);
|
||||
setSubmitting(false);
|
||||
if (respErrors) {
|
||||
setErrors(respErrors);
|
||||
return;
|
||||
}
|
||||
setSuccess(true);
|
||||
};
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
<div>
|
||||
<p className="modalmessage">
|
||||
{t`Sent you a mail with instructions to reset your password.`}
|
||||
</p>
|
||||
<button type="button" onClick={back}>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div style={{ paddingLeft: '5%', paddingRight: '5%', textAlign: 'center' }}>
|
||||
<p className="modaltext">
|
||||
{t`Enter your mail address and we will send you a new password:`}
|
||||
</p><br />
|
||||
<form onSubmit={handleSubmit}>
|
||||
{errors.map((error) => (
|
||||
<p key={error}><span>{t`Error`}</span>: {error}</p>
|
||||
))}
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={email}
|
||||
onChange={(evt) => setEmail(evt.target.value)}
|
||||
type="text"
|
||||
placeholder={t`Email`}
|
||||
/>
|
||||
<br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Submit`}
|
||||
</button>
|
||||
<button type="button" onClick={back}>{t`Cancel`}</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ForgotPassword);
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
const MinecraftModal = () => (
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<p>You can also place pixels from our Minecraft Server at</p>
|
||||
<p><input type="text" value="mc.pixelplanet.fun" readOnly /></p>
|
||||
<p>Please Note that the Minecraft Server is down from time to time</p>
|
||||
</p>
|
||||
);
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const { center } = state.user;
|
||||
return { center };
|
||||
}
|
||||
|
||||
const data = {
|
||||
content: connect(mapStateToProps)(MinecraftModal),
|
||||
title: 'PixelPlanet Minecraft Server',
|
||||
};
|
||||
|
||||
export default data;
|
|
@ -1,36 +1,133 @@
|
|||
/**
|
||||
*
|
||||
/*
|
||||
* SignUp Form to register new user by mail
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import React, { useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
import {
|
||||
validateEMail, validateName, validatePassword,
|
||||
} from '../../utils/validation';
|
||||
import { requestRegistration } from '../../actions/fetch';
|
||||
|
||||
import { showUserAreaModal } from '../../actions';
|
||||
|
||||
// import { send_registration } from '../ui/register';
|
||||
import SignUpForm from '../SignUpForm';
|
||||
import { changeWindowType, loginUser } from '../../actions';
|
||||
|
||||
|
||||
const Register = ({ login }) => (
|
||||
<p style={{ paddingLeft: '5%', paddingRight: '5%' }}>
|
||||
<p className="modaltext">{t`Register new account here`}</p><br />
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<SignUpForm back={login} />
|
||||
<p>{t`Consider joining us on Guilded:`}
|
||||
<a href="./guilded" target="_blank">pixelplanet.fun/guilded</a>
|
||||
</p>
|
||||
</p>
|
||||
</p>
|
||||
);
|
||||
function validate(name, email, password, confirmPassword) {
|
||||
const errors = [];
|
||||
const mailerror = validateEMail(email);
|
||||
if (mailerror) errors.push(mailerror);
|
||||
const nameerror = validateName(name);
|
||||
if (nameerror) errors.push(nameerror);
|
||||
const passworderror = validatePassword(password);
|
||||
if (passworderror) errors.push(passworderror);
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
login() {
|
||||
dispatch(showUserAreaModal());
|
||||
},
|
||||
};
|
||||
if (password !== confirmPassword) {
|
||||
errors.push('Passwords do not match');
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(Register);
|
||||
const inputStyles = {
|
||||
display: 'inline-block',
|
||||
width: '100%',
|
||||
maxWidth: '35em',
|
||||
};
|
||||
|
||||
const Register = ({ windowId }) => {
|
||||
const [name, setName] = useState('');
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [confirmPassword, setConfirmPassword] = useState('');
|
||||
const [submitting, setSubmitting] = useState('');
|
||||
const [errors, setErrors] = useState([]);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const valErrors = validate(name, email, password, confirmPassword);
|
||||
if (valErrors.length > 0) {
|
||||
setErrors(valErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
setSubmitting(true);
|
||||
const { errors: respErrors, me } = await requestRegistration(
|
||||
name,
|
||||
email,
|
||||
password,
|
||||
);
|
||||
setSubmitting(false);
|
||||
if (respErrors) {
|
||||
setErrors(respErrors);
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(loginUser(me));
|
||||
dispatch(changeWindowType(windowId, 'USERAREA'));
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<form
|
||||
style={{ paddingLeft: '5%', paddingRight: '5%' }}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<p className="modaltext">{t`Register new account here`}</p><br />
|
||||
{errors.map((error) => (
|
||||
<p key={error} className="errormessage"><span>{t`Error`}</span>
|
||||
: {error}</p>
|
||||
))}
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={name}
|
||||
autoComplete="username"
|
||||
onChange={(evt) => setName(evt.target.value)}
|
||||
type="text"
|
||||
placeholder={t`Name`}
|
||||
/><br />
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={email}
|
||||
autoComplete="email"
|
||||
onChange={(evt) => setEmail(evt.target.value)}
|
||||
type="text"
|
||||
placeholder={t`Email`}
|
||||
/><br />
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={password}
|
||||
autoComplete="new-password"
|
||||
onChange={(evt) => setPassword(evt.target.value)}
|
||||
type="password"
|
||||
placeholder={t`Password`}
|
||||
/><br />
|
||||
<input
|
||||
style={inputStyles}
|
||||
value={confirmPassword}
|
||||
autoComplete="new-password"
|
||||
onChange={(evt) => setConfirmPassword(evt.target.value)}
|
||||
type="password"
|
||||
placeholder={t`Confirm Password`}
|
||||
/><br />
|
||||
<button type="submit">
|
||||
{(submitting) ? '...' : t`Submit`}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => dispatch(changeWindowType(windowId, 'USERAREA'))}
|
||||
>
|
||||
{t`Cancel`}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
export default React.memo(Register);
|
||||
|
|
|
@ -120,7 +120,7 @@ function Settings({
|
|||
chatNotify,
|
||||
}) {
|
||||
return (
|
||||
<p style={{ paddingLeft: '5%', paddingRight: '5%', paddingTop: 30 }}>
|
||||
<div style={{ paddingLeft: '5%', paddingRight: '5%', paddingTop: 30 }}>
|
||||
<SettingsItem
|
||||
title={t`Show Grid`}
|
||||
description={t`Turn on grid to highlight pixel borders.`}
|
||||
|
@ -203,7 +203,7 @@ function Settings({
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,18 +4,12 @@
|
|||
*/
|
||||
|
||||
import React, { Suspense } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import type { State } from '../../reducers';
|
||||
|
||||
|
||||
import {
|
||||
showRegisterModal, showForgotPasswordModal, setName, setMailreg,
|
||||
} from '../../actions';
|
||||
import LogInForm from '../LogInForm';
|
||||
import LogInArea from '../LogInArea';
|
||||
import Tabs from '../Tabs';
|
||||
import UserAreaContent from '../UserArea';
|
||||
import UserAreaContent from '../UserAreaContent';
|
||||
import Rankings from '../Rankings';
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
|
@ -23,136 +17,38 @@ const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ '.
|
|||
// eslint-disable-next-line max-len
|
||||
const Admintools = React.lazy(() => import(/* webpackChunkName: "admintools" */ '../Admintools'));
|
||||
|
||||
const logoStyle = {
|
||||
marginRight: 5,
|
||||
const UserArea = ({ windowId }) => {
|
||||
const name = useSelector((state) => state.user.name);
|
||||
const userlvl = useSelector((state) => state.user.userlvl);
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<Tabs>
|
||||
<div label={t`Profile`}>
|
||||
{(name) ? <UserAreaContent /> : <LogInArea windowId={windowId} />}
|
||||
</div>
|
||||
<div label={t`Ranking`}>
|
||||
<Rankings />
|
||||
</div>
|
||||
<div label={t`Converter`}>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Converter />
|
||||
</Suspense>
|
||||
</div>
|
||||
{userlvl && (
|
||||
<div label={(userlvl === 1) ? t`Admintools` : t`Modtools`}>
|
||||
<Suspense fallback={<div>{t`Loading...`}</div>}>
|
||||
<Admintools />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
</Tabs>
|
||||
<br />
|
||||
{t`Consider joining us on Guilded:`}
|
||||
<a href="./guilded" target="_blank">pixelplanet.fun/guilded</a>
|
||||
<br />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const LogInArea = ({ register, forgotPassword, me }) => (
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
<p className="modaltext">
|
||||
{t`Login to access more features and stats.`}
|
||||
</p><br />
|
||||
<h2>{t`Login with Name or Mail:`}</h2>
|
||||
<LogInForm me={me} />
|
||||
<p
|
||||
className="modallink"
|
||||
onClick={forgotPassword}
|
||||
role="presentation"
|
||||
>
|
||||
{t`I forgot my Password.`}</p>
|
||||
<h2>{t`or login with:`}</h2>
|
||||
<a href="./api/auth/discord">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/discordlogo.svg`}
|
||||
alt="Discord"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/google">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/googlelogo.svg`}
|
||||
alt="Google"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/facebook">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/facebooklogo.svg`}
|
||||
alt="Facebook"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/vk">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/vklogo.svg`}
|
||||
alt="VK"
|
||||
/>
|
||||
</a>
|
||||
<a href="./api/auth/reddit">
|
||||
<img
|
||||
style={logoStyle}
|
||||
width={32}
|
||||
src={`${window.ssv.assetserver}/redditlogo.svg`}
|
||||
alt="Reddit"
|
||||
/>
|
||||
</a>
|
||||
<h2>{t`or register here:`}</h2>
|
||||
<button type="button" onClick={register}>{t`Register`}</button>
|
||||
</p>
|
||||
);
|
||||
|
||||
const UserArea = ({
|
||||
name,
|
||||
register,
|
||||
forgotPassword,
|
||||
setUserName,
|
||||
setUserMailreg,
|
||||
userlvl,
|
||||
}) => (
|
||||
<p style={{ textAlign: 'center' }}>
|
||||
{(name === null)
|
||||
? (
|
||||
<LogInArea
|
||||
register={register}
|
||||
forgotPassword={forgotPassword}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Tabs>
|
||||
<div label={t`Profile`}>
|
||||
<UserAreaContent
|
||||
setName={setUserName}
|
||||
setMailreg={setUserMailreg}
|
||||
/>
|
||||
</div>
|
||||
<div label={t`Ranking`}>
|
||||
<Rankings />
|
||||
</div>
|
||||
<div label={t`Converter`}>
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Converter />
|
||||
</Suspense>
|
||||
</div>
|
||||
{userlvl && (
|
||||
<div label={(userlvl === 1) ? t`Admintools` : t`Modtools`}>
|
||||
<Suspense fallback={<div>{t`Loading...`}</div>}>
|
||||
<Admintools />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
</Tabs>
|
||||
)}
|
||||
<p>{t`Consider joining us on Guilded:`}
|
||||
<a href="./guilded" target="_blank">pixelplanet.fun/guilded</a>
|
||||
</p>
|
||||
</p>
|
||||
);
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
register() {
|
||||
dispatch(showRegisterModal());
|
||||
},
|
||||
forgotPassword() {
|
||||
dispatch(showForgotPasswordModal());
|
||||
},
|
||||
setUserName(name) {
|
||||
dispatch(setName(name));
|
||||
},
|
||||
setUserMailreg(mailreg) {
|
||||
dispatch(setMailreg(mailreg));
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mapStateToProps(state: State) {
|
||||
const { name, userlvl } = state.user;
|
||||
return { name, userlvl };
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserArea);
|
||||
export default React.memo(UserArea);
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
/*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Help from './Help';
|
||||
import Settings from './Settings';
|
||||
import UserArea from './UserArea';
|
||||
|
@ -14,7 +11,7 @@ import Chat from './Chat';
|
|||
import ForgotPassword from './ForgotPassword';
|
||||
|
||||
export default {
|
||||
NONE: <div />,
|
||||
NONE: null,
|
||||
HELP: Help,
|
||||
SETTINGS: Settings,
|
||||
USERAREA: UserArea,
|
||||
|
@ -23,5 +20,5 @@ export default {
|
|||
CHAT: Chat,
|
||||
CANVAS_SELECTION: CanvasSelect,
|
||||
ARCHIVE: Archive,
|
||||
/* other modals */
|
||||
/* other windows */
|
||||
};
|
||||
|
|
|
@ -327,6 +327,43 @@ export default function windows(
|
|||
};
|
||||
}
|
||||
|
||||
case 'CHANGE_WINDOW_TYPE': {
|
||||
const {
|
||||
windowId,
|
||||
windowType,
|
||||
} = action;
|
||||
const args = {
|
||||
...state.args,
|
||||
[windowId]: {
|
||||
...action.args,
|
||||
},
|
||||
};
|
||||
if (windowId === 0) {
|
||||
return {
|
||||
...state,
|
||||
args,
|
||||
modal: {
|
||||
...state.modal,
|
||||
windowType,
|
||||
},
|
||||
};
|
||||
}
|
||||
const newWindows = state.windows.map((win) => {
|
||||
if (win.windowId !== windowId) {
|
||||
return win;
|
||||
}
|
||||
return {
|
||||
...win,
|
||||
windowType,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...state,
|
||||
args,
|
||||
windows: newWindows,
|
||||
};
|
||||
}
|
||||
|
||||
case 'FOCUS_WINDOW': {
|
||||
const {
|
||||
windowId,
|
||||
|
|
|
@ -239,7 +239,7 @@ tr:nth-child(even) {
|
|||
border-width: thin;
|
||||
border-radius: 4px;
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
height: 22px;
|
||||
white-space: nowrap;
|
||||
font-size: 14px;
|
||||
overflow-x: hidden;
|
||||
|
|
Loading…
Reference in New Issue