Merge branch 'feature/mobile-examples' of https://github.com/ghalestrilo/p5.js-web-editor into feature/mobile-save-sketch
This commit is contained in:
commit
dabd5e0678
15 changed files with 344 additions and 181 deletions
|
@ -143,9 +143,9 @@ class CollectionList extends React.Component {
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{this._renderFieldHeader('name', 'Name')}
|
{this._renderFieldHeader('name', 'Name')}
|
||||||
{(!mobile) && this._renderFieldHeader('createdAt', 'Date Created')}
|
{this._renderFieldHeader('createdAt', `${mobile ? '' : 'Date '}Created`)}
|
||||||
{this._renderFieldHeader('updatedAt', 'Date Updated')}
|
{this._renderFieldHeader('updatedAt', `${mobile ? '' : 'Date '}Updated`)}
|
||||||
{this._renderFieldHeader('numItems', '# sketches')}
|
{this._renderFieldHeader('numItems', mobile ? 'Sketches' : '# sketches')}
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
|
@ -213,7 +213,7 @@ class CollectionListRowBase extends React.Component {
|
||||||
{this.renderCollectionName()}
|
{this.renderCollectionName()}
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
{(!mobile) && <td>{format(new Date(collection.createdAt), 'MMM D, YYYY')}</td>}
|
<td>{mobile && 'Created: '}{format(new Date(collection.createdAt), 'MMM D, YYYY')}</td>
|
||||||
<td>{mobile && 'Updated: '}{formatDateCell(collection.updatedAt)}</td>
|
<td>{mobile && 'Updated: '}{formatDateCell(collection.updatedAt)}</td>
|
||||||
<td>{mobile && '# sketches: '}{(collection.items || []).length}</td>
|
<td>{mobile && '# sketches: '}{(collection.items || []).length}</td>
|
||||||
<td className="sketch-list__dropdown-column">
|
<td className="sketch-list__dropdown-column">
|
||||||
|
|
|
@ -437,8 +437,8 @@ class SketchList extends React.Component {
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
{this._renderFieldHeader('name', 'Sketch')}
|
{this._renderFieldHeader('name', 'Sketch')}
|
||||||
{this._renderFieldHeader('createdAt', 'Date Created')}
|
{this._renderFieldHeader('createdAt', `${mobile ? '' : 'Date '}Created`)}
|
||||||
{this._renderFieldHeader('updatedAt', 'Date Updated')}
|
{this._renderFieldHeader('updatedAt', `${mobile ? '' : 'Date '}Updated`)}
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
|
@ -24,9 +24,11 @@ import Loader from '../App/components/loader';
|
||||||
|
|
||||||
const EXAMPLE_USERNAME = 'p5';
|
const EXAMPLE_USERNAME = 'p5';
|
||||||
|
|
||||||
|
// @ghalestrilo 08/13/2020: I'm sorry
|
||||||
const ContentWrapper = styled(Content)`
|
const ContentWrapper = styled(Content)`
|
||||||
table {
|
table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
|
margin-bottom: ${remSize(120)}
|
||||||
}
|
}
|
||||||
|
|
||||||
td ,thead button {
|
td ,thead button {
|
||||||
|
@ -55,14 +57,18 @@ const ContentWrapper = styled(Content)`
|
||||||
|
|
||||||
tbody td { justify-self: start; text-align: start; padding: 0 }
|
tbody td { justify-self: start; text-align: start; padding: 0 }
|
||||||
tbody td:nth-child(2) { justify-self: start; text-align: start; padding-left: ${remSize(12)}};
|
tbody td:nth-child(2) { justify-self: start; text-align: start; padding-left: ${remSize(12)}};
|
||||||
tbody td:last-child { justify-self: end; text-align: end; };
|
tbody td:last-child {
|
||||||
|
justify-self: end;
|
||||||
|
text-align: end;
|
||||||
|
grid-row-start: 1;
|
||||||
|
grid-column-start: 3;
|
||||||
|
};
|
||||||
|
|
||||||
.sketch-list__dropdown-column { width: auto; };
|
.sketch-list__dropdown-column { width: auto; };
|
||||||
|
|
||||||
tbody { height: ${remSize(48)}; }
|
tbody { height: ${remSize(48)}; }
|
||||||
|
|
||||||
.sketches-table-container {
|
.sketches-table-container {
|
||||||
padding-bottom: ${remSize(160)};
|
|
||||||
background: ${prop('SketchList.background')};
|
background: ${prop('SketchList.background')};
|
||||||
}
|
}
|
||||||
.sketches-table__row {
|
.sketches-table__row {
|
||||||
|
@ -79,18 +85,33 @@ const ContentWrapper = styled(Content)`
|
||||||
};
|
};
|
||||||
|
|
||||||
thead tr {
|
thead tr {
|
||||||
grid-template-columns: 1fr 1fr 1fr 0fr;
|
grid-template-columns: repeat(${props => props.fieldcount}, 1fr) 0fr;
|
||||||
|
${props => props.noheader && 'display: none;'}
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody tr {
|
tbody tr {
|
||||||
padding: ${remSize(8)};
|
padding: ${remSize(8)};
|
||||||
border-radius: ${remSize(4)};
|
border-radius: ${remSize(4)};
|
||||||
grid-template-columns: 5fr 5fr 1fr;
|
grid-template-columns: repeat(${props => props.fieldcount - 1}) 1fr;
|
||||||
grid-template-areas: "name name name" "content content content";
|
grid-template-areas: "name name name" "content content content";
|
||||||
|
grid-row-gap: ${remSize(12)}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loader-container { position: fixed ; padding-bottom: 32% }
|
.loader-container { position: fixed ; padding-bottom: 32% }
|
||||||
|
|
||||||
|
.sketches-table thead th {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-table thead th {
|
||||||
|
height: initial;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.asset-table thead tr {
|
||||||
|
height: ${remSize(32)}
|
||||||
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Subheader = styled.div`
|
const Subheader = styled.div`
|
||||||
|
@ -168,7 +189,7 @@ const MobileDashboard = ({ params, location }) => {
|
||||||
</Header>
|
</Header>
|
||||||
|
|
||||||
|
|
||||||
<ContentWrapper slimheader>
|
<ContentWrapper slimheader fieldcount={panel === Tabs[1] ? 4 : 3} noheader={panel === Tabs[2]}>
|
||||||
<Subheader>
|
<Subheader>
|
||||||
{panel === Tabs[0] && <SketchSearchbar />}
|
{panel === Tabs[0] && <SketchSearchbar />}
|
||||||
{panel === Tabs[1] && <CollectionSearchbar />}
|
{panel === Tabs[1] && <CollectionSearchbar />}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
import { PlusIcon } from '../../../common/icons';
|
import { PlusIcon } from '../../../common/icons';
|
||||||
import CopyableInput from '../../IDE/components/CopyableInput';
|
import CopyableInput from '../../IDE/components/CopyableInput';
|
||||||
|
@ -12,7 +11,7 @@ export const APIKeyPropType = PropTypes.shape({
|
||||||
token: PropTypes.object, // eslint-disable-line
|
token: PropTypes.object, // eslint-disable-line
|
||||||
label: PropTypes.string.isRequired,
|
label: PropTypes.string.isRequired,
|
||||||
createdAt: PropTypes.string.isRequired,
|
createdAt: PropTypes.string.isRequired,
|
||||||
lastUsedAt: PropTypes.string,
|
lastUsedAt: PropTypes.string
|
||||||
});
|
});
|
||||||
|
|
||||||
class APIKeyForm extends React.Component {
|
class APIKeyForm extends React.Component {
|
||||||
|
@ -39,7 +38,7 @@ class APIKeyForm extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeKey(key) {
|
removeKey(key) {
|
||||||
const message = `Are you sure you want to delete "${key.label}"?`;
|
const message = this.props.t('APIKeyForm.ConfirmDelete', { key_label: key.label });
|
||||||
|
|
||||||
if (window.confirm(message)) {
|
if (window.confirm(message)) {
|
||||||
this.props.removeApiKey(key.id);
|
this.props.removeApiKey(key.id);
|
||||||
|
@ -51,10 +50,10 @@ class APIKeyForm extends React.Component {
|
||||||
|
|
||||||
if (hasApiKeys) {
|
if (hasApiKeys) {
|
||||||
return (
|
return (
|
||||||
<APIKeyList apiKeys={this.props.apiKeys} onRemove={this.removeKey} />
|
<APIKeyList apiKeys={this.props.apiKeys} onRemove={this.removeKey} t={this.props.t} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return <p>You have no exsiting tokens.</p>;
|
return <p>{this.props.t('APIKeyForm.NoTokens')}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -63,27 +62,18 @@ class APIKeyForm extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className="api-key-form">
|
<div className="api-key-form">
|
||||||
<p className="api-key-form__summary">
|
<p className="api-key-form__summary">
|
||||||
Personal Access Tokens act like your password to allow automated
|
{this.props.t('APIKeyForm.Summary')}
|
||||||
scripts to access the Editor API. Create a token for each script that
|
|
||||||
needs access.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="api-key-form__section">
|
<div className="api-key-form__section">
|
||||||
<h3 className="api-key-form__title">Create new token</h3>
|
<h3 className="api-key-form__title">{this.props.t('APIKeyForm.CreateToken')}</h3>
|
||||||
<form className="form form--inline" onSubmit={this.addKey}>
|
<form className="form form--inline" onSubmit={this.addKey}>
|
||||||
<label
|
<label htmlFor="keyLabel" className="form__label form__label--hidden ">{this.props.t('APIKeyForm.TokenLabel')}</label>
|
||||||
htmlFor="keyLabel"
|
|
||||||
className="form__label form__label--hidden "
|
|
||||||
>
|
|
||||||
What is this token for?
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
id="keyLabel"
|
id="keyLabel"
|
||||||
onChange={(event) => {
|
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }}
|
||||||
this.setState({ keyLabel: event.target.value });
|
placeholder={this.props.t('APIKeyForm.TokenPlaceholder')}
|
||||||
}}
|
|
||||||
placeholder="What is this token for? e.g. Example import script"
|
|
||||||
type="text"
|
type="text"
|
||||||
value={this.state.keyLabel}
|
value={this.state.keyLabel}
|
||||||
/>
|
/>
|
||||||
|
@ -93,29 +83,25 @@ class APIKeyForm extends React.Component {
|
||||||
label="Create new key"
|
label="Create new key"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
Create
|
{this.props.t('APIKeyForm.CreateTokenSubmit')}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{keyWithToken && (
|
{
|
||||||
|
keyWithToken && (
|
||||||
<div className="api-key-form__new-token">
|
<div className="api-key-form__new-token">
|
||||||
<h4 className="api-key-form__new-token__title">
|
<h4 className="api-key-form__new-token__title">{this.props.t('APIKeyForm.NewTokenTitle')}</h4>
|
||||||
Your new access token
|
|
||||||
</h4>
|
|
||||||
<p className="api-key-form__new-token__info">
|
<p className="api-key-form__new-token__info">
|
||||||
Make sure to copy your new personal access token now. You won’t
|
{this.props.t('APIKeyForm.NewTokenInfo')}
|
||||||
be able to see it again!
|
|
||||||
</p>
|
</p>
|
||||||
<CopyableInput
|
<CopyableInput label={keyWithToken.label} value={keyWithToken.token} />
|
||||||
label={keyWithToken.label}
|
|
||||||
value={keyWithToken.token}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="api-key-form__section">
|
<div className="api-key-form__section">
|
||||||
<h3 className="api-key-form__title">Existing tokens</h3>
|
<h3 className="api-key-form__title">{this.props.t('APIKeyForm.ExistingTokensTitle')}</h3>
|
||||||
{this.renderApiKeys()}
|
{this.renderApiKeys()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -127,6 +113,7 @@ APIKeyForm.propTypes = {
|
||||||
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
|
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
|
||||||
createApiKey: PropTypes.func.isRequired,
|
createApiKey: PropTypes.func.isRequired,
|
||||||
removeApiKey: PropTypes.func.isRequired,
|
removeApiKey: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default APIKeyForm;
|
export default APIKeyForm;
|
||||||
|
|
|
@ -8,22 +8,22 @@ import { APIKeyPropType } from './APIKeyForm';
|
||||||
|
|
||||||
import TrashCanIcon from '../../../images/trash-can.svg';
|
import TrashCanIcon from '../../../images/trash-can.svg';
|
||||||
|
|
||||||
function APIKeyList({ apiKeys, onRemove }) {
|
function APIKeyList({ apiKeys, onRemove, t }) {
|
||||||
return (
|
return (
|
||||||
<table className="api-key-list">
|
<table className="api-key-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>{t('APIKeyList.Name')}</th>
|
||||||
<th>Created on</th>
|
<th>{t('APIKeyList.Created')}</th>
|
||||||
<th>Last used</th>
|
<th>{t('APIKeyList.LastUsed')}</th>
|
||||||
<th>Actions</th>
|
<th>{t('APIKeyList.Actions')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{orderBy(apiKeys, ['createdAt'], ['desc']).map((key) => {
|
{orderBy(apiKeys, ['createdAt'], ['desc']).map((key) => {
|
||||||
const lastUsed = key.lastUsedAt ?
|
const lastUsed = key.lastUsedAt ?
|
||||||
distanceInWordsToNow(new Date(key.lastUsedAt), { addSuffix: true }) :
|
distanceInWordsToNow(new Date(key.lastUsedAt), { addSuffix: true }) :
|
||||||
'Never';
|
t('APIKeyList.Never');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={key.id}>
|
<tr key={key.id}>
|
||||||
|
@ -34,7 +34,7 @@ function APIKeyList({ apiKeys, onRemove }) {
|
||||||
<button
|
<button
|
||||||
className="api-key-list__delete-button"
|
className="api-key-list__delete-button"
|
||||||
onClick={() => onRemove(key)}
|
onClick={() => onRemove(key)}
|
||||||
aria-label="Delete API Key"
|
aria-label={t('APIKeyList.DeleteARIA')}
|
||||||
>
|
>
|
||||||
<TrashCanIcon focusable="false" aria-hidden="true" />
|
<TrashCanIcon focusable="false" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -50,6 +50,7 @@ function APIKeyList({ apiKeys, onRemove }) {
|
||||||
APIKeyList.propTypes = {
|
APIKeyList.propTypes = {
|
||||||
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
|
apiKeys: PropTypes.arrayOf(PropTypes.shape(APIKeyPropType)).isRequired,
|
||||||
onRemove: PropTypes.func.isRequired,
|
onRemove: PropTypes.func.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default APIKeyList;
|
export default APIKeyList;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ function AccountForm(props) {
|
||||||
submitting,
|
submitting,
|
||||||
invalid,
|
invalid,
|
||||||
pristine,
|
pristine,
|
||||||
|
t
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const handleInitiateVerification = (evt) => {
|
const handleInitiateVerification = (evt) => {
|
||||||
|
@ -24,12 +26,10 @@ function AccountForm(props) {
|
||||||
return (
|
return (
|
||||||
<form className="form" onSubmit={handleSubmit(props.updateSettings)}>
|
<form className="form" onSubmit={handleSubmit(props.updateSettings)}>
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="email" className="form__label">
|
<label htmlFor="email" className="form__label">{t('AccountForm.Email')}</label>
|
||||||
Email
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
aria-label="email"
|
aria-label={t('AccountForm.EmailARIA')}
|
||||||
type="text"
|
type="text"
|
||||||
id="email"
|
id="email"
|
||||||
{...domOnlyProps(email)}
|
{...domOnlyProps(email)}
|
||||||
|
@ -38,28 +38,31 @@ function AccountForm(props) {
|
||||||
<span className="form-error">{email.error}</span>
|
<span className="form-error">{email.error}</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
{user.verified !== 'verified' && (
|
{
|
||||||
|
user.verified !== 'verified' &&
|
||||||
|
(
|
||||||
<p className="form__context">
|
<p className="form__context">
|
||||||
<span className="form__status">Unconfirmed.</span>
|
<span className="form__status">{t('AccountForm.Unconfirmed')}</span>
|
||||||
{user.emailVerificationInitiate === true ? (
|
{
|
||||||
<span className="form__status">
|
user.emailVerificationInitiate === true ?
|
||||||
{' '}
|
(
|
||||||
Confirmation sent, check your email.
|
<span className="form__status"> {t('AccountForm.EmailSent')}</span>
|
||||||
</span>
|
) :
|
||||||
) : (
|
(
|
||||||
<Button onClick={handleInitiateVerification}>
|
<Button
|
||||||
Resend confirmation email
|
onClick={handleInitiateVerification}
|
||||||
|
>{t('AccountForm.Resend')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="username" className="form__label">
|
<label htmlFor="username" className="form__label">{t('AccountForm.UserName')}</label>
|
||||||
User Name
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
aria-label="username"
|
aria-label={t('AccountForm.UserNameARIA')}
|
||||||
type="text"
|
type="text"
|
||||||
id="username"
|
id="username"
|
||||||
defaultValue={username}
|
defaultValue={username}
|
||||||
|
@ -70,12 +73,10 @@ function AccountForm(props) {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="current password" className="form__label">
|
<label htmlFor="current password" className="form__label">{t('AccountForm.CurrentPassword')}</label>
|
||||||
Current Password
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
aria-label="currentPassword"
|
aria-label={t('AccountForm.CurrentPasswordARIA')}
|
||||||
type="password"
|
type="password"
|
||||||
id="currentPassword"
|
id="currentPassword"
|
||||||
{...domOnlyProps(currentPassword)}
|
{...domOnlyProps(currentPassword)}
|
||||||
|
@ -85,12 +86,10 @@ function AccountForm(props) {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="new password" className="form__label">
|
<label htmlFor="new password" className="form__label">{t('AccountForm.NewPassword')}</label>
|
||||||
New Password
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
aria-label="newPassword"
|
aria-label={t('AccountForm.NewPasswordARIA')}
|
||||||
type="password"
|
type="password"
|
||||||
id="newPassword"
|
id="newPassword"
|
||||||
{...domOnlyProps(newPassword)}
|
{...domOnlyProps(newPassword)}
|
||||||
|
@ -99,8 +98,10 @@ function AccountForm(props) {
|
||||||
<span className="form-error">{newPassword.error}</span>
|
<span className="form-error">{newPassword.error}</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<Button type="submit" disabled={submitting || invalid || pristine}>
|
<Button
|
||||||
Save All Settings
|
type="submit"
|
||||||
|
disabled={submitting || invalid || pristine}
|
||||||
|
>{t('AccountForm.SubmitSaveAllSettings')}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
@ -123,6 +124,7 @@ AccountForm.propTypes = {
|
||||||
submitting: PropTypes.bool,
|
submitting: PropTypes.bool,
|
||||||
invalid: PropTypes.bool,
|
invalid: PropTypes.bool,
|
||||||
pristine: PropTypes.bool,
|
pristine: PropTypes.bool,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
AccountForm.defaultProps = {
|
AccountForm.defaultProps = {
|
||||||
|
@ -131,4 +133,4 @@ AccountForm.defaultProps = {
|
||||||
invalid: false,
|
invalid: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AccountForm;
|
export default withTranslation()(AccountForm);
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
function NewPasswordForm(props) {
|
function NewPasswordForm(props) {
|
||||||
const {
|
const {
|
||||||
fields: { password, confirmPassword },
|
fields: { password, confirmPassword }, handleSubmit, submitting, invalid, pristine,
|
||||||
handleSubmit,
|
t
|
||||||
submitting,
|
|
||||||
invalid,
|
|
||||||
pristine,
|
|
||||||
} = props;
|
} = props;
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
|
@ -18,12 +15,10 @@ function NewPasswordForm(props) {
|
||||||
onSubmit={handleSubmit(props.updatePassword.bind(this, props.params.reset_password_token))}
|
onSubmit={handleSubmit(props.updatePassword.bind(this, props.params.reset_password_token))}
|
||||||
>
|
>
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="password" className="form__label">
|
<label htmlFor="password" className="form__label">{t('NewPasswordForm.Title')}</label>
|
||||||
Password
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
aria-label="password"
|
aria-label={t('NewPasswordForm.TitleARIA')}
|
||||||
type="password"
|
type="password"
|
||||||
id="Password"
|
id="Password"
|
||||||
{...domOnlyProps(password)}
|
{...domOnlyProps(password)}
|
||||||
|
@ -33,13 +28,11 @@ function NewPasswordForm(props) {
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="confirm password" className="form__label">
|
<label htmlFor="confirm password" className="form__label">{t('NewPasswordForm.ConfirmPassword')}</label>
|
||||||
Confirm Password
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
type="password"
|
type="password"
|
||||||
aria-label="confirm password"
|
aria-label={t('NewPasswordForm.ConfirmPasswordARIA')}
|
||||||
id="confirm password"
|
id="confirm password"
|
||||||
{...domOnlyProps(confirmPassword)}
|
{...domOnlyProps(confirmPassword)}
|
||||||
/>
|
/>
|
||||||
|
@ -47,9 +40,7 @@ function NewPasswordForm(props) {
|
||||||
<span className="form-error">{confirmPassword.error}</span>
|
<span className="form-error">{confirmPassword.error}</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<Button type="submit" disabled={submitting || invalid || pristine}>
|
<Button type="submit" disabled={submitting || invalid || pristine}>{t('NewPasswordForm.SubmitSetNewPassword')}</Button>
|
||||||
Set New Password
|
|
||||||
</Button>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -67,6 +58,7 @@ NewPasswordForm.propTypes = {
|
||||||
params: PropTypes.shape({
|
params: PropTypes.shape({
|
||||||
reset_password_token: PropTypes.string,
|
reset_password_token: PropTypes.string,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
NewPasswordForm.defaultProps = {
|
NewPasswordForm.defaultProps = {
|
||||||
|
@ -75,4 +67,4 @@ NewPasswordForm.defaultProps = {
|
||||||
submitting: false,
|
submitting: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NewPasswordForm;
|
export default withTranslation()(NewPasswordForm);
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
import { domOnlyProps } from '../../../utils/reduxFormUtils';
|
||||||
import Button from '../../../common/Button';
|
import Button from '../../../common/Button';
|
||||||
|
|
||||||
function ResetPasswordForm(props) {
|
function ResetPasswordForm(props) {
|
||||||
const {
|
const {
|
||||||
fields: { email },
|
fields: { email }, handleSubmit, submitting, invalid, pristine, t
|
||||||
handleSubmit,
|
|
||||||
submitting,
|
|
||||||
invalid,
|
|
||||||
pristine,
|
|
||||||
} = props;
|
} = props;
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
|
@ -18,12 +14,10 @@ function ResetPasswordForm(props) {
|
||||||
onSubmit={handleSubmit(props.initiateResetPassword.bind(this))}
|
onSubmit={handleSubmit(props.initiateResetPassword.bind(this))}
|
||||||
>
|
>
|
||||||
<p className="form__field">
|
<p className="form__field">
|
||||||
<label htmlFor="email" className="form__label">
|
<label htmlFor="email" className="form__label">{t('ResetPasswordForm.Email')}</label>
|
||||||
Email used for registration
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form__input"
|
className="form__input"
|
||||||
aria-label="email"
|
aria-label={t('ResetPasswordForm.EmailARIA')}
|
||||||
type="text"
|
type="text"
|
||||||
id="email"
|
id="email"
|
||||||
{...domOnlyProps(email)}
|
{...domOnlyProps(email)}
|
||||||
|
@ -34,11 +28,8 @@ function ResetPasswordForm(props) {
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={
|
disabled={submitting || invalid || pristine || props.user.resetPasswordInitiate}
|
||||||
submitting || invalid || pristine || props.user.resetPasswordInitiate
|
>{t('ResetPasswordForm.Submit')}
|
||||||
}
|
|
||||||
>
|
|
||||||
Send Password Reset Email
|
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
|
@ -54,8 +45,9 @@ ResetPasswordForm.propTypes = {
|
||||||
invalid: PropTypes.bool,
|
invalid: PropTypes.bool,
|
||||||
pristine: PropTypes.bool,
|
pristine: PropTypes.bool,
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
resetPasswordInitiate: PropTypes.bool,
|
resetPasswordInitiate: PropTypes.bool
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
ResetPasswordForm.defaultProps = {
|
ResetPasswordForm.defaultProps = {
|
||||||
|
@ -64,4 +56,4 @@ ResetPasswordForm.defaultProps = {
|
||||||
invalid: false,
|
invalid: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ResetPasswordForm;
|
export default withTranslation()(ResetPasswordForm);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { reduxForm } from 'redux-form';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
import { updateSettings, initiateVerification, createApiKey, removeApiKey } from '../actions';
|
||||||
import AccountForm from '../components/AccountForm';
|
import AccountForm from '../components/AccountForm';
|
||||||
import apiClient from '../../../utils/apiClient';
|
import apiClient from '../../../utils/apiClient';
|
||||||
|
@ -16,9 +17,11 @@ function SocialLoginPanel(props) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<AccountForm {...props} />
|
<AccountForm {...props} />
|
||||||
<h2 className="form-container__divider">Social Login</h2>
|
{/* eslint-disable-next-line react/prop-types */}
|
||||||
|
<h2 className="form-container__divider">{props.t('AccountView.SocialLogin')}</h2>
|
||||||
<p className="account__social-text">
|
<p className="account__social-text">
|
||||||
Use your GitHub or Google account to log into the p5.js Web Editor.
|
{/* eslint-disable-next-line react/prop-types */}
|
||||||
|
{props.t('AccountView.SocialLoginDescription')}
|
||||||
</p>
|
</p>
|
||||||
<div className="account__social-stack">
|
<div className="account__social-stack">
|
||||||
<SocialAuthButton service={SocialAuthButton.services.github} />
|
<SocialAuthButton service={SocialAuthButton.services.github} />
|
||||||
|
@ -39,21 +42,21 @@ class AccountView extends React.Component {
|
||||||
return (
|
return (
|
||||||
<div className="account-settings__container">
|
<div className="account-settings__container">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>p5.js Web Editor | Account Settings</title>
|
<title>{this.props.t('AccountView.Title')}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
|
|
||||||
<Nav layout="dashboard" />
|
<Nav layout="dashboard" />
|
||||||
|
|
||||||
<main className="account-settings">
|
<main className="account-settings">
|
||||||
<header className="account-settings__header">
|
<header className="account-settings__header">
|
||||||
<h1 className="account-settings__title">Account Settings</h1>
|
<h1 className="account-settings__title">{this.props.t('AccountView.Settings')}</h1>
|
||||||
</header>
|
</header>
|
||||||
{accessTokensUIEnabled &&
|
{accessTokensUIEnabled &&
|
||||||
<Tabs className="account__tabs">
|
<Tabs className="account__tabs">
|
||||||
<TabList>
|
<TabList>
|
||||||
<div className="tabs__titles">
|
<div className="tabs__titles">
|
||||||
<Tab><h4 className="tabs__title">Account</h4></Tab>
|
<Tab><h4 className="tabs__title">{this.props.t('AccountView.AccountTab')}</h4></Tab>
|
||||||
{accessTokensUIEnabled && <Tab><h4 className="tabs__title">Access Tokens</h4></Tab>}
|
{accessTokensUIEnabled && <Tab><h4 className="tabs__title">{this.props.t('AccountView.AccessTokensTab')}</h4></Tab>}
|
||||||
</div>
|
</div>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
@ -107,13 +110,14 @@ function asyncValidate(formProps, dispatch, props) {
|
||||||
|
|
||||||
AccountView.propTypes = {
|
AccountView.propTypes = {
|
||||||
previousPath: PropTypes.string.isRequired,
|
previousPath: PropTypes.string.isRequired,
|
||||||
theme: PropTypes.string.isRequired
|
theme: PropTypes.string.isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default reduxForm({
|
export default withTranslation()(reduxForm({
|
||||||
form: 'updateAllSettings',
|
form: 'updateAllSettings',
|
||||||
fields: ['username', 'email', 'currentPassword', 'newPassword'],
|
fields: ['username', 'email', 'currentPassword', 'newPassword'],
|
||||||
validate: validateSettings,
|
validate: validateSettings,
|
||||||
asyncValidate,
|
asyncValidate,
|
||||||
asyncBlurFields: ['username', 'email', 'currentPassword']
|
asyncBlurFields: ['username', 'email', 'currentPassword']
|
||||||
}, mapStateToProps, mapDispatchToProps)(AccountView);
|
}, mapStateToProps, mapDispatchToProps)(AccountView));
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { reduxForm } from 'redux-form';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
|
import i18next from 'i18next';
|
||||||
import NewPasswordForm from '../components/NewPasswordForm';
|
import NewPasswordForm from '../components/NewPasswordForm';
|
||||||
import * as UserActions from '../actions';
|
import * as UserActions from '../actions';
|
||||||
import Nav from '../../../components/Nav';
|
import Nav from '../../../components/Nav';
|
||||||
|
@ -20,13 +22,13 @@ function NewPasswordView(props) {
|
||||||
<Nav layout="dashboard" />
|
<Nav layout="dashboard" />
|
||||||
<div className={newPasswordClass}>
|
<div className={newPasswordClass}>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>p5.js Web Editor | New Password</title>
|
<title>{props.t('NewPasswordView.Title')}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="form-container__content">
|
<div className="form-container__content">
|
||||||
<h2 className="form-container__title">Set a New Password</h2>
|
<h2 className="form-container__title">{props.t('NewPasswordView.Description')}</h2>
|
||||||
<NewPasswordForm {...props} />
|
<NewPasswordForm {...props} />
|
||||||
<p className="new-password__invalid">
|
<p className="new-password__invalid">
|
||||||
The password reset token is invalid or has expired.
|
{props.t('NewPasswordView.TokenInvalidOrExpired')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -41,21 +43,22 @@ NewPasswordView.propTypes = {
|
||||||
validateResetPasswordToken: PropTypes.func.isRequired,
|
validateResetPasswordToken: PropTypes.func.isRequired,
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
resetPasswordInvalid: PropTypes.bool
|
resetPasswordInvalid: PropTypes.bool
|
||||||
}).isRequired
|
}).isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function validate(formProps) {
|
function validate(formProps) {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
|
|
||||||
if (!formProps.password) {
|
if (!formProps.password) {
|
||||||
errors.password = 'Please enter a password';
|
errors.password = i18next.t('NewPasswordView.EmptyPassword');
|
||||||
}
|
}
|
||||||
if (!formProps.confirmPassword) {
|
if (!formProps.confirmPassword) {
|
||||||
errors.confirmPassword = 'Please enter a password confirmation';
|
errors.confirmPassword = i18next.t('NewPasswordView.PasswordConfirmation');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formProps.password !== formProps.confirmPassword) {
|
if (formProps.password !== formProps.confirmPassword) {
|
||||||
errors.password = 'Passwords must match';
|
errors.password = i18next.t('NewPasswordView.PasswordMismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -71,8 +74,8 @@ function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators(UserActions, dispatch);
|
return bindActionCreators(UserActions, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default reduxForm({
|
export default withTranslation()(reduxForm({
|
||||||
form: 'new-password',
|
form: 'new-password',
|
||||||
fields: ['password', 'confirmPassword'],
|
fields: ['password', 'confirmPassword'],
|
||||||
validate
|
validate
|
||||||
}, mapStateToProps, mapDispatchToProps)(NewPasswordView);
|
}, mapStateToProps, mapDispatchToProps)(NewPasswordView));
|
||||||
|
|
|
@ -6,6 +6,7 @@ import classNames from 'classnames';
|
||||||
import { bindActionCreators } from 'redux';
|
import { bindActionCreators } from 'redux';
|
||||||
import { reduxForm } from 'redux-form';
|
import { reduxForm } from 'redux-form';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
import { withTranslation } from 'react-i18next';
|
||||||
import * as UserActions from '../actions';
|
import * as UserActions from '../actions';
|
||||||
import ResetPasswordForm from '../components/ResetPasswordForm';
|
import ResetPasswordForm from '../components/ResetPasswordForm';
|
||||||
import { validateResetPassword } from '../../../utils/reduxFormUtils';
|
import { validateResetPassword } from '../../../utils/reduxFormUtils';
|
||||||
|
@ -23,19 +24,18 @@ function ResetPasswordView(props) {
|
||||||
<Nav layout="dashboard" />
|
<Nav layout="dashboard" />
|
||||||
<div className={resetPasswordClass}>
|
<div className={resetPasswordClass}>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>p5.js Web Editor | Reset Password</title>
|
<title>{props.t('ResetPasswordView.Title')}</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="form-container__content">
|
<div className="form-container__content">
|
||||||
<h2 className="form-container__title">Reset Your Password</h2>
|
<h2 className="form-container__title">{props.t('ResetPasswordView.Reset')}</h2>
|
||||||
<ResetPasswordForm {...props} />
|
<ResetPasswordForm {...props} />
|
||||||
<p className="reset-password__submitted">
|
<p className="reset-password__submitted">
|
||||||
Your password reset email should arrive shortly. If you don't see it, check
|
{props.t('ResetPasswordView.Submitted')}
|
||||||
in your spam folder as sometimes it can end up there.
|
|
||||||
</p>
|
</p>
|
||||||
<p className="form__navigation-options">
|
<p className="form__navigation-options">
|
||||||
<Link className="form__login-button" to="/login">Log In</Link>
|
<Link className="form__login-button" to="/login">{props.t('ResetPasswordView.Login')}</Link>
|
||||||
or
|
{props.t('ResetPasswordView.LoginOr')}
|
||||||
<Link className="form__signup-button" to="/signup">Sign Up</Link>
|
<Link className="form__signup-button" to="/signup">{props.t('ResetPasswordView.SignUp')}</Link>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +48,7 @@ ResetPasswordView.propTypes = {
|
||||||
user: PropTypes.shape({
|
user: PropTypes.shape({
|
||||||
resetPasswordInitiate: PropTypes.bool
|
resetPasswordInitiate: PropTypes.bool
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
|
t: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
@ -60,8 +61,8 @@ function mapDispatchToProps(dispatch) {
|
||||||
return bindActionCreators(UserActions, dispatch);
|
return bindActionCreators(UserActions, dispatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default reduxForm({
|
export default withTranslation()(reduxForm({
|
||||||
form: 'reset-password',
|
form: 'reset-password',
|
||||||
fields: ['email'],
|
fields: ['email'],
|
||||||
validate: validateResetPassword
|
validate: validateResetPassword
|
||||||
}, mapStateToProps, mapDispatchToProps)(ResetPasswordView);
|
}, mapStateToProps, mapDispatchToProps)(ResetPasswordView));
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
import i18n from 'i18next';
|
||||||
export const domOnlyProps = ({
|
export const domOnlyProps = ({
|
||||||
initialValue,
|
initialValue,
|
||||||
autofill,
|
autofill,
|
||||||
|
@ -20,19 +21,19 @@ const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))
|
||||||
|
|
||||||
function validateNameEmail(formProps, errors) {
|
function validateNameEmail(formProps, errors) {
|
||||||
if (!formProps.username) {
|
if (!formProps.username) {
|
||||||
errors.username = 'Please enter a username.';
|
errors.username = i18n.t('ReduxFormUtils.errorEmptyUsername');
|
||||||
} else if (!formProps.username.match(/^.{1,20}$/)) {
|
} else if (!formProps.username.match(/^.{1,20}$/)) {
|
||||||
errors.username = 'Username must be less than 20 characters.';
|
errors.username = i18n.t('ReduxFormUtils.errorLongUsername');
|
||||||
} else if (!formProps.username.match(/^[a-zA-Z0-9._-]{1,20}$/)) {
|
} else if (!formProps.username.match(/^[a-zA-Z0-9._-]{1,20}$/)) {
|
||||||
errors.username = 'Username must only consist of numbers, letters, periods, dashes, and underscores.';
|
errors.username = i18n.t('ReduxFormUtils.errorValidUsername');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formProps.email) {
|
if (!formProps.email) {
|
||||||
errors.email = 'Please enter an email.';
|
errors.email = i18n.t('ReduxFormUtils.errorEmptyEmail');
|
||||||
} else if (
|
} else if (
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
!formProps.email.match(EMAIL_REGEX)) {
|
!formProps.email.match(EMAIL_REGEX)) {
|
||||||
errors.email = 'Please enter a valid email address.';
|
errors.email = i18n.t('ReduxFormUtils.errorInvalidEmail');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,10 +43,10 @@ export function validateSettings(formProps) {
|
||||||
validateNameEmail(formProps, errors);
|
validateNameEmail(formProps, errors);
|
||||||
|
|
||||||
if (formProps.currentPassword && !formProps.newPassword) {
|
if (formProps.currentPassword && !formProps.newPassword) {
|
||||||
errors.newPassword = 'Please enter a new password or leave the current password empty.';
|
errors.newPassword = i18n.t('ReduxFormUtils.errorNewPassword');
|
||||||
}
|
}
|
||||||
if (formProps.newPassword && formProps.newPassword.length < 6) {
|
if (formProps.newPassword && formProps.newPassword.length < 6) {
|
||||||
errors.newPassword = 'Password must be at least 6 characters';
|
errors.newPassword = i18n.t('ReduxFormUtils.errorShortPassword');
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
@ -53,10 +54,10 @@ export function validateSettings(formProps) {
|
||||||
export function validateLogin(formProps) {
|
export function validateLogin(formProps) {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
if (!formProps.email) {
|
if (!formProps.email) {
|
||||||
errors.email = 'Please enter an email';
|
errors.email = i18n.t('ReduxFormUtils.errorEmptyEmail');
|
||||||
}
|
}
|
||||||
if (!formProps.password) {
|
if (!formProps.password) {
|
||||||
errors.password = 'Please enter a password';
|
errors.password = i18n.t('ReduxFormUtils.errorEmptyPassword');
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
@ -67,17 +68,17 @@ export function validateSignup(formProps) {
|
||||||
validateNameEmail(formProps, errors);
|
validateNameEmail(formProps, errors);
|
||||||
|
|
||||||
if (!formProps.password) {
|
if (!formProps.password) {
|
||||||
errors.password = 'Please enter a password';
|
errors.password = i18n.t('ReduxFormUtils.errorEmptyPassword');
|
||||||
}
|
}
|
||||||
if (formProps.password && formProps.password.length < 6) {
|
if (formProps.password && formProps.password.length < 6) {
|
||||||
errors.password = 'Password must be at least 6 characters';
|
errors.password = i18n.t('ReduxFormUtils.errorShortPassword');
|
||||||
}
|
}
|
||||||
if (!formProps.confirmPassword) {
|
if (!formProps.confirmPassword) {
|
||||||
errors.confirmPassword = 'Please enter a password confirmation';
|
errors.confirmPassword = i18n.t('ReduxFormUtils.errorConfirmPassword');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formProps.password !== formProps.confirmPassword && formProps.confirmPassword) {
|
if (formProps.password !== formProps.confirmPassword && formProps.confirmPassword) {
|
||||||
errors.confirmPassword = 'Passwords must match';
|
errors.confirmPassword = i18n.t('ReduxFormUtils.errorPasswordMismatch');
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
|
@ -85,11 +86,11 @@ export function validateSignup(formProps) {
|
||||||
export function validateResetPassword(formProps) {
|
export function validateResetPassword(formProps) {
|
||||||
const errors = {};
|
const errors = {};
|
||||||
if (!formProps.email) {
|
if (!formProps.email) {
|
||||||
errors.email = 'Please enter an email.';
|
errors.email = i18n.t('ReduxFormUtils.errorEmptyEmail');
|
||||||
} else if (
|
} else if (
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
!formProps.email.match(EMAIL_REGEX)) {
|
!formProps.email.match(EMAIL_REGEX)) {
|
||||||
errors.email = 'Please enter a valid email address.';
|
errors.email = i18n.t('ReduxFormUtils.errorInvalidEmail');
|
||||||
}
|
}
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,6 @@
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"p5logoARIA": "p5.js Logo"
|
"p5logoARIA": "p5.js Logo"
|
||||||
|
|
||||||
},
|
},
|
||||||
"IDEView": {
|
"IDEView": {
|
||||||
"SubmitFeedback": "Submit Feedback"
|
"SubmitFeedback": "Submit Feedback"
|
||||||
|
@ -197,7 +196,7 @@
|
||||||
"NewFileForm": {
|
"NewFileForm": {
|
||||||
"AddFileSubmit": "Add File",
|
"AddFileSubmit": "Add File",
|
||||||
"Placeholder": "Name"
|
"Placeholder": "Name"
|
||||||
},
|
},
|
||||||
"NewFolderModal": {
|
"NewFolderModal": {
|
||||||
"Title": "Create Folder",
|
"Title": "Create Folder",
|
||||||
"CloseButtonARIA": "Close New Folder Modal",
|
"CloseButtonARIA": "Close New Folder Modal",
|
||||||
|
@ -208,5 +207,87 @@
|
||||||
"NewFolderForm": {
|
"NewFolderForm": {
|
||||||
"AddFolderSubmit": "Add Folder",
|
"AddFolderSubmit": "Add Folder",
|
||||||
"Placeholder": "Name"
|
"Placeholder": "Name"
|
||||||
|
},
|
||||||
|
"ResetPasswordForm": {
|
||||||
|
"Email": "Email used for registration",
|
||||||
|
"EmailARIA": "email",
|
||||||
|
"Submit": "Send Password Reset Email"
|
||||||
|
},
|
||||||
|
"ResetPasswordView": {
|
||||||
|
"Title": "p5.js Web Editor | Reset Password",
|
||||||
|
"Reset": "Reset Your Password",
|
||||||
|
"Submitted": "Your password reset email should arrive shortly. If you don't see it, check\n in your spam folder as sometimes it can end up there.",
|
||||||
|
"Login": "Log In",
|
||||||
|
"LoginOr": "or",
|
||||||
|
"SignUp": "Sign Up"
|
||||||
|
},
|
||||||
|
"ReduxFormUtils": {
|
||||||
|
"errorInvalidEmail": "Please enter a valid email address",
|
||||||
|
"errorEmptyEmail": "Please enter an email",
|
||||||
|
"errorPasswordMismatch": "Passwords must match",
|
||||||
|
"errorEmptyPassword": "Please enter a password",
|
||||||
|
"errorShortPassword": "Password must be at least 6 characters",
|
||||||
|
"errorConfirmPassword": "Please enter a password confirmation",
|
||||||
|
"errorNewPassword": "Please enter a new password or leave the current password empty.",
|
||||||
|
"errorEmptyUsername": "Please enter a username.",
|
||||||
|
"errorLongUsername": "Username must be less than 20 characters.",
|
||||||
|
"errorValidUsername": "Username must only consist of numbers, letters, periods, dashes, and underscores."
|
||||||
|
},
|
||||||
|
"NewPasswordView": {
|
||||||
|
"Title": "p5.js Web Editor | New Password",
|
||||||
|
"Description": "Set a New Password",
|
||||||
|
"TokenInvalidOrExpired": "The password reset token is invalid or has expired.",
|
||||||
|
"EmptyPassword": "Please enter a password",
|
||||||
|
"PasswordConfirmation": "Please enter a password confirmation",
|
||||||
|
"PasswordMismatch": "Passwords must match"
|
||||||
|
},
|
||||||
|
"AccountForm": {
|
||||||
|
"Email": "Email",
|
||||||
|
"EmailARIA": "email",
|
||||||
|
"Unconfirmed": "Unconfirmed.",
|
||||||
|
"EmailSent": "Confirmation sent, check your email.",
|
||||||
|
"Resend": "Resend confirmation email",
|
||||||
|
"UserName": "User Name",
|
||||||
|
"UserNameARIA": "Username",
|
||||||
|
"CurrentPassword": "Current Password",
|
||||||
|
"CurrentPasswordARIA": "Current Password",
|
||||||
|
"NewPassword": "New Password",
|
||||||
|
"NewPasswordARIA": "New Password",
|
||||||
|
"SubmitSaveAllSettings": "Save All Settings"
|
||||||
|
},
|
||||||
|
"AccountView": {
|
||||||
|
"SocialLogin": "Social Login",
|
||||||
|
"SocialLoginDescription": "Use your GitHub or Google account to log into the p5.js Web Editor.",
|
||||||
|
"Title": "p5.js Web Editor | Account Settings",
|
||||||
|
"Settings": "Account Settings",
|
||||||
|
"AccountTab": "Account",
|
||||||
|
"AccessTokensTab": "Access Tokens"
|
||||||
|
},
|
||||||
|
"APIKeyForm": {
|
||||||
|
"ConfirmDelete": "Are you sure you want to delete {{key_label}}?",
|
||||||
|
"Summary": "Personal Access Tokens act like your password to allow automated\n scripts to access the Editor API. Create a token for each script\n that needs access.",
|
||||||
|
"CreateToken": "Create new token",
|
||||||
|
"TokenLabel": "What is this token for?",
|
||||||
|
"TokenPlaceholder": "What is this token for? e.g. Example import script",
|
||||||
|
"CreateTokenSubmit": "Create",
|
||||||
|
"NoTokens": "You have no existing tokens.",
|
||||||
|
"NewTokenTitle": "Your new access token",
|
||||||
|
"NewTokenInfo": "Make sure to copy your new personal access token now.\n You won’t be able to see it again!",
|
||||||
|
"ExistingTokensTitle": "Existing tokens"
|
||||||
|
},
|
||||||
|
"APIKeyList": {
|
||||||
|
"Name": "Name",
|
||||||
|
"Created": "Created on",
|
||||||
|
"LastUsed": "Last used",
|
||||||
|
"Actions": "Actions",
|
||||||
|
"Never": "Never",
|
||||||
|
"DeleteARIA": "Delete API Key"
|
||||||
|
},
|
||||||
|
"NewPasswordForm": {
|
||||||
|
"Title": "Password",
|
||||||
|
"TitleARIA": "Password",
|
||||||
|
"ConfirmPassword": "Confirm Password",
|
||||||
|
"ConfirmPasswordARIA": "Confirm Password",
|
||||||
|
"SubmitSetNewPassword": "Set New Password"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -203,13 +203,91 @@
|
||||||
"EnterName": "Por favor introduce un nombre",
|
"EnterName": "Por favor introduce un nombre",
|
||||||
"EmptyName": " El nombre del directorio no debe contener solo espacios vacíos",
|
"EmptyName": " El nombre del directorio no debe contener solo espacios vacíos",
|
||||||
"InvalidExtension": "El nombre del directorio no debe contener una extensión"
|
"InvalidExtension": "El nombre del directorio no debe contener una extensión"
|
||||||
},
|
},
|
||||||
"NewFolderForm": {
|
"NewFolderForm": {
|
||||||
"AddFolderSubmit": "Agregar Directorio",
|
"AddFolderSubmit": "Agregar Directorio",
|
||||||
"Placeholder": "Nombre"
|
"Placeholder": "Nombre"
|
||||||
|
},
|
||||||
|
"ResetPasswordForm": {
|
||||||
|
"Email": "Correo electrónico usado al registrarse",
|
||||||
|
"EmailARIA": "correo electrónico",
|
||||||
|
"Submit": "Enviar correo para regenerar contraseña"
|
||||||
|
},
|
||||||
|
"ResetPasswordView": {
|
||||||
|
"Title": "Editor Web p5.js | Regenerar Contraseña",
|
||||||
|
"Reset": "Regenerar Contraseña",
|
||||||
|
"Submitted": "Your password reset email should arrive shortly. If you don't see it, check\n in your spam folder as sometimes it can end up there.",
|
||||||
|
"Login": "Ingresa",
|
||||||
|
"LoginOr": "o",
|
||||||
|
"SignUp": "Registráte"
|
||||||
|
},
|
||||||
|
"ReduxFormUtils": {
|
||||||
|
"errorInvalidEmail": "Por favor introduce un correo electrónico válido",
|
||||||
|
"errorEmptyEmail": "Por favor introduce un correo electrónico",
|
||||||
|
"errorPasswordMismatch": "Las contraseñas deben coincidir",
|
||||||
|
"errorEmptyPassword": "Por favor introduce una contraseña",
|
||||||
|
"errorShortPassword": "La contraseña debe tener al menos 6 caracteres",
|
||||||
|
"errorConfirmPassword": "Por favor confirma una contraseña",
|
||||||
|
"errorNewPassword": "Por favor introduce una nueva contraseña o deja la actual contraseña vacía",
|
||||||
|
"errorEmptyUsername": "Por favor introduce tu identificación",
|
||||||
|
"errorLongUsername": "La identificación debe ser menor a 20 caracteres.",
|
||||||
|
"errorValidUsername": "La identificación debe consistir solamente de números, letras, puntos, guiones y guiones bajos."
|
||||||
|
},
|
||||||
|
"NewPasswordView": {
|
||||||
|
"Title": "Editor Web p5.js | Nueva Contraseña",
|
||||||
|
"Description": "Define una nueva contraseña",
|
||||||
|
"TokenInvalidOrExpired": "El token para regenerar la contraseña es inválido o ha expirado.",
|
||||||
|
"EmptyPassword": "Por favor introduce una contraseña",
|
||||||
|
"PasswordConfirmation": "Por favor confirma la contraseña",
|
||||||
|
"PasswordMismatch": "Las contraseña deben coincidir"
|
||||||
|
},
|
||||||
|
"AccountForm": {
|
||||||
|
"Email": "Correo Electrónico",
|
||||||
|
"EmailARIA": "correo electrónico",
|
||||||
|
"Unconfirmed": "Sin confirmar.",
|
||||||
|
"EmailSent": "Confirmación enviada, revisa tu correo electrónico.",
|
||||||
|
"Resend": "Reenviar correo de confirmación",
|
||||||
|
"UserName": "Nombre de Identificación",
|
||||||
|
"UserNameARIA": "Nombre de identificación",
|
||||||
|
"CurrentPassword": "Contraseña Actual",
|
||||||
|
"CurrentPasswordARIA": "Contraseña Actual",
|
||||||
|
"NewPassword": "Nueva Contraseña",
|
||||||
|
"NewPasswordARIA": "Nueva Contraseña",
|
||||||
|
"SubmitSaveAllSettings": "Guardar Todas Las Configuraciones"
|
||||||
|
},
|
||||||
|
"AccountView": {
|
||||||
|
"SocialLogin": "Identificacion usando redes sociales",
|
||||||
|
"SocialLoginDescription": "Usa tu cuenta de GitHub o Google para acceder al Editor Web de p5.js .",
|
||||||
|
"Title": "Editor Web p5.js | Configuración Cuenta",
|
||||||
|
"Settings": "Configuración de la Cuenta",
|
||||||
|
"AccountTab": "Cuenta",
|
||||||
|
"AccessTokensTab": "Tokens de acceso"
|
||||||
|
},
|
||||||
|
"APIKeyForm": {
|
||||||
|
"ConfirmDelete": "¿Estas seguro que quieres borrar {{key_label}}?",
|
||||||
|
"Summary": " Los Tokens de acceso personal actuan como tu contraseña para permitir\n a scripts automáticos acceder al API del Editor. Crea un token por cada script \n que necesite acceso.",
|
||||||
|
"CreateToken": "Crear nuevo token",
|
||||||
|
"TokenLabel": "¿Para que es este token?",
|
||||||
|
"TokenPlaceholder": "¿Para que es este token? p.e. Ejemplo para Importar un Archivo",
|
||||||
|
"CreateTokenSubmit": "Crear",
|
||||||
|
"NoTokens": "No tienes tokens.",
|
||||||
|
"NewTokenTitle": "Tu nuevo token de acceso",
|
||||||
|
"NewTokenInfo": "Asegurate de copiar tu token ahora mismo.\n ¡No podras verlo de nuevo!",
|
||||||
|
"ExistingTokensTitle": "Tokens existentes"
|
||||||
|
},
|
||||||
|
"APIKeyList": {
|
||||||
|
"Name": "Nombre",
|
||||||
|
"Created": "Creado en",
|
||||||
|
"LastUsed": "Usado por última vez",
|
||||||
|
"Actions": "Acciones",
|
||||||
|
"Never": "Nunca",
|
||||||
|
"DeleteARIA": "Borrar clave de API"
|
||||||
|
},
|
||||||
|
"NewPasswordForm": {
|
||||||
|
"Title": "Contraseña",
|
||||||
|
"TitleARIA": "Contraseña",
|
||||||
|
"ConfirmPassword": "Confirmar Contraseña",
|
||||||
|
"ConfirmPasswordARIA": "Confirmar contraseña",
|
||||||
|
"SubmitSetNewPassword": "Crear Nueva Contraseña"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue