Skip to content
This repository has been archived by the owner on Jun 28, 2021. It is now read-only.

User authentication and profile #460

Merged
merged 10 commits into from
Nov 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
NODE_ENV=development
PORT=8000
API_URL=http://quran.com:3000
ONE_QURAN_URL=http://localhost:3030
SEGMENTS_KEY=
SENTRY_KEY_CLIENT=
SENTRY_KEY_SERVER=
# Quran.com - development app, no need to worry!
FACEBOOK_APP_ID=1599019233731707
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
Raven: true,
mixpanel: true,
"expect": true,
"browser": true
"browser": true,
"FB": true
}
}
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ ENV NODE_ENV production
ENV API_URL http://api.quran.com:3000
ENV SENTRY_KEY_CLIENT https://44c105328ae544ae9928f9eb74b40061@app.getsentry.com/80639
ENV SENTRY_KEY_SERVER https://44c105328ae544ae9928f9eb74b40061:41ca814d33124e04ab450104c3938cb1@app.getsentry.com/80639
# It's okay because it's only the APP ID
ENV FACEBOOK_APP_ID 1557596491207315
ENV ONE_QURAN_URL https://one.quran.com
ENV PORT 8000
ENV NODE_PATH "./src"

Expand Down
2 changes: 1 addition & 1 deletion src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ window.clearCookies = () => {
reactCookie.remove('isFirstTime');
};

match({ history, routes: routes() }, (error, redirectLocation, renderProps) => {
match({ history, routes: routes(store) }, (error, redirectLocation, renderProps) => {
const component = (
<Router
{...renderProps}
Expand Down
110 changes: 110 additions & 0 deletions src/components/FacebookButton/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import { facebook } from 'redux/actions/auth';

import config from 'config';

@connect(
null,
{ facebook, push }
)
export default class FacebookLogin extends Component {
static propTypes = {
callback: PropTypes.func.isRequired,
appId: PropTypes.string.isRequired,
xfbml: PropTypes.bool,
cookie: PropTypes.bool,
scope: PropTypes.string,
textButton: PropTypes.string,
autoLoad: PropTypes.bool,
size: PropTypes.string,
fields: PropTypes.string,
cssClass: PropTypes.string,
version: PropTypes.string,
icon: PropTypes.string,
push: PropTypes.func,
facebook: PropTypes.func
};

static defaultProps = {
callback: () => {},
appId: config.facebookAppId,
textButton: 'Connect with Facebook',
icon: 'fa-facebook',
scope: 'email,public_profile,user_location',
xfbml: true,
cookie: true,
autoLoad: false,
size: 'md',
fields: 'first_name,name,picture',
cssClass: 'btn btn-facebook btn-',
version: '2.7'
};

componentDidMount() {
window.fbAsyncInit = () => {
FB.init({
appId: this.props.appId,
xfbml: this.props.xfbml,
cookie: this.props.cookie,
version: `v${this.props.version}`,
});

if (this.props.autoLoad) {
FB.getLoginStatus(this.checkLoginState);
}
};

// Load the SDK asynchronously
(function(d, s, id) { // eslint-disable-line
const element = d.getElementsByTagName(s)[0];
const fjs = element;
let js = element;
if (d.getElementById(id)) { return; }
js = d.createElement(s); js.id = id;
js.src = '//connect.facebook.net/en_US/sdk.js';
fjs.parentNode.insertBefore(js, fjs);
}(window.document, 'script', 'facebook-jssdk')); // eslint-disable-line
}

responseApi = (authResponse) => {
const { callback, facebook, push } = this.props; // eslint-disable-line no-shadow

return FB.api('/me', { fields: this.props.fields }, (me) => {
me.accessToken = authResponse.accessToken; // eslint-disable-line
callback(me);

return facebook(authResponse.accessToken).then(action => !action.error && push('/'));
});
};

checkLoginState = (response) => {
if (response.authResponse) {
this.responseApi(response.authResponse);
} else {
if (this.props.callback) {
this.props.callback({ status: response.status });
}
}
};

handleClick = () => {
FB.login(this.checkLoginState, { scope: this.props.scope });
};

render() {
return (
<div>
<button
className={`${this.props.cssClass}${this.props.size}`}
onClick={this.handleClick}
>
{this.props.icon && <i className={`margin-md-right fa ${this.props.icon}`} />}
{this.props.textButton}
</button>
<div id="fb-root"></div>
</div>
);
}
}
51 changes: 20 additions & 31 deletions src/components/IndexHeader/Nav/index.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,25 @@
import React, { Component, PropTypes } from 'react';
import Link from 'react-router/lib/Link';
import { connect } from 'react-redux';

class IndexHeaderNav extends Component {
export class IndexHeaderNav extends Component {
static propTypes = {
navlink: PropTypes.bool
user: PropTypes.object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we use PropTypes.shape({ ... }) for objects?

};

state = {
open: false
};

openNav(e) {
e.preventDefault();
openNav(event) {
event.preventDefault();

this.setState({open: !this.state.open});
}

links() {
let classNames = `links ${this.state.open ? 'open' : ''}`;

if (this.props.navlink === false) {
return (
<ul className={classNames}>
<li>
<Link to="/apps" data-metrics-event-name="IndexHeader:Link:Mobile">
Mobile
</Link>
</li>
<li>
<a href="https://quran.zendesk.com/hc/en-us/articles/210090626-Development-help" target="_blank" data-metrics-event-name="IndexHeader:Link:Developer">
Developers
</a>
</li>
<li>
<a href="http://legacy.quran.com" data-metrics-event-name="IndexHeader:Link:Legacy">Legacy Quran.com</a>
</li>
<li>
<a href="https://quran.zendesk.com/hc/en-us" data-metrics-event-name="IndexHeader:Link:Contact">
Contact us
</a>
</li>
</ul>
);
}
const { user } = this.props;
const classNames = `links ${this.state.open ? 'open' : ''}`;

return (
<ul className={classNames}>
Expand All @@ -69,6 +46,14 @@ class IndexHeaderNav extends Component {
Contact us
</a>
</li>
{
user &&
<li>
<Link to="/profile" data-metrics-event-name="IndexHeader:Link:Profile">
{user.firstName}
</Link>
</li>
}
</ul>
);
}
Expand All @@ -82,4 +67,8 @@ class IndexHeaderNav extends Component {
}
}

export default IndexHeaderNav;
export default connect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a minor note on consistency, in some components we use the decorator style, and in others we don't. I think we should stick to one style. IMHO, I prefer to not use decorators. 😃

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 on no decorators.

state => ({
user: state.auth.user
})
)(IndexHeaderNav);
7 changes: 3 additions & 4 deletions src/components/IndexHeader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ import React, { Component, PropTypes } from 'react';
import Link from 'react-router/lib/Link';

import SearchInput from '../SearchInput';
import IndexHeaderNav from './Nav';
import Nav from './Nav';

import debug from '../../helpers/debug';

const logo = require('../../../static/images/logo-lg-w.png');

export default class IndexHeader extends Component {
static propTypes = {
noSearch: PropTypes.bool,
navlink: PropTypes.any
noSearch: PropTypes.bool
};

renderSearch() {
Expand All @@ -29,7 +28,7 @@ export default class IndexHeader extends Component {

return (
<div className="index-header" style={{backgroundColor: '#2CA4AB'}}>
<IndexHeaderNav navlink={this.props.navlink} />
<Nav />
<div className="container">
<div className="row">
<div className="col-md-10 col-md-offset-1 text-center">
Expand Down
78 changes: 78 additions & 0 deletions src/components/QuranNav/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { PropTypes } from 'react';
import LinkContainer from 'react-router-bootstrap/lib/LinkContainer';
import Navbar from 'react-bootstrap/lib/Navbar';
import Nav from 'react-bootstrap/lib/Nav';
import NavDropdown from 'react-bootstrap/lib/NavDropdown';
import MenuItem from 'react-bootstrap/lib/MenuItem';
import NavItem from 'react-bootstrap/lib/NavItem';
import Image from 'react-bootstrap/lib/Image';
const Header = Navbar.Header;
const Collapse = Navbar.Collapse;
const Toggle = Navbar.Toggle;
import { connect } from 'react-redux';

import userType from 'types/userType';

const styles = require('./style.scss');

export const QuranNav = ({ user }) => (
<Navbar inverse fluid className={styles.nav}>
<Header>
<Toggle />
</Header>
<Collapse>
<Nav />
<Nav pullRight>
<LinkContainer to="/apps" data-metrics-event-name="IndexHeader:Link:Mobile">
<NavItem>
Mobile
</NavItem>
</LinkContainer>
<NavItem href="https://quran.zendesk.com/hc/en-us/articles/210090626-Development-help" target="_blank" data-metrics-event-name="IndexHeader:Link:Developer">
Developers
</NavItem>
<NavItem href="http://legacy.quran.com" data-metrics-event-name="IndexHeader:Link:Legacy">
Legacy Quran.com
</NavItem>
<LinkContainer to="/donations" data-metrics-event-name="IndexHeader:Link:Contribute">
<NavItem>
Contribute
</NavItem>
</LinkContainer>
<NavItem href="https://quran.zendesk.com/hc/en-us" data-metrics-event-name="IndexHeader:Link:Contact">
Contact us
</NavItem>
{
user &&
<NavDropdown
title={
<span className={styles.name}>
<Image src={user.image} className={styles.image} circle />
{user.firstName}
</span>
}
id="user-dropdown"
>
<LinkContainer
to="/profile"
data-metrics-event-name="IndexHeader:Link:Profile"
>
<MenuItem eventKey={3.1}>Profile</MenuItem>
</LinkContainer>
<MenuItem eventKey={3.3}>Logout</MenuItem>
</NavDropdown>
}
</Nav>
</Collapse>
</Navbar>
);

QuranNav.propTypes = {
user: PropTypes.shape(userType)
};

export default connect(
state => ({
user: state.auth.user
})
)(QuranNav);
27 changes: 27 additions & 0 deletions src/components/QuranNav/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
$navbar-height: 40px;
.nav {
min-height: $navbar-height;
border-radius: 0px;
margin-bottom: 0px;

:global(.navbar-nav > li > a) {
padding-top: ($navbar-height - 20) / 2;
padding-bottom: ($navbar-height - 20) / 2;
}

&.transparent{
background: transparent;
}
}

.name{
padding-left: 25px;
}

.image{
width: 30px;
position: absolute;
left: 5px;
top: 50%;
transform: translateY(-50%);
}
2 changes: 2 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ module.exports = Object.assign({
host: process.env.HOST || 'localhost',
port: process.env.PORT,
api: process.env.API_URL,
oneQuran: process.env.ONE_QURAN_URL,
sentryClient: process.env.SENTRY_KEY_CLIENT,
sentryServer: process.env.SENTRY_KEY_SERVER,
facebookAppId: process.env.FACEBOOK_APP_ID,
app: {
head: {
titleTemplate: `%s - ${title}`,
Expand Down
9 changes: 9 additions & 0 deletions src/containers/Login/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

import FacebookButton from 'components/FacebookButton';

export default () => (
<div>
<FacebookButton />
</div>
);
Loading