diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..86c445f
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015", "react"]
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7b07eb5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+settings-staging.json
+settings-production.json
diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders
new file mode 100644
index 0000000..ce276f3
--- /dev/null
+++ b/.meteor/.finished-upgraders
@@ -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
diff --git a/.meteor/.gitignore b/.meteor/.gitignore
new file mode 100644
index 0000000..4083037
--- /dev/null
+++ b/.meteor/.gitignore
@@ -0,0 +1 @@
+local
diff --git a/.meteor/.id b/.meteor/.id
new file mode 100644
index 0000000..9c23f12
--- /dev/null
+++ b/.meteor/.id
@@ -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
diff --git a/.meteor/packages b/.meteor/packages
new file mode 100644
index 0000000..388cfde
--- /dev/null
+++ b/.meteor/packages
@@ -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
diff --git a/.meteor/platforms b/.meteor/platforms
new file mode 100644
index 0000000..efeba1b
--- /dev/null
+++ b/.meteor/platforms
@@ -0,0 +1,2 @@
+server
+browser
diff --git a/.meteor/release b/.meteor/release
new file mode 100644
index 0000000..fb6f3bc
--- /dev/null
+++ b/.meteor/release
@@ -0,0 +1 @@
+METEOR@1.4.4.2
diff --git a/.meteor/versions b/.meteor/versions
new file mode 100644
index 0000000..e2110b2
--- /dev/null
+++ b/.meteor/versions
@@ -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
diff --git a/client/main.html b/client/main.html
new file mode 100644
index 0000000..db2b85f
--- /dev/null
+++ b/client/main.html
@@ -0,0 +1,13 @@
+
+
+ Pup
+
+
+
+
+
+
+
+
+
+
diff --git a/client/main.js b/client/main.js
new file mode 100644
index 0000000..1412f30
--- /dev/null
+++ b/client/main.js
@@ -0,0 +1 @@
+import '../imports/startup/client';
diff --git a/imports/api/Documents/Documents.js b/imports/api/Documents/Documents.js
new file mode 100644
index 0000000..4974aab
--- /dev/null
+++ b/imports/api/Documents/Documents.js
@@ -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;
diff --git a/imports/modules/validate.js b/imports/modules/validate.js
new file mode 100644
index 0000000..33f6175
--- /dev/null
+++ b/imports/modules/validate.js
@@ -0,0 +1,4 @@
+import $ from 'jquery';
+import 'jquery-validation';
+
+export default (form, options) => $(form).validate(options);
diff --git a/imports/startup/client/index.js b/imports/startup/client/index.js
new file mode 100644
index 0000000..ccebfda
--- /dev/null
+++ b/imports/startup/client/index.js
@@ -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(, document.getElementById('react-root')));
diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/components/Authenticated/Authenticated.js b/imports/ui/components/Authenticated/Authenticated.js
new file mode 100644
index 0000000..442d0eb
--- /dev/null
+++ b/imports/ui/components/Authenticated/Authenticated.js
@@ -0,0 +1,22 @@
+import React, { PropTypes } from 'react';
+import { Route, Redirect } from 'react-router-dom';
+
+const Authenticated = ({ loggingIn, authenticated, component, ...rest }) => (
+ {
+ if (loggingIn) return ;
+ return authenticated ?
+ (React.createElement(component, { ...props, loggingIn, authenticated })) :
+ ();
+ }}
+ />
+);
+
+Authenticated.propTypes = {
+ loggingIn: PropTypes.bool.isRequired,
+ authenticated: PropTypes.bool.isRequired,
+ component: PropTypes.func.isRequired,
+};
+
+export default Authenticated;
diff --git a/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js b/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js
new file mode 100644
index 0000000..d8518b8
--- /dev/null
+++ b/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js
@@ -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 }) => (
+
+
+
+
+);
+
+AuthenticatedNavigation.propTypes = {
+ name: PropTypes.string.isRequired,
+};
+
+export default AuthenticatedNavigation;
diff --git a/imports/ui/components/InputHint/InputHint.js b/imports/ui/components/InputHint/InputHint.js
new file mode 100644
index 0000000..6342880
--- /dev/null
+++ b/imports/ui/components/InputHint/InputHint.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import './InputHint.scss';
+
+const InputHint = ({ children }) => (
+
+ {children}
+
+);
+
+InputHint.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export default InputHint;
diff --git a/imports/ui/components/InputHint/InputHint.scss b/imports/ui/components/InputHint/InputHint.scss
new file mode 100644
index 0000000..521d272
--- /dev/null
+++ b/imports/ui/components/InputHint/InputHint.scss
@@ -0,0 +1,9 @@
+@import '../../stylesheets/colors';
+
+.InputHint {
+ display: block;
+ margin-top: 8px;
+ font-style: italic;
+ color: $gray-light;
+ font-size: 13px;
+}
diff --git a/imports/ui/components/Navigation/Navigation.js b/imports/ui/components/Navigation/Navigation.js
new file mode 100644
index 0000000..2993b39
--- /dev/null
+++ b/imports/ui/components/Navigation/Navigation.js
@@ -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 }) => (
+
+
+
+ Pup
+
+
+
+
+ {!authenticated ? : }
+
+
+);
+
+Navigation.defaultProps = {
+ name: PropTypes.string,
+};
+
+Navigation.propTypes = {
+ authenticated: PropTypes.bool.isRequired,
+ name: PropTypes.string,
+};
+
+export default Navigation;
diff --git a/imports/ui/components/Navigation/Navigation.scss b/imports/ui/components/Navigation/Navigation.scss
new file mode 100644
index 0000000..0734946
--- /dev/null
+++ b/imports/ui/components/Navigation/Navigation.scss
@@ -0,0 +1,6 @@
+.navbar {
+ border-radius: 0;
+ border-left: none;
+ border-right: none;
+ border-top: none;
+}
diff --git a/imports/ui/components/OAuthLoginButton/OAuthLoginButton.js b/imports/ui/components/OAuthLoginButton/OAuthLoginButton.js
new file mode 100644
index 0000000..5e795d0
--- /dev/null
+++ b/imports/ui/components/OAuthLoginButton/OAuthLoginButton.js
@@ -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: Log In with Facebook,
+ google: Log In with Google,
+ github: Log In with GitHub,
+};
+
+const OAuthLoginButton = ({ service, options, callback }) => (
+
+);
+
+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;
diff --git a/imports/ui/components/OAuthLoginButton/OAuthLoginButton.scss b/imports/ui/components/OAuthLoginButton/OAuthLoginButton.scss
new file mode 100644
index 0000000..59051a5
--- /dev/null
+++ b/imports/ui/components/OAuthLoginButton/OAuthLoginButton.scss
@@ -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;
+}
diff --git a/imports/ui/components/Public/Public.js b/imports/ui/components/Public/Public.js
new file mode 100644
index 0000000..250575b
--- /dev/null
+++ b/imports/ui/components/Public/Public.js
@@ -0,0 +1,22 @@
+import React, { PropTypes } from 'react';
+import { Route, Redirect } from 'react-router-dom';
+
+const Public = ({ loggingIn, authenticated, component, ...rest }) => (
+ {
+ if (loggingIn) return ;
+ return !authenticated ?
+ (React.createElement(component, { ...props, loggingIn, authenticated })) :
+ ();
+ }}
+ />
+);
+
+Public.propTypes = {
+ loggingIn: PropTypes.bool.isRequired,
+ authenticated: PropTypes.bool.isRequired,
+ component: PropTypes.func.isRequired,
+};
+
+export default Public;
diff --git a/imports/ui/components/PublicNavigation/PublicNavigation.js b/imports/ui/components/PublicNavigation/PublicNavigation.js
new file mode 100644
index 0000000..b254b86
--- /dev/null
+++ b/imports/ui/components/PublicNavigation/PublicNavigation.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import { LinkContainer } from 'react-router-bootstrap';
+import { Nav, NavItem } from 'react-bootstrap';
+
+const PublicNavigation = () => (
+
+);
+
+export default PublicNavigation;
diff --git a/imports/ui/layouts/App/App.js b/imports/ui/layouts/App/App.js
new file mode 100644
index 0000000..acc05d3
--- /dev/null
+++ b/imports/ui/layouts/App/App.js
@@ -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 => (
+
+ {!props.loading ?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
: ''}
+
+);
+
+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);
diff --git a/imports/ui/layouts/App/App.test.js b/imports/ui/layouts/App/App.test.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/Documents/Documents.js b/imports/ui/pages/Documents/Documents.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/EditDocument/EditDocument.js b/imports/ui/pages/EditDocument/EditDocument.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/Index/Index.js b/imports/ui/pages/Index/Index.js
new file mode 100644
index 0000000..212b840
--- /dev/null
+++ b/imports/ui/pages/Index/Index.js
@@ -0,0 +1,18 @@
+import React from 'react';
+import { Jumbotron } from 'react-bootstrap';
+
+import './Index.scss';
+
+const Index = () => (
+
+
+
+ Need help building your app? Check out our mentorship service.
+
+
+);
+
+export default Index;
diff --git a/imports/ui/pages/Index/Index.scss b/imports/ui/pages/Index/Index.scss
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/Login/Login.js b/imports/ui/pages/Login/Login.js
new file mode 100644
index 0000000..ba8624d
--- /dev/null
+++ b/imports/ui/pages/Login/Login.js
@@ -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 (
+
+
+ Log In
+
+
+
+
+
+
+
+
+
+
+
+
+
);
+ }
+}
+
+Login.propTypes = {
+ history: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+};
+
+export default Login;
diff --git a/imports/ui/pages/Login/Login.scss b/imports/ui/pages/Login/Login.scss
new file mode 100644
index 0000000..4f1a1ba
--- /dev/null
+++ b/imports/ui/pages/Login/Login.scss
@@ -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;
+ }
+ }
+}
diff --git a/imports/ui/pages/NewDocument/NewDocument.js b/imports/ui/pages/NewDocument/NewDocument.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/NotFound/NotFound.js b/imports/ui/pages/NotFound/NotFound.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/Profile/Profile.js b/imports/ui/pages/Profile/Profile.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/RecoverPassword/RecoverPassword.js b/imports/ui/pages/RecoverPassword/RecoverPassword.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/ResetPassword/ResetPassword.js b/imports/ui/pages/ResetPassword/ResetPassword.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/pages/Signup/Signup.js b/imports/ui/pages/Signup/Signup.js
new file mode 100644
index 0000000..3cf2440
--- /dev/null
+++ b/imports/ui/pages/Signup/Signup.js
@@ -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 (
+
+
+ Sign Up
+
+
+
+
+
+
+
+
+
+
+
+
+
);
+ }
+}
+
+Signup.propTypes = {
+ history: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
+};
+
+export default Signup;
diff --git a/imports/ui/pages/Signup/Signup.scss b/imports/ui/pages/Signup/Signup.scss
new file mode 100644
index 0000000..db42ca7
--- /dev/null
+++ b/imports/ui/pages/Signup/Signup.scss
@@ -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;
+ }
+ }
+}
diff --git a/imports/ui/pages/ViewDocument/ViewDocument.js b/imports/ui/pages/ViewDocument/ViewDocument.js
new file mode 100644
index 0000000..e69de29
diff --git a/imports/ui/stylesheets/_colors.scss b/imports/ui/stylesheets/_colors.scss
new file mode 100644
index 0000000..5914a7e
--- /dev/null
+++ b/imports/ui/stylesheets/_colors.scss
@@ -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;
diff --git a/imports/ui/stylesheets/_forms.scss b/imports/ui/stylesheets/_forms.scss
new file mode 100644
index 0000000..3753e33
--- /dev/null
+++ b/imports/ui/stylesheets/_forms.scss
@@ -0,0 +1,12 @@
+@import './mixins';
+@import './colors';
+
+form {
+ label.error {
+ display: block;
+ margin-top: 8px;
+ font-size: 13px;
+ font-weight: normal;
+ color: $danger;
+ }
+}
diff --git a/imports/ui/stylesheets/_mixins.scss b/imports/ui/stylesheets/_mixins.scss
new file mode 100644
index 0000000..01d5602
--- /dev/null
+++ b/imports/ui/stylesheets/_mixins.scss
@@ -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;
+ }
+}
diff --git a/imports/ui/stylesheets/app.scss b/imports/ui/stylesheets/app.scss
new file mode 100644
index 0000000..03af845
--- /dev/null
+++ b/imports/ui/stylesheets/app.scss
@@ -0,0 +1,2 @@
+@import './colors';
+@import './forms';
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..99d77b3
--- /dev/null
+++ b/package.json
@@ -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
+ }
+ }
+}
diff --git a/server/main.js b/server/main.js
new file mode 100644
index 0000000..31a9e0e
--- /dev/null
+++ b/server/main.js
@@ -0,0 +1,5 @@
+import { Meteor } from 'meteor/meteor';
+
+Meteor.startup(() => {
+ // code to run on server at startup
+});
diff --git a/settings-development.json b/settings-development.json
new file mode 100644
index 0000000..2d824da
--- /dev/null
+++ b/settings-development.json
@@ -0,0 +1,19 @@
+{
+ "public": {},
+ "private": {
+ "OAuth": {
+ "facebook": {
+ "clientId": "",
+ "secret": ""
+ },
+ "google": {
+ "clientId": "",
+ "secret": ""
+ },
+ "github": {
+ "clientId": "",
+ "secret": ""
+ }
+ }
+ }
+}