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:
HF 2021-05-01 00:51:08 +02:00
parent a60242617d
commit e80e5737ea
36 changed files with 1071 additions and 1410 deletions

View File

@ -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',

View File

@ -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',

View File

@ -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 }

View File

@ -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>
);
}

View File

@ -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>:&nbsp;
{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>:&nbsp;
{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);

View File

@ -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>:&nbsp;{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>:&nbsp;{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);

View File

@ -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>

View File

@ -270,8 +270,8 @@ function Converter({
);
return (
<p style={{ textAlign: 'center' }}>
<p className="modalcotext">{t`Choose Canvas`}:&nbsp;
<div style={{ textAlign: 'center' }}>
<div className="modalcotext">{t`Choose Canvas`}:&nbsp;
<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}`}:&nbsp;
<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>
);
}

View File

@ -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>
:&nbsp;{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>
:&nbsp;{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);

View File

@ -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);

View File

@ -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>:&nbsp;{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>:&nbsp;{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);

View File

@ -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,

View File

@ -44,7 +44,7 @@ const ModalRoot = () => {
return null;
}
const Content = COMPONENTS[windowType || 'NONE'];
const Content = COMPONENTS[windowType];
return (
(render || open)

View File

@ -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>:&nbsp;{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;

View File

@ -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);

View File

@ -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>
:&nbsp;{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);

View File

@ -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);

View File

@ -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>
&nbsp;
<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);

View File

@ -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>
&nbsp;
<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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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) && (

View File

@ -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

View File

@ -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]);
}

View File

@ -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:`}&nbsp;
<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:`}&nbsp;
<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);

View File

@ -173,7 +173,7 @@ const Chat = ({
}
</ul>
{(ownName) ? (
<div classNam="chatinput">
<div className="chatinput">
<form
onSubmit={(e) => handleSubmit(e)}
style={{ display: 'flex', flexDirection: 'row' }}

View File

@ -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:`}&nbsp;
<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>:&nbsp;{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);

View File

@ -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;

View File

@ -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:`}&nbsp;
<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>
:&nbsp;{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);

View File

@ -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>
);
}

View File

@ -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:`}&nbsp;
<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:`}&nbsp;
<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);

View File

@ -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 */
};

View File

@ -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,

View File

@ -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;