Added design of the API key page

This commit is contained in:
Vertmo 2018-10-13 22:14:46 +02:00 committed by Cassie Tarakajian
parent 18239eb2e0
commit 3d2a862d9d
7 changed files with 225 additions and 4 deletions

View file

@ -0,0 +1,71 @@
import PropTypes from 'prop-types';
import React from 'react';
class APIKeyForm extends React.Component {
constructor(props) {
super(props);
this.state = { keyLabel: '' };
this.addKey = this.addKey.bind(this);
}
addKey(event) {
// TODO
console.log('addKey');
this.props.updateSettings();
event.preventDefault();
return false;
}
removeKey(k) {
// TODO
console.log(k);
}
render() {
return (
<div>
<h2 className="form__label">Key label</h2>
<form className="form" onSubmit={this.addKey}>
<input
type="text"
className="form__input"
id="keyLabel"
onChange={(event) => { this.setState({ keyLabel: event.target.value }); }}
/><br />
<input
type="submit"
value="Create new Key"
disabled={this.state.keyLabel === ''}
/>
</form>
<table className="form__table">
<tbody>
{[{
id: 1,
label: 'MyFirstAPI',
createdAt: new Date(),
lastUsedAt: new Date()
}, {
id: 2,
label: 'MyOtherAPI',
createdAt: new Date(),
lastUsedAt: new Date()
}].map(v => (
<tr key={v.id}>
<td><b>{v.label}</b><br />Created on: {v.createdAt.toLocaleDateString()} {v.createdAt.toLocaleTimeString()}</td>
<td>Last used on:<br /> {v.lastUsedAt.toLocaleDateString()} {v.lastUsedAt.toLocaleTimeString()}</td>
<td><button className="form__button-remove" onClick={() => this.removeKey(v)}>Delete</button></td>
</tr>))}
</tbody>
</table>
</div>
);
}
}
APIKeyForm.propTypes = {
updateSettings: PropTypes.func.isRequired,
};
export default APIKeyForm;

View file

@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { reduxForm } from 'redux-form'; import { reduxForm } from 'redux-form';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { browserHistory } from 'react-router'; import { Link, browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg'; import InlineSVG from 'react-inlinesvg';
import axios from 'axios'; import axios from 'axios';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
@ -22,8 +22,12 @@ class AccountView extends React.Component {
this.gotoHomePage = this.gotoHomePage.bind(this); this.gotoHomePage = this.gotoHomePage.bind(this);
} }
componentDidMount() {
document.body.className = this.props.theme;
}
closeAccountPage() { closeAccountPage() {
browserHistory.push(this.props.previousPath); browserHistory.goBack();
} }
gotoHomePage() { gotoHomePage() {
@ -47,6 +51,7 @@ class AccountView extends React.Component {
<div className="form-container__content"> <div className="form-container__content">
<h2 className="form-container__title">My Account</h2> <h2 className="form-container__title">My Account</h2>
<AccountForm {...this.props} /> <AccountForm {...this.props} />
<Link to="/account/advanced">Advanced Settings</Link>
<h2 className="form-container__divider">Or</h2> <h2 className="form-container__divider">Or</h2>
<GithubButton buttonText="Login with Github" /> <GithubButton buttonText="Login with Github" />
</div> </div>
@ -59,7 +64,7 @@ function mapStateToProps(state) {
return { return {
initialValues: state.user, // <- initialValues for reduxForm initialValues: state.user, // <- initialValues for reduxForm
user: state.user, user: state.user,
previousPath: state.ide.previousPath theme: state.preferences.theme
}; };
} }
@ -86,7 +91,7 @@ function asyncValidate(formProps, dispatch, props) {
} }
AccountView.propTypes = { AccountView.propTypes = {
previousPath: PropTypes.string.isRequired, theme: PropTypes.string.isRequired
}; };
export default reduxForm({ export default reduxForm({

View file

@ -0,0 +1,104 @@
import PropTypes from 'prop-types';
import React from 'react';
import { reduxForm } from 'redux-form';
import { bindActionCreators } from 'redux';
import { browserHistory } from 'react-router';
import InlineSVG from 'react-inlinesvg';
import axios from 'axios';
import { Helmet } from 'react-helmet';
import { updateSettings, initiateVerification } from '../actions';
import { validateSettings } from '../../../utils/reduxFormUtils';
import APIKeyForm from '../components/APIKeyForm';
const exitUrl = require('../../../images/exit.svg');
const logoUrl = require('../../../images/p5js-logo.svg');
// TODO tmp
const ident = () => {};
class AccountView extends React.Component {
constructor(props) {
super(props);
this.closeAccountPage = this.closeAccountPage.bind(this);
this.gotoHomePage = this.gotoHomePage.bind(this);
}
componentDidMount() {
document.body.className = this.props.theme;
}
closeAccountPage() {
browserHistory.goBack();
}
gotoHomePage() {
browserHistory.push('/');
}
render() {
return (
<div className="form-container">
<Helmet>
<title>p5.js Web Editor | Advanced Settings</title>
</Helmet>
<div className="form-container__header">
<button className="form-container__logo-button" onClick={this.gotoHomePage}>
<InlineSVG src={logoUrl} alt="p5js Logo" />
</button>
<button className="form-container__exit-button" onClick={this.closeAccountPage}>
<InlineSVG src={exitUrl} alt="Close Account Page" />
</button>
</div>
<div className="form-container__content">
<h2 className="form-container__title">Advanced Settings</h2>
<APIKeyForm
updateSettings={ident}
/>
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
initialValues: state.user, // <- initialValues for reduxForm
user: state.user,
previousPath: state.ide.previousPath,
theme: state.preferences.theme
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ updateSettings, initiateVerification }, dispatch);
}
function asyncValidate(formProps, dispatch, props) {
const fieldToValidate = props.form._active;
if (fieldToValidate) {
const queryParams = {};
queryParams[fieldToValidate] = formProps[fieldToValidate];
queryParams.check_type = fieldToValidate;
return axios.get('/api/signup/duplicate_check', { params: queryParams })
.then((response) => {
if (response.data.exists) {
const error = {};
error[fieldToValidate] = response.data.message;
throw error;
}
});
}
return Promise.resolve(true).then(() => {});
}
AccountView.propTypes = {
theme: PropTypes.string.isRequired
};
export default reduxForm({
form: 'updateAllSettings',
fields: ['username', 'email', 'currentPassword', 'newPassword'],
validate: validateSettings,
asyncValidate,
asyncBlurFields: ['username', 'email', 'currentPassword']
}, mapStateToProps, mapDispatchToProps)(AccountView);

View file

@ -9,6 +9,7 @@ import ResetPasswordView from './modules/User/pages/ResetPasswordView';
import EmailVerificationView from './modules/User/pages/EmailVerificationView'; import EmailVerificationView from './modules/User/pages/EmailVerificationView';
import NewPasswordView from './modules/User/pages/NewPasswordView'; import NewPasswordView from './modules/User/pages/NewPasswordView';
import AccountView from './modules/User/pages/AccountView'; import AccountView from './modules/User/pages/AccountView';
import AdvancedSettingsView from './modules/User/pages/AdvancedSettingsView';
// import SketchListView from './modules/Sketch/pages/SketchListView'; // import SketchListView from './modules/Sketch/pages/SketchListView';
import { getUser } from './modules/User/actions'; import { getUser } from './modules/User/actions';
import { stopSketch } from './modules/IDE/actions/ide'; import { stopSketch } from './modules/IDE/actions/ide';
@ -38,6 +39,7 @@ const routes = store => (
<Route path="/sketches" component={IDEView} /> <Route path="/sketches" component={IDEView} />
<Route path="/assets" component={IDEView} /> <Route path="/assets" component={IDEView} />
<Route path="/account" component={AccountView} /> <Route path="/account" component={AccountView} />
<Route path="/account/advanced" component={AdvancedSettingsView} />
<Route path="/:username/sketches/:project_id" component={IDEView} /> <Route path="/:username/sketches/:project_id" component={IDEView} />
<Route path="/:username/sketches" component={IDEView} /> <Route path="/:username/sketches" component={IDEView} />
<Route path="/about" component={IDEView} /> <Route path="/about" component={IDEView} />

View file

@ -37,3 +37,17 @@
.form-container__exit-button { .form-container__exit-button {
@extend %none-themify-icon-with-hover; @extend %none-themify-icon-with-hover;
} }
.form__table {
display: block;
max-width: 900px;
border-collapse: collapse;
& td {
max-width: 300px;
border: 1px solid #ddd;
padding: 0 10px 0 10px;
}
& tr:nth-child(even) {
background-color: #f2f2f2;
}
}

View file

@ -51,3 +51,20 @@
.form input[type="submit"]:disabled { .form input[type="submit"]:disabled {
cursor: not-allowed; cursor: not-allowed;
} }
.form__button-remove {
@extend %forms-button;
margin: 1rem 0 1rem 0;
@include themify() {
color: getThemifyVariable('console-error-background-color');
border-color: getThemifyVariable('console-error-background-color');
&:enabled:hover {
border-color: getThemifyVariable('console-error-background-color');
background-color: getThemifyVariable('console-error-background-color');
}
&:enabled:active {
border-color: getThemifyVariable('console-error-background-color');
background-color: getThemifyVariable('console-error-background-color');
}
}
}

View file

@ -87,6 +87,14 @@ router.get('/account', (req, res) => {
} }
}); });
router.get('/account/advanced', (req, res) => {
if (req.user) {
res.send(renderIndex());
} else {
res.redirect('/login');
}
});
router.get('/about', (req, res) => { router.get('/about', (req, res) => {
res.send(renderIndex()); res.send(renderIndex());
}); });