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 = () => ( +
+ + Clever Beagle +

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

+ + + + + + + + + +
(this.form = form)} onSubmit={event => event.preventDefault()}> +

Log In with an Email Address

+ + Email Address + (this.emailAddress = emailAddress)} + className="form-control" + /> + + + Password + (this.password = password)} + className="form-control" + /> + + +

+ {'Don\'t have an account?'} Sign Up. +

+
+ +
+
); + } +} + +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

+ + + + + + + + + +
(this.form = form)} onSubmit={event => event.preventDefault()}> +

Sign Up with an Email Address

+ + + + First Name + (this.firstName = firstName)} + className="form-control" + /> + + + + + Last Name + (this.lastName = lastName)} + className="form-control" + /> + + + + + Email Address + (this.emailAddress = emailAddress)} + className="form-control" + /> + + + Password + (this.password = password)} + className="form-control" + /> + Use at least six characters. + + +

+ Already have an account? Log In. +

+
+ +
+
); + } +} + +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": "" + } + } + } +}