add oauth flows, profile, logout page, and index page
This commit is contained in:
parent
650c93273a
commit
b0270cc98b
31 changed files with 449 additions and 162 deletions
|
|
@ -21,14 +21,14 @@ react-meteor-data
|
|||
alanning:roles
|
||||
fourseven:scss
|
||||
twbs:bootstrap
|
||||
accounts-base
|
||||
accounts-password
|
||||
service-configuration
|
||||
accounts-facebook
|
||||
accounts-github
|
||||
accounts-google
|
||||
accounts-base@1.2.17
|
||||
accounts-password@1.3.6
|
||||
service-configuration@1.0.11
|
||||
accounts-facebook@1.1.1
|
||||
accounts-github@1.2.1
|
||||
accounts-google@1.1.2
|
||||
themeteorchef:bert
|
||||
fortawesome:fontawesome
|
||||
aldeed:collection2-core
|
||||
audit-argument-checks
|
||||
ddp-rate-limiter
|
||||
aldeed:collection2-core@2.0.0
|
||||
audit-argument-checks@1.0.7
|
||||
ddp-rate-limiter@1.0.7
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
METEOR@1.4.4.2
|
||||
METEOR@1.4.4.3
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ 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
|
||||
aldeed:collection2-core@2.0.1
|
||||
allow-deny@1.0.5
|
||||
audit-argument-checks@1.0.7
|
||||
autoupdate@1.3.12
|
||||
|
|
@ -36,7 +35,7 @@ email@1.2.1
|
|||
es5-shim@4.6.15
|
||||
facebook-oauth@1.3.0
|
||||
fastclick@1.0.13
|
||||
fortawesome:fontawesome@4.4.0_1
|
||||
fortawesome:fontawesome@4.7.0
|
||||
fourseven:scss@4.5.0
|
||||
geojson-utils@1.0.10
|
||||
github-oauth@1.2.0
|
||||
|
|
@ -51,7 +50,6 @@ 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
|
||||
|
|
@ -93,7 +91,7 @@ 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
|
||||
tmeasday:check-npm-versions@0.3.1
|
||||
tracker@1.1.3
|
||||
twbs:bootstrap@3.3.6
|
||||
ui@1.0.13
|
||||
|
|
|
|||
|
|
@ -46,6 +46,6 @@ Documents.schema = new SimpleSchema({
|
|||
},
|
||||
});
|
||||
|
||||
// Documents.attachSchema(Documents.schema);
|
||||
Documents.attachSchema(Documents.schema);
|
||||
|
||||
export default Documents;
|
||||
|
|
|
|||
14
imports/api/OAuth/server/methods.js
Normal file
14
imports/api/OAuth/server/methods.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { ServiceConfiguration } from 'meteor/service-configuration';
|
||||
|
||||
Meteor.methods({
|
||||
'oauth.verifyConfiguration': function oauthVerifyConfiguration(services) {
|
||||
check(services, Array);
|
||||
const verifiedServices = [];
|
||||
services.forEach((service) => {
|
||||
if (ServiceConfiguration.configurations.findOne({ service })) verifiedServices.push(service);
|
||||
});
|
||||
return verifiedServices.sort();
|
||||
},
|
||||
});
|
||||
|
|
@ -5,6 +5,7 @@ Meteor.publish('users.editProfile', function usersProfile() {
|
|||
fields: {
|
||||
emails: 1,
|
||||
profile: 1,
|
||||
services: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
2
imports/startup/server/accounts/index.js
Normal file
2
imports/startup/server/accounts/index.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
import './oauth';
|
||||
import './email-templates';
|
||||
13
imports/startup/server/accounts/oauth.js
Normal file
13
imports/startup/server/accounts/oauth.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import { ServiceConfiguration } from 'meteor/service-configuration';
|
||||
|
||||
const OAuthSettings = Meteor.settings.private.OAuth;
|
||||
|
||||
if (OAuthSettings) {
|
||||
Object.keys(OAuthSettings).forEach((service) => {
|
||||
ServiceConfiguration.configurations.upsert(
|
||||
{ service },
|
||||
{ $set: OAuthSettings[service] },
|
||||
);
|
||||
});
|
||||
}
|
||||
7
imports/startup/server/accounts/on-create-user.js
Normal file
7
imports/startup/server/accounts/on-create-user.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { Accounts } from 'meteor/accounts-base';
|
||||
|
||||
Accounts.onCreateUser((options, user) => {
|
||||
const userToCreate = user;
|
||||
if (options.profile) userToCreate.profile = options.profile;
|
||||
return userToCreate;
|
||||
});
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import '../../api/Documents/methods';
|
||||
import '../../api/Documents/server/publications';
|
||||
|
||||
import '../../api/OAuth/server/methods';
|
||||
|
||||
import '../../api/Users/server/methods';
|
||||
import '../../api/Users/server/publications';
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const Authenticated = ({ loggingIn, authenticated, component, ...rest }) => (
|
|||
if (loggingIn) return <div />;
|
||||
return authenticated ?
|
||||
(React.createElement(component, { ...props, loggingIn, authenticated })) :
|
||||
(<Redirect to="/login" />);
|
||||
(<Redirect to="/logout" />);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class DocumentEditor extends React.Component {
|
|||
}
|
||||
|
||||
DocumentEditor.defaultProps = {
|
||||
doc: PropTypes.object,
|
||||
doc: { title: '', body: '' },
|
||||
};
|
||||
|
||||
DocumentEditor.propTypes = {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import AuthenticatedNavigation from '../AuthenticatedNavigation/AuthenticatedNav
|
|||
|
||||
import './Navigation.scss';
|
||||
|
||||
const Navigation = ({ authenticated, name }) => (
|
||||
const Navigation = props => (
|
||||
<Navbar>
|
||||
<Navbar.Header>
|
||||
<Navbar.Brand>
|
||||
|
|
@ -16,18 +16,17 @@ const Navigation = ({ authenticated, name }) => (
|
|||
<Navbar.Toggle />
|
||||
</Navbar.Header>
|
||||
<Navbar.Collapse>
|
||||
{!authenticated ? <PublicNavigation /> : <AuthenticatedNavigation name={name} />}
|
||||
{!props.authenticated ? <PublicNavigation /> : <AuthenticatedNavigation {...props} />}
|
||||
</Navbar.Collapse>
|
||||
</Navbar>
|
||||
);
|
||||
|
||||
Navigation.defaultProps = {
|
||||
name: PropTypes.string,
|
||||
name: '',
|
||||
};
|
||||
|
||||
Navigation.propTypes = {
|
||||
authenticated: PropTypes.bool.isRequired,
|
||||
name: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
|
|
|
|||
|
|
@ -5,58 +5,54 @@ import { Bert } from 'meteor/themeteorchef:bert';
|
|||
|
||||
import './OAuthLoginButton.scss';
|
||||
|
||||
const handleLogin = (service, options, callback) => {
|
||||
const defaultOptions = {
|
||||
const handleLogin = (service, callback) => {
|
||||
const options = {
|
||||
facebook: {
|
||||
requestPermissions: ['email'],
|
||||
loginStyle: 'redirect',
|
||||
loginStyle: 'popup',
|
||||
},
|
||||
github: {
|
||||
requestPermissions: ['user:email'],
|
||||
loginStyle: 'popup',
|
||||
},
|
||||
google: {
|
||||
requestPermissions: ['email'],
|
||||
requestOfflineToken: true,
|
||||
loginStyle: 'redirect',
|
||||
},
|
||||
github: {
|
||||
requestPermissions: ['user:email'],
|
||||
loginStyle: 'redirect',
|
||||
loginStyle: 'popup',
|
||||
},
|
||||
}[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);
|
||||
google: Meteor.loginWithGoogle,
|
||||
}[service](options, callback);
|
||||
};
|
||||
|
||||
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>,
|
||||
google: <span><i className="fa fa-google" /> Log In with Google</span>,
|
||||
};
|
||||
|
||||
const OAuthLoginButton = ({ service, options, callback }) => (
|
||||
const OAuthLoginButton = ({ service, callback }) => (
|
||||
<button
|
||||
className={`OAuthLoginButton OAuthLoginButton-${service}`}
|
||||
type="button"
|
||||
onClick={() => handleLogin(service, options, callback)}
|
||||
onClick={() => handleLogin(service, callback)}
|
||||
>
|
||||
{serviceLabel[service]}
|
||||
</button>
|
||||
);
|
||||
|
||||
OAuthLoginButton.defaultProps = {
|
||||
options: PropTypes.object,
|
||||
callback: PropTypes.func,
|
||||
callback: (error) => {
|
||||
if (error) Bert.alert(error.message, 'danger');
|
||||
},
|
||||
};
|
||||
|
||||
OAuthLoginButton.propTypes = {
|
||||
service: PropTypes.string.isRequired,
|
||||
options: PropTypes.object,
|
||||
callback: PropTypes.func,
|
||||
};
|
||||
|
||||
|
|
|
|||
42
imports/ui/components/OAuthLoginButtons/OAuthLoginButtons.js
Normal file
42
imports/ui/components/OAuthLoginButtons/OAuthLoginButtons.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { createContainer } from 'meteor/react-meteor-data';
|
||||
import { ReactiveVar } from 'meteor/reactive-var';
|
||||
import OAuthLoginButton from '../OAuthLoginButton/OAuthLoginButton';
|
||||
|
||||
import './OAuthLoginButtons.scss';
|
||||
|
||||
const OAuthLoginButtons = ({ services, emailMessage }) => (services.length ? (
|
||||
<div className={`OAuthLoginButtons ${emailMessage ? 'WithEmailMessage' : ''}`}>
|
||||
{services.map(service => <OAuthLoginButton key={service} service={service} />)}
|
||||
{emailMessage ? <p className="EmailMessage" style={{ marginLeft: `-${emailMessage.offset}px` }}>
|
||||
{emailMessage.text}
|
||||
</p> : ''}
|
||||
</div>
|
||||
) : <div />);
|
||||
|
||||
OAuthLoginButtons.propTypes = {
|
||||
services: PropTypes.array.isRequired,
|
||||
emailMessage: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
const verificationComplete = new ReactiveVar(false);
|
||||
const verifiedServices = new ReactiveVar([]);
|
||||
|
||||
export default createContainer(({ services }) => {
|
||||
if (!verificationComplete.get()) {
|
||||
Meteor.call('oauth.verifyConfiguration', services, (error, response) => {
|
||||
if (error) {
|
||||
console.warn(error);
|
||||
} else {
|
||||
verifiedServices.set(response);
|
||||
verificationComplete.set(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
services: verifiedServices.get(),
|
||||
};
|
||||
}, OAuthLoginButtons);
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
@import '../../stylesheets/colors';
|
||||
|
||||
.Signup {
|
||||
form {
|
||||
position: relative;
|
||||
border-top: 1px solid $gray-lighter;
|
||||
margin-top: 15px;
|
||||
padding-top: 25px;
|
||||
.OAuthLoginButtons {
|
||||
margin-bottom: 25px;
|
||||
|
||||
.SignupWithEmail {
|
||||
&.WithEmailMessage {
|
||||
position: relative;
|
||||
border-bottom: 1px solid $gray-lighter;
|
||||
padding-bottom: 30px;
|
||||
|
||||
.EmailMessage {
|
||||
display: inline-block;
|
||||
background: #fff;
|
||||
padding: 0 10px;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
bottom: -19px;
|
||||
left: 50%;
|
||||
margin-left: -106px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ 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 Logout from '../../pages/Logout/Logout';
|
||||
import RecoverPassword from '../../pages/RecoverPassword/RecoverPassword';
|
||||
import ResetPassword from '../../pages/ResetPassword/ResetPassword';
|
||||
import Profile from '../../pages/Profile/Profile';
|
||||
|
|
@ -34,6 +35,7 @@ const App = props => (
|
|||
<Authenticated exact path="/profile" component={Profile} {...props} />
|
||||
<Public path="/signup" component={Signup} {...props} />
|
||||
<Public path="/login" component={Login} {...props} />
|
||||
<Public path="/logout" component={Logout} {...props} />
|
||||
<Route name="recover-password" path="/recover-password" component={RecoverPassword} />
|
||||
<Route name="reset-password" path="/reset-password/:token" component={ResetPassword} />
|
||||
<Route component={NotFound} />
|
||||
|
|
@ -47,17 +49,24 @@ App.propTypes = {
|
|||
loading: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const getUserName = name => ({
|
||||
string: name,
|
||||
object: `${name.first} ${name.last}`,
|
||||
}[typeof name]);
|
||||
|
||||
export default createContainer(() => {
|
||||
const loggingIn = Meteor.loggingIn();
|
||||
const user = Meteor.user();
|
||||
const userId = Meteor.userId();
|
||||
const loading = !Roles.subscription.ready();
|
||||
const name = user && user.profile && user.profile.name && getUserName(user.profile.name);
|
||||
const emailAddress = user && user.emails && user.emails[0].address;
|
||||
|
||||
return {
|
||||
loading,
|
||||
loggingIn,
|
||||
authenticated: !loggingIn && !!userId,
|
||||
name: user && user.profile ? `${user.profile.name.first} ${user.profile.name.last}` : '',
|
||||
name: name || emailAddress,
|
||||
roles: !loading && Roles.getRolesForUser(userId),
|
||||
};
|
||||
}, App);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ Documents.propTypes = {
|
|||
|
||||
export default createContainer(() => {
|
||||
const subscription = Meteor.subscribe('documents');
|
||||
console.log(DocumentsCollection.find().fetch());
|
||||
return {
|
||||
loading: !subscription.ready(),
|
||||
documents: DocumentsCollection.find().fetch(),
|
||||
|
|
|
|||
|
|
@ -1,17 +1,20 @@
|
|||
import React from 'react';
|
||||
import { Jumbotron } from 'react-bootstrap';
|
||||
import { Button } 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>
|
||||
<img
|
||||
src="https://s3-us-west-2.amazonaws.com/cleverbeagle-assets/graphics/email-icon.png"
|
||||
alt="Clever Beagle"
|
||||
/>
|
||||
<h1>Pup</h1>
|
||||
<p>A boilerplate for products.</p>
|
||||
<div>
|
||||
<Button href="https://pup.cleverbeagle.com">Read the Docs</Button>
|
||||
<Button href="https://github.com/cleverbeagle/pup"><i className="fa fa-star" /> Star on GitHub</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
@import '../../stylesheets/mixins';
|
||||
@import '../../stylesheets/colors';
|
||||
|
||||
.Index {
|
||||
padding: 20px;
|
||||
background: $cb-blue;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
color: lighten($cb-blue, 25%);
|
||||
}
|
||||
|
||||
> div {
|
||||
display: inline-block;
|
||||
margin: 10px 0 0;
|
||||
|
||||
.btn:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn:last-child {
|
||||
background: darken($cb-blue, 20%);
|
||||
color: lighten($cb-blue, 30%);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
.Index {
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(desktop) {
|
||||
.Index {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,10 @@ 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 OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons';
|
||||
import AccountPageFooter from '../../components/AccountPageFooter/AccountPageFooter';
|
||||
import validate from '../../../modules/validate';
|
||||
|
||||
import './Login.scss';
|
||||
|
||||
class Login extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -62,15 +60,16 @@ class Login extends React.Component {
|
|||
<h4 className="page-header">Log In</h4>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<FormGroup className="OAuthLoginButtons">
|
||||
<OAuthLoginButton service="facebook" />
|
||||
<OAuthLoginButton service="google" />
|
||||
<OAuthLoginButton service="github" />
|
||||
</FormGroup>
|
||||
<OAuthLoginButtons
|
||||
services={['facebook', 'github', 'google']}
|
||||
emailMessage={{
|
||||
offset: 100,
|
||||
text: 'Log In with an Email Address',
|
||||
}}
|
||||
/>
|
||||
</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
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
@import '../../stylesheets/mixins';
|
||||
@import '../../stylesheets/colors';
|
||||
|
||||
.Login {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
imports/ui/pages/Logout/Logout.js
Normal file
23
imports/ui/pages/Logout/Logout.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
|
||||
import './Logout.scss';
|
||||
|
||||
const Logout = () => (
|
||||
<div className="Logout">
|
||||
<img
|
||||
src="https://s3-us-west-2.amazonaws.com/cleverbeagle-assets/graphics/email-icon.png"
|
||||
alt="Clever Beagle"
|
||||
/>
|
||||
<h1>Stay safe out there.</h1>
|
||||
<p>{'Don\'t forget to like and follow Clever Beagle elsewhere on the web:'}</p>
|
||||
<ul className="FollowUsElsewhere">
|
||||
<li><a href="https://facebook.com/cleverbeagle"><i className="fa fa-facebook-official" /></a></li>
|
||||
<li><a href="https://twitter.com/clvrbgl"><i className="fa fa-twitter" /></a></li>
|
||||
<li><a href="https://github.com/cleverbeagle"><i className="fa fa-github" /></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
|
||||
Logout.propTypes = {};
|
||||
|
||||
export default Logout;
|
||||
57
imports/ui/pages/Logout/Logout.scss
Normal file
57
imports/ui/pages/Logout/Logout.scss
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
@import '../../stylesheets/mixins';
|
||||
@import '../../stylesheets/colors';
|
||||
|
||||
.Logout {
|
||||
padding: 20px;
|
||||
background: $cb-blue;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
|
||||
img {
|
||||
width: 100px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 18px;
|
||||
color: lighten($cb-blue, 25%);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
padding: 0;
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
ul li {
|
||||
float: left;
|
||||
font-size: 28px;
|
||||
line-height: 28px;
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
.Logout {
|
||||
padding: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@include breakpoint(desktop) {
|
||||
.Logout {
|
||||
padding: 40px;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Row, Col, FormGroup, ControlLabel, Button } from 'react-bootstrap';
|
||||
import { capitalize } from '@cleverbeagle/strings';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { Accounts } from 'meteor/accounts-base';
|
||||
import { Bert } from 'meteor/themeteorchef:bert';
|
||||
|
|
@ -10,10 +11,17 @@ import { createContainer } from 'meteor/react-meteor-data';
|
|||
import InputHint from '../../components/InputHint/InputHint';
|
||||
import validate from '../../../modules/validate';
|
||||
|
||||
import './Profile.scss';
|
||||
|
||||
class Profile extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.getUserType = this.getUserType.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
this.renderOAuthUser = this.renderOAuthUser.bind(this);
|
||||
this.renderPasswordUser = this.renderPasswordUser.bind(this);
|
||||
this.renderProfileForm = this.renderProfileForm.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -66,6 +74,13 @@ class Profile extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
getUserType(user) {
|
||||
const userToCheck = user;
|
||||
delete userToCheck.services.resume;
|
||||
const service = Object.keys(userToCheck.services)[0];
|
||||
return service === 'password' ? 'password' : 'oauth';
|
||||
}
|
||||
|
||||
handleSubmit() {
|
||||
const profile = {
|
||||
emailAddress: this.emailAddress.value,
|
||||
|
|
@ -88,6 +103,85 @@ class Profile extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
renderOAuthUser(loading, user) {
|
||||
return !loading ? (<div className="OAuthProfile">
|
||||
{Object.keys(user.services).map(service => (
|
||||
<div key={service} className={`LoggedInWith ${service}`}>
|
||||
<div className="ServiceIcon"><i className={`fa fa-${service === 'facebook' ? 'facebook-official' : service}`} /></div>
|
||||
<p>{`You're logged in with ${capitalize(service)} using the email address ${user.services[service].email}.`}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>) : <div />;
|
||||
}
|
||||
|
||||
renderPasswordUser(loading, user) {
|
||||
return !loading ? (<div>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<ControlLabel>First Name</ControlLabel>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
defaultValue={user.profile.name.first}
|
||||
ref={firstName => (this.firstName = firstName)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<ControlLabel>Last Name</ControlLabel>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
defaultValue={user.profile.name.last}
|
||||
ref={lastName => (this.lastName = lastName)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<FormGroup>
|
||||
<ControlLabel>Email Address</ControlLabel>
|
||||
<input
|
||||
type="email"
|
||||
name="emailAddress"
|
||||
defaultValue={user.emails[0].address}
|
||||
ref={emailAddress => (this.emailAddress = emailAddress)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ControlLabel>Current Password</ControlLabel>
|
||||
<input
|
||||
type="password"
|
||||
name="currentPassword"
|
||||
ref={currentPassword => (this.currentPassword = currentPassword)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ControlLabel>New Password</ControlLabel>
|
||||
<input
|
||||
type="password"
|
||||
name="newPassword"
|
||||
ref={newPassword => (this.newPassword = newPassword)}
|
||||
className="form-control"
|
||||
/>
|
||||
<InputHint>Use at least six characters.</InputHint>
|
||||
</FormGroup>
|
||||
<Button type="submit" bsStyle="success">Save Profile</Button>
|
||||
</div>) : <div />;
|
||||
}
|
||||
|
||||
renderProfileForm(loading, user) {
|
||||
return !loading ? ({
|
||||
password: this.renderPasswordUser,
|
||||
oauth: this.renderOAuthUser,
|
||||
}[this.getUserType(user)])(loading, user) : <div />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, user } = this.props;
|
||||
return (<div className="Profile">
|
||||
|
|
@ -95,65 +189,7 @@ class Profile extends React.Component {
|
|||
<Col xs={12} sm={6} md={4}>
|
||||
<h4 className="page-header">Edit Profile</h4>
|
||||
<form ref={form => (this.form = form)} onSubmit={event => event.preventDefault()}>
|
||||
{!loading ? (<div>
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<ControlLabel>First Name</ControlLabel>
|
||||
<input
|
||||
type="text"
|
||||
name="firstName"
|
||||
defaultValue={user.profile.name.first}
|
||||
ref={firstName => (this.firstName = firstName)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
<Col xs={6}>
|
||||
<FormGroup>
|
||||
<ControlLabel>Last Name</ControlLabel>
|
||||
<input
|
||||
type="text"
|
||||
name="lastName"
|
||||
defaultValue={user.profile.name.last}
|
||||
ref={lastName => (this.lastName = lastName)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
<FormGroup>
|
||||
<ControlLabel>Email Address</ControlLabel>
|
||||
<input
|
||||
type="email"
|
||||
name="emailAddress"
|
||||
defaultValue={user.emails[0].address}
|
||||
ref={emailAddress => (this.emailAddress = emailAddress)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
<h5 className="page-header">Change Password</h5>
|
||||
<FormGroup>
|
||||
<ControlLabel>Current Password</ControlLabel>
|
||||
<input
|
||||
type="password"
|
||||
name="currentPassword"
|
||||
ref={currentPassword => (this.currentPassword = currentPassword)}
|
||||
className="form-control"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<ControlLabel>New Password</ControlLabel>
|
||||
<input
|
||||
type="password"
|
||||
name="newPassword"
|
||||
ref={newPassword => (this.newPassword = newPassword)}
|
||||
className="form-control"
|
||||
/>
|
||||
<InputHint>Use at least six characters.</InputHint>
|
||||
</FormGroup>
|
||||
<Button type="submit" bsStyle="success">Save Profile</Button>
|
||||
</div>) : <div />}
|
||||
{this.renderProfileForm(loading, user)}
|
||||
</form>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
|
|||
39
imports/ui/pages/Profile/Profile.scss
Normal file
39
imports/ui/pages/Profile/Profile.scss
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
@import '../../stylesheets/colors';
|
||||
|
||||
.OAuthProfile {
|
||||
.LoggedInWith {
|
||||
padding: 10px 20px;
|
||||
border-radius: 3px;
|
||||
color: #fff;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.ServiceIcon {
|
||||
float: left;
|
||||
width: auto;
|
||||
margin-right: 15px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
|
||||
i {
|
||||
font-size: 30px;
|
||||
position: relative;
|
||||
top: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.facebook {
|
||||
background: $facebook;
|
||||
}
|
||||
|
||||
&.github {
|
||||
background: $github;
|
||||
}
|
||||
|
||||
&.google {
|
||||
background: $google;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,13 +4,11 @@ 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 OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons';
|
||||
import InputHint from '../../components/InputHint/InputHint';
|
||||
import AccountPageFooter from '../../components/AccountPageFooter/AccountPageFooter';
|
||||
import validate from '../../../modules/validate';
|
||||
|
||||
import './Signup.scss';
|
||||
|
||||
class Signup extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -86,15 +84,16 @@ class Signup extends React.Component {
|
|||
<h4 className="page-header">Sign Up</h4>
|
||||
<Row>
|
||||
<Col xs={12}>
|
||||
<FormGroup className="OAuthLoginButtons">
|
||||
<OAuthLoginButton service="facebook" />
|
||||
<OAuthLoginButton service="google" />
|
||||
<OAuthLoginButton service="github" />
|
||||
</FormGroup>
|
||||
<OAuthLoginButtons
|
||||
services={['facebook', 'github', 'google']}
|
||||
emailMessage={{
|
||||
offset: 97,
|
||||
text: 'Sign Up with an Email Address',
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -13,3 +13,8 @@ $gray-lighter: #eee;
|
|||
$facebook: #3b5998;
|
||||
$google: #ea4335;
|
||||
$github: $gray-dark;
|
||||
|
||||
$cb-blue: #4285F4;
|
||||
$cb-green: #00D490;
|
||||
$cb-yellow: #FFCF50;
|
||||
$cb-red: #DA5847;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
"dependencies": {
|
||||
"@cleverbeagle/dates": "^0.2.0",
|
||||
"@cleverbeagle/seeder": "^1.1.0",
|
||||
"@cleverbeagle/strings": "^0.1.0",
|
||||
"babel-runtime": "^6.20.0",
|
||||
"bcrypt": "^1.0.2",
|
||||
"jquery": "^2.2.4",
|
||||
|
|
|
|||
|
|
@ -1,18 +1,22 @@
|
|||
{
|
||||
"public": {},
|
||||
"private": {
|
||||
"MAIL_URL": "",
|
||||
"OAuth": {
|
||||
"facebook": {
|
||||
"clientId": "",
|
||||
"secret": ""
|
||||
"appId": "",
|
||||
"secret": "",
|
||||
"loginStyle": "popup"
|
||||
},
|
||||
"google": {
|
||||
"clientId": "",
|
||||
"secret": ""
|
||||
"secret": "",
|
||||
"loginStyle": "popup"
|
||||
},
|
||||
"github": {
|
||||
"clientId": "",
|
||||
"secret": ""
|
||||
"secret": "",
|
||||
"loginStyle": "popup"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue