diff --git a/server/routes/validate.js b/server/routes/validate.js index 86327f4..c5820d4 100644 --- a/server/routes/validate.js +++ b/server/routes/validate.js @@ -4,16 +4,26 @@ const router = express.Router(); const route = function(db) { router.post('/', function(req, res, next) { - const passcode = req.body.passcode; + const password = req.body.password; + const passwordRef = db.ref('server/password'); + passwordRef.once('value', function(s) { + res.json({ + valid: password === s.val(), + }); + }); + }); + + router.post('/uid', function(req, res, next) { + const password = req.body.password; const uid = req.body.uid; - const passcodeRef = db.ref('server/passcode'); - passcodeRef.once('value', function(s) { + const passwordRef = db.ref('server/password'); + passwordRef.once('value', function(s) { if (!uid) { return res.status(400).json({ error: 'No UID', }); } - const valid = passcode === s.val(); + const valid = password === s.val(); const userRef = db.ref(`users/${uid}`) .set({ isAdmin: false, diff --git a/src/App.js b/src/App.js index 5852be9..bda87c5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,11 +1,9 @@ import React, { Component } from 'react'; -import { BrowserRouter, Switch, Route } from 'react-router-dom'; +import { BrowserRouter } from 'react-router-dom'; -import Navigation from './components/Navigation'; -import Home from './routes/Home'; -import Login from './routes/Login'; +import Menu from './components/Menu'; +import Login from './components/Login'; import Admin from './routes/Admin'; -import NotFound from './routes/NotFound'; import { auth, db } from './rebase'; @@ -28,20 +26,21 @@ class App extends Component { } componentWillMount() { - this.registerAuthStateChange('main', (user) => { - console.log('mainevent', user); - if (user) { - this.setState({ - user: { - uid: user.uid, - displayName: user.displayName, - photoURL: user.photoURL, - }, - }); - this.userStateBinding = db.bindToState(`users/${user.uid}`, { - context: this, - state: 'user.data', - }); + // Set up auth state observer + auth.onAuthStateChanged((res) => { + if (res) { + const user = {...this.state.user}; + user.uid = res.uid; + user.displayName = res.displayName; + user.photoURL = res.photoURL; + user.data = {}; + this.setState({ user }); + if (!this.userStateBinding) { + this.userStateBinding = db.bindToState(`users/${user.uid}`, { + context: this, + state: 'user.data', + }); + } } else if (this.userStateBinding) { db.removeBinding(this.userStateBinding); this.setState({ @@ -49,38 +48,25 @@ class App extends Component { }); } }); - - // Set up auth state observer - auth.onAuthStateChanged((user) => { - // loop through all registered auth state events - Object.values(this.authStateEvents).forEach((fn) => fn(user)); - }); - } - - registerAuthStateChange = (key, fn) => { - this.authStateEvents[key] = fn; } render() { return ( - -
- - - } /> - } /> - } /> - - -
-
+
+ +
+ {this.state.user.uid && this.state.user.data.approved + ? '' + : + } + {this.state.user.data.isAdmin + ? + : '' + } +
+
+ +
); } } diff --git a/src/Colours.js b/src/Colours.js new file mode 100644 index 0000000..c3668f3 --- /dev/null +++ b/src/Colours.js @@ -0,0 +1,8 @@ +const Colours = { + nearblack: '#242424', + darkgrey: '#2A2B2F', + offwhite: '#EFEFEF', + white: '#FFFFFF', +} + +export default Colours; diff --git a/src/components/Login.js b/src/components/Login.js new file mode 100644 index 0000000..694badf --- /dev/null +++ b/src/components/Login.js @@ -0,0 +1,166 @@ +import React from 'react'; +import { auth } from '../rebase'; +import firebase from 'firebase'; +import axios from 'axios'; +import styled from 'styled-components'; + +class Login extends React.Component { + state = { + authProvider: null, + password: null, + passwordValid: false, + } + + componentWillUpdate(prev, next) { + const authProvider = next.authProvider; + if (!this.props.user.uid && next.passwordValid && authProvider) { + const password = this.state.password; + if (authProvider === 'anonymous') { + auth.signInAnonymously().then(res => { + this.submitUid(res.uid, password); + }).catch(this.handleError); + } else if (authProvider === 'twitter') { + const provider = new firebase.auth.TwitterAuthProvider(); + auth.signInWithPopup(provider).then(res => { + this.submitUid(res.user.uid, password); + }).catch(this.handleError); + } else if (authProvider === 'facebook') { + const provider = new firebase.auth.FacebookAuthProvider(); + provider.addScope('email'); + auth.signInWithPopup(provider).then(res => { + this.submitUid(res.user.uid, password); + }).catch(err => console.error(err)); + } + } + } + + handleError = (err) => { + console.error(err); + } + + submitPassword = (e) => { + e.preventDefault(); + const password = this.password.value; + if (this.props.user.uid) { + return this.submitUid(this.props.user.uid, password); + } + this.form.reset(); + axios.post('/api/validate', { password }).then((res) => { + if (res.data.valid) { + this.setState({ + password, + passwordValid: true, + }); + } + }).catch(this.handleError); + } + + submitUid = (uid, password) => { + if (uid && password) { + axios.post('/api/validate/uid', { uid, password }).then(res => { + auth.signInWithCustomToken(res.data.token) + .catch(this.handleError); + }).catch(this.handleError); + } + } +/* + authWithTwitterUpgrade = () => { + const currentUid = this.props.user.uid; + const provider = new firebase.auth.TwitterAuthProvider(); + auth.signInWithPopup(provider).then(res => { + const newUid = res.user.uid; + if (newUid) { + setTimeout(() => { + console.log('upgrading', { currentUid, newUid }); + axios.post('/api/upgrade', { + currentUid, + newUid, + }).then(res => { + auth.signInWithCustomToken(res.data.token) + .catch((err) => { + console.error(err); + }); + }).catch(err => console.error(err)); + },500); + } + }).catch(err => console.error(err)); + } +*/ + + authAnonymously = (e) => { + e.preventDefault(); + this.setState({ authProvider: 'anonymous' }); + } + + authWithProvider = (authProvider) => { + this.setState({ authProvider }); + } + + renderPasswordInput = () => { + return ( +
+

Please enter the password to continue

+
this.form = el} onSubmit={(e) => this.submitPassword(e)}> + this.password = el} placeholder="Password" /> + +
+
+ ) + } + + renderIntro = () => { + return ( +
+

Sign in

+ Sign in anonymously +

or

+

Sign in with:

+ + +
+ ) + } + + render() { + const authProvider = this.state.authProvider; + const isAuthed = this.props.user.uid !== null; + const isApproved = this.props.user.data.approved; + const isAnonymous = this.props.user.data.isAnonymous; + const isValid = this.state.passwordValid; + console.log({ authProvider, isAuthed, isApproved, isAnonymous, isValid }); + + let state; + if (!isAuthed && !authProvider) { + state = 'intro'; + } else if ((!isAuthed && !isValid) || !isApproved) { + state = 'password'; + } + return ( + + { state === 'intro' ? this.renderIntro() : '' } + { state === 'password' ? this.renderPasswordInput() : '' } + + ) + } +} + +const LoginContainer = styled.div` + position: fixed; + top: 50%; + left: 50%; + transform: translateX(-50%) translateY(-50%); + padding: 5%; + border: 1px solid black; + text-align: center; +` +const Link = styled.a` + text-decoration: none; +` +const Input = styled.input` + border: 1px solid black; +`; +const Button = styled.button` + border: 1px solid black; +` + +export default Login; diff --git a/src/components/Menu.js b/src/components/Menu.js new file mode 100644 index 0000000..4d25f6f --- /dev/null +++ b/src/components/Menu.js @@ -0,0 +1,133 @@ +import React from 'react'; +import { auth } from '../rebase'; +import styled from 'styled-components'; +import Colours from '../Colours'; + +class Menu extends React.Component { + state = { + expanded: false, + } + + logout = () => { + auth.signOut(); + } + + toggleMenu = () => { + this.setState({ expanded: !this.state.expanded }); + } + + render() { + return ( + + + + + + + + Log out + + ) + } +} + + +const MenuContainer = styled.div` + position: fixed; + top: 0; + left: 0; + z-index: 9; + opacity: ${props => props.expanded ? '1' : '.6'}; + min-width: ${props => props.expanded ? '140px' : '50px'}; + min-height: ${props => props.expanded ? '100%' : '42px'}; + background-color: ${Colours.darkgrey}; + overflow: hidden; + transition: min-width .5s ease-in-out, + min-height .5s ease-in-out, + opacity .5s ease-in-out; + + &:hover { + opacity: 1; + } + + &:after { + display: block; + content: ''; + width: 0; + height: 0; + position: absolute; + right: 0; + bottom: 0; + border-top: ${props => props.expanded ? `0 solid ${Colours.darkgrey}` : `32px solid ${Colours.darkgrey}`}; + border-right: ${props => props.expanded ? `0 solid ${Colours.white}` : `40px solid ${Colours.white}`}; + transition: border-top .5s ease, + border-right .5s ease; + } +` + +const MenuButton = styled.button` + position: fixed; + top: 10px; + left: 10px; + width: 30px; + height: 22px; + z-index: 10; + background: none; + color: ${Colours.offwhite}; + border: none; + cursor: pointer; + transform: rotate(0deg); + transition: .5s ease-in-out; + + span { + display: block; + position: absolute; + height: 4px; + width: 100%; + border-radius: 9px; + opacity: 1; + left: 0; + transform: rotate(0deg); + transition: .25s ease-in-out; + background-color: ${props => props.expanded ? Colours.offwhite : Colours.white} + } + + span:nth-child(1) { + top: ${props => props.expanded ? '9px' : '0'}; + left: ${props => props.expanded ? '50%' : '0'}; + width: ${props => props.expanded ? '0%' : '100%'}; + } + + span:nth-child(2) { + top: 9px; + transform: ${props => props.expanded ? 'rotate(45deg)' : ''}; + } + span:nth-child(3) { + top: 9px; + transform: ${props => props.expanded ? 'rotate(-45deg)' : ''}; + } + + span:nth-child(4) { + top: ${props => props.expanded ? '9px' : '18px'}; + left: ${props => props.expanded ? '50%' : '0'}; + width: ${props => props.expanded ? '0%' : '100%'}; + } +` + +const LogoutButton = styled.button` + display: block; + position: absolute; + bottom: 0; + left: ${props => props.expanded ? '0' : '-100%'}; + width: 100%; + z-index:10; + border: 0; + padding: 15px 0; + background-color: ${Colours.nearblack}; + color: ${Colours.offwhite}; + cursor: pointer; + transition: left .5s ease-in-out; + transition-delay: ${props => props.expanded ? '.75s' : '0s'}; +` + +export default Menu; diff --git a/src/components/Navigation.js b/src/components/Navigation.js deleted file mode 100644 index af9a070..0000000 --- a/src/components/Navigation.js +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; - -class Navigation extends React.Component { - render() { - return ( - - ) - } -} - -export default Navigation; diff --git a/src/routes/Login.js b/src/routes/Login.js deleted file mode 100644 index 25a58ec..0000000 --- a/src/routes/Login.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { auth } from '../rebase'; -import firebase from 'firebase'; -import axios from 'axios'; -//import { Redirect } from 'react-router-dom'; - -class Login extends React.Component { - authWithTwitter = () => { - const currentUid = this.props.user.uid; - const provider = new firebase.auth.TwitterAuthProvider(); - auth.signInWithPopup(provider).then(res => { - const newUid = res.user.uid; - if (newUid) { - setTimeout(() => { - console.log('upgrading', { currentUid, newUid }); - axios.post('/api/upgrade', { - currentUid, - newUid, - }).then(res => { - auth.signInWithCustomToken(res.data.token) - .catch((err) => { - console.error(err); - }); - }).catch(err => console.error(err)); - },500); - } - }).catch(err => console.error(err)); - } - - authWithFacebook = () => { - if (this.props.user.uid) { - auth.signOut(); - } - const provider = new firebase.auth.FacebookAuthProvider(); - provider.addScope('email'); - auth.signInWithPopup(provider).catch(err => console.error(err)); - } - - render() { -// if (this.props.user.uid) { -// return -// } - return ( -
-

Login

-

You need to sign in

- - -

Your UID is: {this.props.user.uid}

-
- ) - } -} - -export default Login;