wip Pup 1.0.0
This commit is contained in:
commit
cdc15f019d
48 changed files with 1100 additions and 0 deletions
3
.babelrc
Normal file
3
.babelrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["es2015", "react"]
|
||||
}
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
settings-staging.json
|
||||
settings-production.json
|
||||
16
.meteor/.finished-upgraders
Normal file
16
.meteor/.finished-upgraders
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# This file contains information which helps Meteor properly upgrade your
|
||||
# app when you run 'meteor update'. You should check it into version control
|
||||
# with your project.
|
||||
|
||||
notices-for-0.9.0
|
||||
notices-for-0.9.1
|
||||
0.9.4-platform-file
|
||||
notices-for-facebook-graph-api-2
|
||||
1.2.0-standard-minifiers-package
|
||||
1.2.0-meteor-platform-split
|
||||
1.2.0-cordova-changes
|
||||
1.2.0-breaking-changes
|
||||
1.3.0-split-minifiers-package
|
||||
1.4.0-remove-old-dev-bundle-link
|
||||
1.4.1-add-shell-server-package
|
||||
1.4.3-split-account-service-packages
|
||||
1
.meteor/.gitignore
vendored
Normal file
1
.meteor/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
local
|
||||
7
.meteor/.id
Normal file
7
.meteor/.id
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# This file contains a token that is unique to your project.
|
||||
# Check it into your repository along with the rest of this directory.
|
||||
# It can be used for purposes such as:
|
||||
# - ensuring you don't accidentally deploy one app on top of another
|
||||
# - providing package authors with aggregated statistics
|
||||
|
||||
16be20efyo0qb53r01o
|
||||
32
.meteor/packages
Normal file
32
.meteor/packages
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# Meteor packages used by this project, one per line.
|
||||
# Check this file (and the other files in this directory) into your repository.
|
||||
#
|
||||
# 'meteor add' and 'meteor remove' will edit this file for you,
|
||||
# but you can also edit it by hand.
|
||||
|
||||
meteor-base@1.0.4 # Packages every Meteor app needs to have
|
||||
mobile-experience@1.0.4 # Packages for a great mobile UX
|
||||
mongo@1.1.17 # The database Meteor supports right now
|
||||
blaze-html-templates@1.0.4 # Compile .html files into Meteor Blaze views
|
||||
reactive-var@1.0.11 # Reactive variable for tracker
|
||||
tracker@1.1.3 # Meteor's client-side reactive programming library
|
||||
|
||||
standard-minifier-css@1.3.4 # CSS minifier run for production mode
|
||||
standard-minifier-js@2.0.0 # JS minifier run for production mode
|
||||
es5-shim@4.6.15 # ECMAScript 5 compatibility for older browsers.
|
||||
ecmascript@0.7.3 # Enable ECMAScript2015+ syntax in app code
|
||||
shell-server@0.2.3 # Server-side component of the `meteor shell` command
|
||||
|
||||
react-meteor-data
|
||||
alanning:roles
|
||||
fourseven:scss
|
||||
twbs:bootstrap
|
||||
accounts-base
|
||||
accounts-password
|
||||
service-configuration
|
||||
accounts-facebook
|
||||
accounts-github
|
||||
accounts-google
|
||||
themeteorchef:bert
|
||||
fortawesome:fontawesome
|
||||
aldeed:collection2-core
|
||||
2
.meteor/platforms
Normal file
2
.meteor/platforms
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
server
|
||||
browser
|
||||
1
.meteor/release
Normal file
1
.meteor/release
Normal file
|
|
@ -0,0 +1 @@
|
|||
METEOR@1.4.4.2
|
||||
102
.meteor/versions
Normal file
102
.meteor/versions
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
accounts-base@1.2.17
|
||||
accounts-facebook@1.1.1
|
||||
accounts-github@1.2.1
|
||||
accounts-google@1.1.2
|
||||
accounts-oauth@1.1.15
|
||||
accounts-password@1.3.6
|
||||
alanning:roles@1.2.16
|
||||
aldeed:collection2-core@1.2.0
|
||||
aldeed:simple-schema@1.5.3
|
||||
allow-deny@1.0.5
|
||||
autoupdate@1.3.12
|
||||
babel-compiler@6.18.2
|
||||
babel-runtime@1.0.1
|
||||
base64@1.0.10
|
||||
binary-heap@1.0.10
|
||||
blaze@2.3.2
|
||||
blaze-html-templates@1.1.2
|
||||
blaze-tools@1.0.10
|
||||
boilerplate-generator@1.0.11
|
||||
caching-compiler@1.1.9
|
||||
caching-html-compiler@1.1.2
|
||||
callback-hook@1.0.10
|
||||
check@1.2.5
|
||||
ddp@1.2.5
|
||||
ddp-client@1.3.4
|
||||
ddp-common@1.2.8
|
||||
ddp-rate-limiter@1.0.7
|
||||
ddp-server@1.3.14
|
||||
deps@1.0.12
|
||||
diff-sequence@1.0.7
|
||||
ecmascript@0.7.3
|
||||
ecmascript-runtime@0.3.15
|
||||
ejson@1.0.13
|
||||
email@1.2.1
|
||||
es5-shim@4.6.15
|
||||
facebook-oauth@1.3.0
|
||||
fastclick@1.0.13
|
||||
fortawesome:fontawesome@4.4.0_1
|
||||
fourseven:scss@4.5.0
|
||||
geojson-utils@1.0.10
|
||||
github-oauth@1.2.0
|
||||
google-oauth@1.2.4
|
||||
hot-code-push@1.0.4
|
||||
html-tools@1.0.11
|
||||
htmljs@1.0.11
|
||||
http@1.2.12
|
||||
id-map@1.0.9
|
||||
jquery@1.11.10
|
||||
launch-screen@1.1.1
|
||||
livedata@1.0.18
|
||||
localstorage@1.0.12
|
||||
logging@1.1.17
|
||||
mdg:validation-error@0.2.0
|
||||
meteor@1.6.1
|
||||
meteor-base@1.0.4
|
||||
minifier-css@1.2.16
|
||||
minifier-js@2.0.0
|
||||
minimongo@1.0.23
|
||||
mobile-experience@1.0.4
|
||||
mobile-status-bar@1.0.14
|
||||
modules@0.8.2
|
||||
modules-runtime@0.7.10
|
||||
mongo@1.1.17
|
||||
mongo-id@1.0.6
|
||||
npm-bcrypt@0.9.2
|
||||
npm-mongo@2.2.24
|
||||
oauth@1.1.13
|
||||
oauth2@1.1.11
|
||||
observe-sequence@1.0.16
|
||||
ordered-dict@1.0.9
|
||||
promise@0.8.8
|
||||
raix:eventemitter@0.1.3
|
||||
random@1.0.10
|
||||
rate-limit@1.0.8
|
||||
react-meteor-data@0.2.11
|
||||
reactive-dict@1.1.8
|
||||
reactive-var@1.0.11
|
||||
reload@1.1.11
|
||||
retry@1.0.9
|
||||
routepolicy@1.0.12
|
||||
service-configuration@1.0.11
|
||||
session@1.1.7
|
||||
sha@1.0.9
|
||||
shell-server@0.2.3
|
||||
spacebars@1.0.15
|
||||
spacebars-compiler@1.1.2
|
||||
srp@1.0.10
|
||||
standard-minifier-css@1.3.4
|
||||
standard-minifier-js@2.0.0
|
||||
templating@1.3.2
|
||||
templating-compiler@1.3.2
|
||||
templating-runtime@1.3.2
|
||||
templating-tools@1.1.2
|
||||
themeteorchef:bert@2.1.2
|
||||
tmeasday:check-npm-versions@0.2.0
|
||||
tracker@1.1.3
|
||||
twbs:bootstrap@3.3.6
|
||||
ui@1.0.13
|
||||
underscore@1.0.10
|
||||
url@1.1.0
|
||||
webapp@1.3.15
|
||||
webapp-hashing@1.0.9
|
||||
13
client/main.html
Normal file
13
client/main.html
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Pup</title>
|
||||
<meta name="description" content="A description for the application.">
|
||||
<meta name="viewport" content="initial-scale=1, minimal-ui, maximum-scale=1, minimum-scale=1" />
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" />
|
||||
<link rel="shortcut icon" type="image/png" href="/favicon.png?v1" sizes="16x16 32x32 64x64">
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-precomposed.png">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="react-root"></div>
|
||||
</body>
|
||||
1
client/main.js
Normal file
1
client/main.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
import '../imports/startup/client';
|
||||
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';
|
||||
77
package.json
Normal file
77
package.json
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"name": "pup",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "meteor --settings settings-development.json",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-runtime": "^6.20.0",
|
||||
"jquery": "^2.2.4",
|
||||
"jquery-validation": "^1.16.0",
|
||||
"meteor-node-stubs": "~0.2.4",
|
||||
"moment": "^2.18.1",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^15.5.4",
|
||||
"react-addons-pure-render-mixin": "^15.5.2",
|
||||
"react-bootstrap": "^0.31.0",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-router-bootstrap": "^0.24.2",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"simpl-schema": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "^20.0.3",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"enzyme": "^2.8.2",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-config-airbnb": "^15.0.1",
|
||||
"eslint-plugin-import": "^2.2.0",
|
||||
"eslint-plugin-jsx-a11y": "^5.0.3",
|
||||
"eslint-plugin-meteor": "^4.0.1",
|
||||
"eslint-plugin-react": "^7.0.1",
|
||||
"jest": "^20.0.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"plugins": [
|
||||
"meteor",
|
||||
"react"
|
||||
],
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:meteor/recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"globals": {
|
||||
"expect": false
|
||||
},
|
||||
"rules": {
|
||||
"import/no-unresolved": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"import/extensions": 0,
|
||||
"no-underscore-dangle": [
|
||||
"error",
|
||||
{
|
||||
"allow": [
|
||||
"_id",
|
||||
"_ensureIndex",
|
||||
"_verifyEmailToken",
|
||||
"_resetPasswordToken",
|
||||
"_name"
|
||||
]
|
||||
}
|
||||
],
|
||||
"class-methods-use-this": 0,
|
||||
"react/jsx-filename-extension": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
5
server/main.js
Normal file
5
server/main.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
Meteor.startup(() => {
|
||||
// code to run on server at startup
|
||||
});
|
||||
19
settings-development.json
Normal file
19
settings-development.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"public": {},
|
||||
"private": {
|
||||
"OAuth": {
|
||||
"facebook": {
|
||||
"clientId": "",
|
||||
"secret": ""
|
||||
},
|
||||
"google": {
|
||||
"clientId": "",
|
||||
"secret": ""
|
||||
},
|
||||
"github": {
|
||||
"clientId": "",
|
||||
"secret": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue