wip Pup 1.0.0
This commit is contained in:
commit
cdc15f019d
48 changed files with 1100 additions and 0 deletions
51
imports/api/Documents/Documents.js
Normal file
51
imports/api/Documents/Documents.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* eslint-disable consistent-return */
|
||||
|
||||
import { Mongo } from 'meteor/mongo';
|
||||
import SimpleSchema from 'simpl-schema';
|
||||
|
||||
const Documents = new Mongo.Collection('Documents');
|
||||
|
||||
Documents.allow({
|
||||
insert: () => false,
|
||||
update: () => false,
|
||||
remove: () => false,
|
||||
});
|
||||
|
||||
Documents.deny({
|
||||
insert: () => true,
|
||||
update: () => true,
|
||||
remove: () => true,
|
||||
});
|
||||
|
||||
Documents.schema = new SimpleSchema({
|
||||
owner: {
|
||||
type: String,
|
||||
label: 'The ID of the user this document belongs to.',
|
||||
},
|
||||
createdAt: {
|
||||
type: String,
|
||||
label: 'The date this document was created.',
|
||||
autoValue() {
|
||||
if (this.isInsert) return (new Date()).toISOString();
|
||||
},
|
||||
},
|
||||
updatedAt: {
|
||||
type: String,
|
||||
label: 'The date this document was last updated.',
|
||||
autoValue() {
|
||||
if (this.isInsert || this.isUpdate) return (new Date()).toISOString();
|
||||
},
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
label: 'The title of the document.',
|
||||
},
|
||||
body: {
|
||||
type: String,
|
||||
label: 'The body of the document.',
|
||||
},
|
||||
});
|
||||
|
||||
Documents.attachSchema(Documents.schema);
|
||||
|
||||
export default Documents;
|
||||
4
imports/modules/validate.js
Normal file
4
imports/modules/validate.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import $ from 'jquery';
|
||||
import 'jquery-validation';
|
||||
|
||||
export default (form, options) => $(form).validate(options);
|
||||
8
imports/startup/client/index.js
Normal file
8
imports/startup/client/index.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import App from '../../ui/layouts/App/App';
|
||||
|
||||
import '../../ui/stylesheets/app.scss';
|
||||
|
||||
Meteor.startup(() => render(<App />, document.getElementById('react-root')));
|
||||
0
imports/startup/server/index.js
Normal file
0
imports/startup/server/index.js
Normal file
22
imports/ui/components/Authenticated/Authenticated.js
Normal file
22
imports/ui/components/Authenticated/Authenticated.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { Route, Redirect } from 'react-router-dom';
|
||||
|
||||
const Authenticated = ({ loggingIn, authenticated, component, ...rest }) => (
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props) => {
|
||||
if (loggingIn) return <div />;
|
||||
return authenticated ?
|
||||
(React.createElement(component, { ...props, loggingIn, authenticated })) :
|
||||
(<Redirect to="/login" />);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
Authenticated.propTypes = {
|
||||
loggingIn: PropTypes.bool.isRequired,
|
||||
authenticated: PropTypes.bool.isRequired,
|
||||
component: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Authenticated;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { LinkContainer } from 'react-router-bootstrap';
|
||||
import { Nav, NavItem, NavDropdown, MenuItem } from 'react-bootstrap';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const AuthenticatedNavigation = ({ name }) => (
|
||||
<div>
|
||||
<Nav>
|
||||
<LinkContainer to="/documents">
|
||||
<NavItem eventKey={1} href="/documents">Documents</NavItem>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
<Nav pullRight>
|
||||
<NavDropdown eventKey={2} title={name} id="user-nav-dropdown">
|
||||
<LinkContainer to="/profile">
|
||||
<NavItem eventKey={2.1} href="/profile">Profile</NavItem>
|
||||
</LinkContainer>
|
||||
<MenuItem divider />
|
||||
<MenuItem eventKey={2.2} onClick={() => Meteor.logout()}>Logout</MenuItem>
|
||||
</NavDropdown>
|
||||
</Nav>
|
||||
</div>
|
||||
);
|
||||
|
||||
AuthenticatedNavigation.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default AuthenticatedNavigation;
|
||||
16
imports/ui/components/InputHint/InputHint.js
Normal file
16
imports/ui/components/InputHint/InputHint.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './InputHint.scss';
|
||||
|
||||
const InputHint = ({ children }) => (
|
||||
<div className="InputHint">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
InputHint.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default InputHint;
|
||||
9
imports/ui/components/InputHint/InputHint.scss
Normal file
9
imports/ui/components/InputHint/InputHint.scss
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
@import '../../stylesheets/colors';
|
||||
|
||||
.InputHint {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-style: italic;
|
||||
color: $gray-light;
|
||||
font-size: 13px;
|
||||
}
|
||||
33
imports/ui/components/Navigation/Navigation.js
Normal file
33
imports/ui/components/Navigation/Navigation.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Navbar } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PublicNavigation from '../PublicNavigation/PublicNavigation';
|
||||
import AuthenticatedNavigation from '../AuthenticatedNavigation/AuthenticatedNavigation';
|
||||
|
||||
import './Navigation.scss';
|
||||
|
||||
const Navigation = ({ authenticated, name }) => (
|
||||
<Navbar>
|
||||
<Navbar.Header>
|
||||
<Navbar.Brand>
|
||||
<Link to="/">Pup</Link>
|
||||
</Navbar.Brand>
|
||||
<Navbar.Toggle />
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
{!authenticated ? <PublicNavigation /> : <AuthenticatedNavigation name={name} />}
|
||||
</Navbar.Collapse>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
Navigation.defaultProps = {
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
Navigation.propTypes = {
|
||||
authenticated: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
6
imports/ui/components/Navigation/Navigation.scss
Normal file
6
imports/ui/components/Navigation/Navigation.scss
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
.navbar {
|
||||
border-radius: 0;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
}
|
||||
63
imports/ui/components/OAuthLoginButton/OAuthLoginButton.js
Normal file
63
imports/ui/components/OAuthLoginButton/OAuthLoginButton.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Bert } from 'meteor/themeteorchef:bert';
|
||||
|
||||
import './OAuthLoginButton.scss';
|
||||
|
||||
const handleLogin = (service, options, callback) => {
|
||||
const defaultOptions = {
|
||||
facebook: {
|
||||
requestPermissions: ['email'],
|
||||
loginStyle: 'redirect',
|
||||
},
|
||||
google: {
|
||||
requestPermissions: ['email'],
|
||||
requestOfflineToken: true,
|
||||
loginStyle: 'redirect',
|
||||
},
|
||||
github: {
|
||||
requestPermissions: ['user:email'],
|
||||
loginStyle: 'redirect',
|
||||
},
|
||||
}[service];
|
||||
|
||||
const defaultCallback = (error) => {
|
||||
if (error) Bert.alert(error.reason, 'danger');
|
||||
};
|
||||
|
||||
return {
|
||||
facebook: Meteor.loginWithFacebook,
|
||||
google: Meteor.loginWithGoogle,
|
||||
github: Meteor.loginWithGithub,
|
||||
}[service](options || defaultOptions, callback || defaultCallback);
|
||||
};
|
||||
|
||||
const serviceLabel = {
|
||||
facebook: <span><i className="fa fa-facebook" /> Log In with Facebook</span>,
|
||||
google: <span><i className="fa fa-google" /> Log In with Google</span>,
|
||||
github: <span><i className="fa fa-github" /> Log In with GitHub</span>,
|
||||
};
|
||||
|
||||
const OAuthLoginButton = ({ service, options, callback }) => (
|
||||
<button
|
||||
className={`OAuthLoginButton OAuthLoginButton-${service}`}
|
||||
type="button"
|
||||
onClick={() => handleLogin(service, options, callback)}
|
||||
>
|
||||
{serviceLabel[service]}
|
||||
</button>
|
||||
);
|
||||
|
||||
OAuthLoginButton.defaultProps = {
|
||||
options: PropTypes.object,
|
||||
callback: PropTypes.func,
|
||||
};
|
||||
|
||||
OAuthLoginButton.propTypes = {
|
||||
service: PropTypes.string.isRequired,
|
||||
options: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
callback: PropTypes.func,
|
||||
};
|
||||
|
||||
export default OAuthLoginButton;
|
||||
52
imports/ui/components/OAuthLoginButton/OAuthLoginButton.scss
Normal file
52
imports/ui/components/OAuthLoginButton/OAuthLoginButton.scss
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
@import '../../stylesheets/colors';
|
||||
|
||||
.OAuthLoginButton {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
border: none;
|
||||
background: $gray-lighter;
|
||||
border-radius: 3px;
|
||||
|
||||
i {
|
||||
margin-right: 3px;
|
||||
font-size: 18px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
&.OAuthLoginButton-facebook {
|
||||
background: $facebook;
|
||||
color: #fff;
|
||||
|
||||
&:hover { background: darken($facebook, 5%); }
|
||||
}
|
||||
|
||||
&.OAuthLoginButton-google {
|
||||
background: $google;
|
||||
color: #fff;
|
||||
|
||||
&:hover { background: darken($google, 5%); }
|
||||
}
|
||||
|
||||
&.OAuthLoginButton-github {
|
||||
background: $github;
|
||||
color: #fff;
|
||||
|
||||
&:hover { background: darken($github, 5%); }
|
||||
}
|
||||
|
||||
&:active {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.OAuthLoginButton + .OAuthLoginButton {
|
||||
margin-top: 10px;
|
||||
}
|
||||
22
imports/ui/components/Public/Public.js
Normal file
22
imports/ui/components/Public/Public.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { Route, Redirect } from 'react-router-dom';
|
||||
|
||||
const Public = ({ loggingIn, authenticated, component, ...rest }) => (
|
||||
<Route
|
||||
{...rest}
|
||||
render={(props) => {
|
||||
if (loggingIn) return <div />;
|
||||
return !authenticated ?
|
||||
(React.createElement(component, { ...props, loggingIn, authenticated })) :
|
||||
(<Redirect to="/documents" />);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
Public.propTypes = {
|
||||
loggingIn: PropTypes.bool.isRequired,
|
||||
authenticated: PropTypes.bool.isRequired,
|
||||
component: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Public;
|
||||
16
imports/ui/components/PublicNavigation/PublicNavigation.js
Normal file
16
imports/ui/components/PublicNavigation/PublicNavigation.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React from 'react';
|
||||
import { LinkContainer } from 'react-router-bootstrap';
|
||||
import { Nav, NavItem } from 'react-bootstrap';
|
||||
|
||||
const PublicNavigation = () => (
|
||||
<Nav pullRight>
|
||||
<LinkContainer to="/signup">
|
||||
<NavItem eventKey={1} href="/signup">Sign Up</NavItem>
|
||||
</LinkContainer>
|
||||
<LinkContainer to="/login">
|
||||
<NavItem eventKey={2} href="/login">Log In</NavItem>
|
||||
</LinkContainer>
|
||||
</Nav>
|
||||
);
|
||||
|
||||
export default PublicNavigation;
|
||||
61
imports/ui/layouts/App/App.js
Normal file
61
imports/ui/layouts/App/App.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
|
||||
import { Grid } from 'react-bootstrap';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import { Roles } from 'meteor/alanning:roles';
|
||||
import Navigation from '../../components/Navigation/Navigation';
|
||||
import Authenticated from '../../components/Authenticated/Authenticated';
|
||||
import Public from '../../components/Public/Public';
|
||||
import Index from '../../pages/Index/Index';
|
||||
import Documents from '../../pages/Documents/Documents';
|
||||
import NewDocument from '../../pages/NewDocument/NewDocument';
|
||||
import ViewDocument from '../../pages/ViewDocument/ViewDocument';
|
||||
import EditDocument from '../../pages/EditDocument/EditDocument';
|
||||
import Signup from '../../pages/Signup/Signup';
|
||||
import Login from '../../pages/Login/Login';
|
||||
import RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
|
||||
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
|
||||
import NotFound from '../../pages/NotFound/NotFound';
|
||||
|
||||
const App = props => (
|
||||
<Router>
|
||||
{!props.loading ? <div className="App">
|
||||
<Navigation {...props} />
|
||||
<Grid>
|
||||
<Switch>
|
||||
<Route exact name="index" path="/" component={Index} />
|
||||
<Authenticated exact path="/documents" component={Documents} {...props} />
|
||||
<Authenticated exact path="/documents/new" component={NewDocument} {...props} />
|
||||
<Authenticated exact path="/documents/:_id" component={ViewDocument} {...props} />
|
||||
<Authenticated exact path="/documents/:_id/edit" component={EditDocument} {...props} />
|
||||
<Public path="/signup" component={Signup} {...props} />
|
||||
<Public path="/login" component={Login} {...props} />
|
||||
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
|
||||
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</Grid>
|
||||
</div> : ''}
|
||||
</Router>
|
||||
);
|
||||
|
||||
App.propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default createContainer(() => {
|
||||
const loggingIn = Meteor.loggingIn();
|
||||
const user = Meteor.user();
|
||||
const userId = Meteor.userId();
|
||||
const loading = !Roles.subscription.ready();
|
||||
|
||||
return {
|
||||
loading,
|
||||
loggingIn,
|
||||
authenticated: !loggingIn && !!userId,
|
||||
name: user && user.profile ? `${user.profile.name.first} ${user.profile.name.last}` : '',
|
||||
roles: !loading && Roles.getRolesForUser(userId),
|
||||
};
|
||||
}, App);
|
||||
0
imports/ui/layouts/App/App.test.js
Normal file
0
imports/ui/layouts/App/App.test.js
Normal file
0
imports/ui/pages/Documents/Documents.js
Normal file
0
imports/ui/pages/Documents/Documents.js
Normal file
0
imports/ui/pages/EditDocument/EditDocument.js
Normal file
0
imports/ui/pages/EditDocument/EditDocument.js
Normal file
18
imports/ui/pages/Index/Index.js
Normal file
18
imports/ui/pages/Index/Index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import { Jumbotron } from 'react-bootstrap';
|
||||
|
||||
import './Index.scss';
|
||||
|
||||
const Index = () => (
|
||||
<div className="Index">
|
||||
<Jumbotron>
|
||||
<img
|
||||
src="https://s3-us-west-2.amazonaws.com/cleverbeagle-assets/graphics/email-icon.png"
|
||||
alt="Clever Beagle"
|
||||
/>
|
||||
<p>Need help building your app? Check out our mentorship service.</p>
|
||||
</Jumbotron>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default Index;
|
||||
0
imports/ui/pages/Index/Index.scss
Normal file
0
imports/ui/pages/Index/Index.scss
Normal file
106
imports/ui/pages/Login/Login.js
Normal file
106
imports/ui/pages/Login/Login.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import React from 'react';
|
||||
import { Row, Col, FormGroup, ControlLabel, Button } from 'react-bootstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Bert } from 'meteor/themeteorchef:bert';
|
||||
import OAuthLoginButton from '../../components/OAuthLoginButton/OAuthLoginButton';
|
||||
import validate from '../../../modules/validate';
|
||||
|
||||
import './Login.scss';
|
||||
|
||||
class Login extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const component = this;
|
||||
|
||||
validate(component.form, {
|
||||
rules: {
|
||||
emailAddress: {
|
||||
required: true,
|
||||
email: true,
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
emailAddress: {
|
||||
required: 'Need an email address here.',
|
||||
email: 'Is this email address correct?',
|
||||
},
|
||||
password: {
|
||||
required: 'Need a password here.',
|
||||
},
|
||||
},
|
||||
submitHandler() { component.handleSubmit(); },
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const { history } = this.props;
|
||||
|
||||
Meteor.loginWithPassword(this.emailAddress.value, this.password.value, (error) => {
|
||||
if (error) {
|
||||
Bert.alert(error.reason, 'danger');
|
||||
} else {
|
||||
Bert.alert('Welcome back!', 'success');
|
||||
history.push('/documents');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className="Login">
|
||||
<Row>
|
||||
<Col xs={12} sm={6} md={5} lg={4}>
|
||||
<h4 className="page-header">Log In</h4>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<FormGroup className="OAuthLoginButtons">
|
||||
<OAuthLoginButton service="facebook" />
|
||||
<OAuthLoginButton service="google" />
|
||||
<OAuthLoginButton service="github" />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<form ref={form => (this.form = form)} onSubmit={event => event.preventDefault()}>
|
||||
<p className="LoginWithEmail">Log In with an Email Address</p>
|
||||
<FormGroup>
|
||||
<ControlLabel>Email Address</ControlLabel>
|
||||
<input
|
||||
type="email"
|
||||
name="emailAddress"
|
||||
ref={emailAddress => (this.emailAddress = emailAddress)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ControlLabel>Password</ControlLabel>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
ref={password => (this.password = password)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
<Button type="submit" bsStyle="success">Log In</Button>
|
||||
<p className="DontHaveAnAccount">
|
||||
{'Don\'t have an account?'} <Link to="/signup">Sign Up</Link>.
|
||||
</p>
|
||||
</form>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
Login.propTypes = {
|
||||
history: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
|
||||
export default Login;
|
||||
47
imports/ui/pages/Login/Login.scss
Normal file
47
imports/ui/pages/Login/Login.scss
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
@import '../../stylesheets/mixins';
|
||||
@import '../../stylesheets/colors';
|
||||
|
||||
.Login {
|
||||
.page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
border-top: 1px solid $gray-lighter;
|
||||
margin-top: 15px;
|
||||
padding-top: 25px;
|
||||
|
||||
.LoginWithEmail {
|
||||
display: inline-block;
|
||||
background: #fff;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 50%;
|
||||
margin-left: -106px;
|
||||
}
|
||||
|
||||
.DontHaveAnAccount {
|
||||
margin-top: 25px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid $gray-lighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
.Login {
|
||||
.page-header {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(desktop) {
|
||||
.Login {
|
||||
.page-header {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
imports/ui/pages/NewDocument/NewDocument.js
Normal file
0
imports/ui/pages/NewDocument/NewDocument.js
Normal file
0
imports/ui/pages/NotFound/NotFound.js
Normal file
0
imports/ui/pages/NotFound/NotFound.js
Normal file
0
imports/ui/pages/Profile/Profile.js
Normal file
0
imports/ui/pages/Profile/Profile.js
Normal file
0
imports/ui/pages/RecoverPassword/RecoverPassword.js
Normal file
0
imports/ui/pages/RecoverPassword/RecoverPassword.js
Normal file
0
imports/ui/pages/ResetPassword/ResetPassword.js
Normal file
0
imports/ui/pages/ResetPassword/ResetPassword.js
Normal file
155
imports/ui/pages/Signup/Signup.js
Normal file
155
imports/ui/pages/Signup/Signup.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import React from 'react';
|
||||
import { Row, Col, FormGroup, ControlLabel, Button } from 'react-bootstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { Bert } from 'meteor/themeteorchef:bert';
|
||||
import OAuthLoginButton from '../../components/OAuthLoginButton/OAuthLoginButton';
|
||||
import InputHint from '../../components/InputHint/InputHint';
|
||||
import validate from '../../../modules/validate';
|
||||
|
||||
import './Signup.scss';
|
||||
|
||||
class Signup extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const component = this;
|
||||
|
||||
validate(component.form, {
|
||||
rules: {
|
||||
firstName: {
|
||||
required: true,
|
||||
},
|
||||
lastName: {
|
||||
required: true,
|
||||
},
|
||||
emailAddress: {
|
||||
required: true,
|
||||
email: true,
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
minlength: 6,
|
||||
},
|
||||
},
|
||||
messages: {
|
||||
firstName: {
|
||||
required: 'What\'s your first name?',
|
||||
},
|
||||
lastName: {
|
||||
required: 'What\'s your last name?',
|
||||
},
|
||||
emailAddress: {
|
||||
required: 'Need an email address here.',
|
||||
email: 'Is this email address correct?',
|
||||
},
|
||||
password: {
|
||||
required: 'Need a password here.',
|
||||
minlength: 'Please use at least six characters.',
|
||||
},
|
||||
},
|
||||
submitHandler() { component.handleSubmit(); },
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const { history } = this.props;
|
||||
|
||||
Accounts.createUser({
|
||||
email: this.emailAddress.value,
|
||||
password: this.password.value,
|
||||
profile: {
|
||||
name: {
|
||||
first: this.firstName.value,
|
||||
last: this.lastName.value,
|
||||
},
|
||||
},
|
||||
}, (error) => {
|
||||
if (error) {
|
||||
Bert.alert(error.reason, 'danger');
|
||||
} else {
|
||||
Bert.alert('Welcome!', 'success');
|
||||
history.push('/documents');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<div className="Signup">
|
||||
<Row>
|
||||
<Col xs={12} sm={6} md={5} lg={4}>
|
||||
<h4 className="page-header">Sign Up</h4>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<FormGroup className="OAuthLoginButtons">
|
||||
<OAuthLoginButton service="facebook" />
|
||||
<OAuthLoginButton service="google" />
|
||||
<OAuthLoginButton service="github" />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<form ref={form => (this.form = form)} onSubmit={event => event.preventDefault()}>
|
||||
<p className="SignupWithEmail">Sign Up with an Email Address</p>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<ControlLabel>First Name</ControlLabel>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
ref={firstName => (this.firstName = firstName)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<ControlLabel>Last Name</ControlLabel>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
ref={lastName => (this.lastName = lastName)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<FormGroup>
|
||||
<ControlLabel>Email Address</ControlLabel>
|
||||
<input
|
||||
type="email"
|
||||
name="emailAddress"
|
||||
ref={emailAddress => (this.emailAddress = emailAddress)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ControlLabel>Password</ControlLabel>
|
||||
<input
|
||||
type="password"
|
||||
name="password"
|
||||
ref={password => (this.password = password)}
|
||||
className="form-control"
|
||||
/>
|
||||
<InputHint>Use at least six characters.</InputHint>
|
||||
</FormGroup>
|
||||
<Button type="submit" bsStyle="success">Sign Up</Button>
|
||||
<p className="HaveAnAccount">
|
||||
Already have an account? <Link to="/login">Log In</Link>.
|
||||
</p>
|
||||
</form>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
Signup.propTypes = {
|
||||
history: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
|
||||
export default Signup;
|
||||
47
imports/ui/pages/Signup/Signup.scss
Normal file
47
imports/ui/pages/Signup/Signup.scss
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
@import '../../stylesheets/mixins';
|
||||
@import '../../stylesheets/colors';
|
||||
|
||||
.Signup {
|
||||
.page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
position: relative;
|
||||
border-top: 1px solid $gray-lighter;
|
||||
margin-top: 15px;
|
||||
padding-top: 25px;
|
||||
|
||||
.SignupWithEmail {
|
||||
display: inline-block;
|
||||
background: #fff;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: 50%;
|
||||
margin-left: -106px;
|
||||
}
|
||||
|
||||
.HaveAnAccount {
|
||||
margin-top: 25px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid $gray-lighter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
.Signup {
|
||||
.page-header {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(desktop) {
|
||||
.Signup {
|
||||
.page-header {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
0
imports/ui/pages/ViewDocument/ViewDocument.js
Normal file
0
imports/ui/pages/ViewDocument/ViewDocument.js
Normal file
15
imports/ui/stylesheets/_colors.scss
Normal file
15
imports/ui/stylesheets/_colors.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
$primary: #337ab7;
|
||||
$success: #5cb85c;
|
||||
$info: #5bc0de;
|
||||
$warning: #f0ad4e;
|
||||
$danger: #d9534f;
|
||||
|
||||
$gray-darker: #222;
|
||||
$gray-dark: #333;
|
||||
$gray: #555;
|
||||
$gray-light: #777;
|
||||
$gray-lighter: #eee;
|
||||
|
||||
$facebook: #3b5998;
|
||||
$google: #ea4335;
|
||||
$github: $gray-dark;
|
||||
12
imports/ui/stylesheets/_forms.scss
Normal file
12
imports/ui/stylesheets/_forms.scss
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
@import './mixins';
|
||||
@import './colors';
|
||||
|
||||
form {
|
||||
label.error {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: $danger;
|
||||
}
|
||||
}
|
||||
23
imports/ui/stylesheets/_mixins.scss
Normal file
23
imports/ui/stylesheets/_mixins.scss
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
@mixin breakpoint($point) {
|
||||
@if $point == desktop-large {
|
||||
@media (min-width: 1200px) { @content; }
|
||||
} @else if $point == desktop {
|
||||
@media (min-width: 1024px) { @content; }
|
||||
} @else if $point == tablet {
|
||||
@media (min-width: 768px) { @content; }
|
||||
} @else if $point == handheld-large {
|
||||
@media (min-width: 480px) { @content; }
|
||||
} @else {
|
||||
@media (min-width: $point) { @content; }
|
||||
}
|
||||
}
|
||||
|
||||
@mixin ie {
|
||||
@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
@content;
|
||||
}
|
||||
|
||||
@supports (-ms-accelerator:true) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
2
imports/ui/stylesheets/app.scss
Normal file
2
imports/ui/stylesheets/app.scss
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
@import './colors';
|
||||
@import './forms';
|
||||
Loading…
Add table
Add a link
Reference in a new issue