diff --git a/client/package.json b/client/package.json index 26958c2e1..38b4686d6 100644 --- a/client/package.json +++ b/client/package.json @@ -62,6 +62,8 @@ "dependencies": { "@advancedalgos/key-vault-client": "0.0.5", "@advancedalgos/teams-client": "0.0.8", + "@material-ui/core": "^3.2.0", + "@material-ui/icons": "^3.0.1", "babel-polyfill": "^6.26.0", "react": "^16.5.2", "react-dom": "^16.5.2", diff --git a/client/src/App.js b/client/src/App.js index 5eb065122..3d9730629 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -1,20 +1,46 @@ +import { ApolloProvider } from 'react-apollo' import React, { Component } from 'react' import { Route, BrowserRouter, Switch } from 'react-router-dom' -import App from '@advancedalgos/teams-client' -import App2 from '@advancedalgos/key-vault-client' +import MuiThemeProvider from '@material-ui/core/styles/MuiThemeProvider' +import CssBaseline from '@material-ui/core/CssBaseline' +import { withStyles } from '@material-ui/core/styles' +import { theme, globalStyles } from './styles' -export const Master = () => ( +import client from './graphql/apollo' +import Auth from './auth' + +import { Header, Footer, Home, Callback } from './views' + +import Teams from '@advancedalgos/teams-client' +import KeyVault from '@advancedalgos/key-vault-client' + +export const auth = new Auth( + result => console.log('auth result', result), + client +) + +export const MasterApp = () => ( - - - - + + + +
+
+ + + } + /> + + +
+
+
) -export const Temporary = () =>( -
Hello Advanced Alogs!
-) - -export default Master +export default withStyles(globalStyles)(MasterApp) diff --git a/client/src/assets/advanced-algos/aa-logo-dark.svg b/client/src/assets/advanced-algos/aa-logo-dark.svg new file mode 100644 index 000000000..479feeb86 --- /dev/null +++ b/client/src/assets/advanced-algos/aa-logo-dark.svg @@ -0,0 +1 @@ +aa-logo-dark \ No newline at end of file diff --git a/client/src/assets/advanced-algos/aa-logo-vert.svg b/client/src/assets/advanced-algos/aa-logo-vert.svg new file mode 100644 index 000000000..442e9a7fd --- /dev/null +++ b/client/src/assets/advanced-algos/aa-logo-vert.svg @@ -0,0 +1 @@ +aa-logo-vert \ No newline at end of file diff --git a/client/src/assets/advanced-algos/aa-logo.png b/client/src/assets/advanced-algos/aa-logo.png new file mode 100644 index 000000000..9701ee1c0 Binary files /dev/null and b/client/src/assets/advanced-algos/aa-logo.png differ diff --git a/client/src/assets/advanced-algos/favicon/android-chrome-192x192.png b/client/src/assets/advanced-algos/favicon/android-chrome-192x192.png new file mode 100644 index 000000000..150043a06 Binary files /dev/null and b/client/src/assets/advanced-algos/favicon/android-chrome-192x192.png differ diff --git a/client/src/assets/advanced-algos/favicon/android-chrome-512x512.png b/client/src/assets/advanced-algos/favicon/android-chrome-512x512.png new file mode 100644 index 000000000..d98b9ce8d Binary files /dev/null and b/client/src/assets/advanced-algos/favicon/android-chrome-512x512.png differ diff --git a/client/src/assets/advanced-algos/favicon/apple-touch-icon.png b/client/src/assets/advanced-algos/favicon/apple-touch-icon.png new file mode 100644 index 000000000..95ec62746 Binary files /dev/null and b/client/src/assets/advanced-algos/favicon/apple-touch-icon.png differ diff --git a/client/src/assets/advanced-algos/favicon/browserconfig.xml b/client/src/assets/advanced-algos/favicon/browserconfig.xml new file mode 100644 index 000000000..b3930d0f0 --- /dev/null +++ b/client/src/assets/advanced-algos/favicon/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/client/src/assets/advanced-algos/favicon/favicon-16x16.png b/client/src/assets/advanced-algos/favicon/favicon-16x16.png new file mode 100644 index 000000000..99bda00af Binary files /dev/null and b/client/src/assets/advanced-algos/favicon/favicon-16x16.png differ diff --git a/client/src/assets/advanced-algos/favicon/favicon-32x32.png b/client/src/assets/advanced-algos/favicon/favicon-32x32.png new file mode 100644 index 000000000..08c2369b0 Binary files /dev/null and b/client/src/assets/advanced-algos/favicon/favicon-32x32.png differ diff --git a/client/src/assets/advanced-algos/favicon/favicon.ico b/client/src/assets/advanced-algos/favicon/favicon.ico new file mode 100644 index 000000000..cd2c5d6af Binary files /dev/null and b/client/src/assets/advanced-algos/favicon/favicon.ico differ diff --git a/client/src/assets/advanced-algos/favicon/html_code.html b/client/src/assets/advanced-algos/favicon/html_code.html new file mode 100644 index 000000000..a66b72bb6 --- /dev/null +++ b/client/src/assets/advanced-algos/favicon/html_code.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/advanced-algos/favicon/mstile-150x150.png b/client/src/assets/advanced-algos/favicon/mstile-150x150.png new file mode 100644 index 000000000..78a06c05a Binary files /dev/null and b/client/src/assets/advanced-algos/favicon/mstile-150x150.png differ diff --git a/client/src/assets/advanced-algos/favicon/safari-pinned-tab.svg b/client/src/assets/advanced-algos/favicon/safari-pinned-tab.svg new file mode 100644 index 000000000..db3ba7cde --- /dev/null +++ b/client/src/assets/advanced-algos/favicon/safari-pinned-tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/advanced-algos/favicon/site.webmanifest b/client/src/assets/advanced-algos/favicon/site.webmanifest new file mode 100644 index 000000000..002445914 --- /dev/null +++ b/client/src/assets/advanced-algos/favicon/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Advanced Algos", + "short_name": "Advanced Algos", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/client/src/auth/Auth0.js b/client/src/auth/Auth0.js new file mode 100644 index 000000000..37d937578 --- /dev/null +++ b/client/src/auth/Auth0.js @@ -0,0 +1,7 @@ +export const AUTH_CONFIG = { + api_audience: process.env.AUTH0_AUDIENCE, + domain: process.env.AUTH0_DOMAIN, + clientId: process.env.AUTH0_CLIENTID, + callbackUrl: process.env.AUTH0_CALLBACK_URL, + logoutUrl: process.env.AUTH0_LOGOUT_URL +} diff --git a/client/src/auth/index.js b/client/src/auth/index.js new file mode 100644 index 000000000..1c3fbfa80 --- /dev/null +++ b/client/src/auth/index.js @@ -0,0 +1,256 @@ +import Auth0Lock from 'auth0-lock' +import gql from 'graphql-tag' + +import { getItem, setItem, removeItem } from '../utils/local-storage' +import { validObject, deleteCookie } from '../utils/js-helpers' + +import { client } from '../App' + +import { AUTH_CONFIG } from './Auth0' // create by renaming Auth0.sample.js to Auth0.js and setting vars + +const AUTHENTICATE = gql` + mutation authenticate($idToken: String!) { + authenticate(idToken: $idToken) { + alias + authId + } + } +` + +const VERIFY_TEAM_INVITE = gql` + mutation verifyTeamInvite($token: String!) { + verifyTeamInvite(token: $token) { + email + team { + slug + } + } + } +` + +export const defaultOptions = { + oidcConformant: true, + autoclose: true, + auth: { + sso: true, + redirectUrl: window.location.origin + '/callback', + responseType: 'token id_token', + audience: `${AUTH_CONFIG.api_audience}`, + params: { + scope: `openid profile email user_metadata app_metadata picture` + } + } +} + +export const inviteOptions = { + oidcConformant: true, + autoclose: false, + allowedConnections: ['Username-Password-Authentication'], + allowShowPassword: true, + auth: { + sso: false, + redirectUrl: window.location.origin + '/callback', + responseType: 'token id_token', + audience: `${AUTH_CONFIG.api_audience}`, + params: { + state: '', + scope: `email` + } + }, + prefill: { email: '' } +} + +let lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, defaultOptions) + +class Auth { + constructor (cb, apolloClient) { + this.handleAuthentication() + // binds functions to keep this context + this.apolloClient = apolloClient + this.cb = cb.bind(this) + this.login = this.login.bind(this) + this.loginInvite = this.loginInvite.bind(this) + this.logout = this.logout.bind(this) + this.isAuthenticated = this.isAuthenticated.bind(this) + this.checkSession = this.checkSession.bind(this) + } + + login () { + // Call the show method to display the widget. + lock.show() + } + + async loginInvite (jwt) { + let email + let team + try { + const data = await client.mutate({ + mutation: VERIFY_TEAM_INVITE, + variables: { token: jwt } + }) + + console.log('loginInvite auth: ', await data.data.verifyTeamInvite) + setItem('invite', JSON.stringify(data.data.verifyTeamInvite)) + + email = data.data.verifyTeamInvite.email + team = data.data.verifyTeamInvite.team.slug + } catch (err) { + return console.log('loginInvite err: ', err) + } + inviteOptions.prefill.email = email + inviteOptions.auth.params.state = `${email}|${team}` + let lockInvite = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, inviteOptions) + lockInvite.show() + } + + handleAuthentication () { + // Add a callback for Lock's `authenticated` event + lock.on('authenticated', this.setSession.bind(this)) + // Add a callback for Lock's `authorization_error` event + lock.on('authorization_error', err => { + const data = { status: `error`, errMessage: err.error } + this.cb(data) + }) + } + + checkSession () { + return new Promise((resolve, reject) => { + // Add a callback for Lock's `authenticated` event + lock.checkSession( + { + responseType: 'token id_token', + audience: AUTH_CONFIG.api_audience, + scope: + 'openid email profile read:teams write:teams read:member write:member', + connection: 'github', + prompt: 'none' + }, + function (err, authResult) { + if (!err) { + resolve(authResult) + } else { + reject(err) + } + } + ) + }) + } + + setSession (authResult) { + if (authResult && authResult.accessToken && authResult.idToken) { + // Set the time that the access token will expire at + let expiresAt = JSON.stringify( + authResult.expiresIn * 1000 + new Date().getTime() + ) + setItem('access_token', authResult.accessToken) + setItem('id_token', authResult.idToken) + setItem('expires_at', expiresAt) + const data = { + status: `success`, + accessToken: authResult.accessToken, + idToken: authResult.idToken, + expiresAt + } + console.log('setSession idTokenPayload: ', authResult.idTokenPayload) + + this.signinOrCreateAccount({ ...data }) + this.cb(data) + const user = { + authId: authResult.idTokenPayload.sub, + alias: authResult.idTokenPayload.nickname + } + setItem('user', JSON.stringify(user)) + if (window.location.href.includes(`callback`)) { + // window.location.href = '/dashboard' + } + return true + } + } + + async signinOrCreateAccount ({ accessToken, idToken, expiresAt }) { + try { + const data = await client.mutate({ + mutation: AUTHENTICATE, + variables: { idToken } + }) + + const user = { + authId: data.data.authenticate.authId, + alias: data.data.authenticate.alias + } + setItem('user', JSON.stringify(user)) + if (window.location.href.includes(`callback`)) { + window.location.href = '/dashboard' + } + return data + } catch (err) { + return console.log('Sign in or create account error: ', err) + } + } + + logout () { + // Clear access token and ID token from local storage + removeItem('access_token') + removeItem('id_token') + removeItem('expires_at') + removeItem('authUser') + removeItem('auth0.ssodata') + window.localStorage.clear() + deleteCookie('ajs_anonymous_id') + deleteCookie('ajs_user_id') + deleteCookie('current_tenant') + + lock.logout({ returnTo: AUTH_CONFIG.logoutUrl }) + } + + async isAuthenticated () { + // check session and run Auth0 SS0 + const getUser = await getItem('authUser') + const getExpires = await getItem('expires_at') + let user = JSON.parse(getUser) + + if (new Date().getTime() < getExpires * 1000) { + console.log( + 'handleAuth.user exp: ', + user, + getExpires, + window.location.href + ) + + return user + } + + const checkSSO = await this.checkSession() + .then(result => { + console.log('handleAuth.checksessions: ', result) + const user = { + authId: result.idTokenPayload.sub, + alias: result.idTokenPayload.nickname + } + setItem('authUser', JSON.stringify(user)) + // user confirmed, log into client + this.setSession(result) + return result.idTokenPayload + }) + .catch(err => { + if (err.error === 'login_required') { + // If graphql error, update session + // otherwise send to login + // return this.login() + } + return err + }) + + if (/manage|profile|create|dashboard/.test(window.location.href) && !user) { + this.login() + } + + if (validObject(checkSSO, 'error')) { + return false + } else { + return checkSSO + } + } +} + +export default Auth diff --git a/client/src/graphql/apollo.js b/client/src/graphql/apollo.js new file mode 100644 index 000000000..d1a7e7b00 --- /dev/null +++ b/client/src/graphql/apollo.js @@ -0,0 +1,113 @@ +import { ApolloClient } from 'apollo-client' +import { HttpLink } from 'apollo-link-http' +import { InMemoryCache } from 'apollo-cache-inmemory' +import { ApolloLink, split, Observable } from 'apollo-link' +import { onError } from 'apollo-link-error' +import { setContext } from 'apollo-link-context' +import { WebSocketLink } from 'apollo-link-ws' +import { SubscriptionClient } from 'subscriptions-transport-ws' +import { getMainDefinition } from 'apollo-utilities' + +import { getItem } from './utils/local-storage' + +const graphqlEndpoint = + process.env.NODE_ENV === 'production' + ? process.env.PROD_GRAPHQL + : process.env.DEV_GRAPHQL + +const wsUri = graphqlEndpoint.replace(/^http/, 'ws') + +const globalVar = + typeof global !== 'undefined' + ? global + : typeof window !== 'undefined' + ? window + : {} +const webSocketImpl = globalVar.WebSocket || globalVar.MozWebSocket + +const wsClient = new SubscriptionClient( + wsUri, + { + reconnect: true + }, + webSocketImpl +) + +wsClient.onDisconnected(() => { + // console.log('onDisconnected') +}) + +wsClient.onReconnected(() => { + // console.log('onReconnected') +}) + +const httpLink = new HttpLink({ uri: graphqlEndpoint }) + +const link = split( + ({ query }) => { + const { kind, operation } = getMainDefinition(query) + return kind === 'OperationDefinition' && operation === 'subscription' + }, + new WebSocketLink(wsClient), + httpLink +) + +const authRetryLink = onError( + ({ graphQLErrors, networkError, operation, forward }) => { + if (graphQLErrors) { + // User access token has expired + // console.log('authLink: ', graphQLErrors) // check for error message to intercept and resend with Auth0 access token + if (graphQLErrors[0].message === 'Not logged in') { + // We assume we have auth0 access token needed to run the async request + // Let's refresh token through async request + return new Observable(observer => { + getItem('access_token') + .then(accessToken => { + operation.setContext(({ headers = {} }) => ({ + headers: { + // Re-add old headers + ...headers, + // Switch out old access token for new one + Authorization: `Bearer ${accessToken}` || null + } + })) + }) + .then(() => { + const subscriber = { + next: observer.next.bind(observer), + error: observer.error.bind(observer), + complete: observer.complete.bind(observer) + } + + // Retry last failed request + forward(operation).subscribe(subscriber) + }) + .catch(error => { + // No auth0 access token available, we force user to login + observer.error(error) + }) + }) + } + } + } +) + +const authLink = setContext(async (_, { headers }) => { + // get the authentication token from local storage if it exists + await getItem('access_token').then(token => { + return { + headers: { + ...headers, + Authorization: token ? `Bearer ${token}` : `` + } + } + }) +}) + +const cache = new InMemoryCache().restore(window.__APOLLO_STATE__) + +export const client = new ApolloClient({ + link: ApolloLink.from([authRetryLink, authLink, link]), + cache, + connectToDevTools: true +}) diff --git a/client/src/index.js b/client/src/index.js index 6438fd3e1..e6c15fbd9 100644 --- a/client/src/index.js +++ b/client/src/index.js @@ -2,9 +2,9 @@ import React from 'react' import ReactDOM from 'react-dom' -import App from './App' +import MasterApp from './App' -ReactDOM.render(, document.getElementById('root')) +ReactDOM.render(, document.getElementById('root')) let frontendReloadCount = 0 diff --git a/client/src/styles/index.js b/client/src/styles/index.js new file mode 100644 index 000000000..26043dfc0 --- /dev/null +++ b/client/src/styles/index.js @@ -0,0 +1,72 @@ +import { createMuiTheme } from '@material-ui/core/styles' +import indigo from '@material-ui/core/colors/indigo' +// import red from '@material-ui/core/colors/red' + +export const theme = createMuiTheme({ + palette: { + primary: { main: '#303036' }, + secondary: indigo + }, + typography: { + fontFamily: '"Saira","Saira Condenses", sans-serif' + }, + overrides: { + Paper: { + root: { + backgroundColor: '#ffffff' + }, + elevation: 0, + square: true + } + } +}) + +export const globalStyles = theme => ({ + '@global': { + body: { + backgroundColor: theme.palette.common.white + } + }, + appBar: { + position: 'relative' + }, + toolbarTitle: { + flex: 1 + }, + layout: { + width: 'auto', + marginLeft: theme.spacing.unit * 3, + marginRight: theme.spacing.unit * 3, + [theme.breakpoints.up(900 + theme.spacing.unit * 3 * 2)]: { + width: 900, + marginLeft: 'auto', + marginRight: 'auto' + } + }, + heroContent: { + maxWidth: 600, + margin: '0 auto', + padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px` + }, + cardHeader: { + backgroundColor: theme.palette.grey[200] + }, + cardPricing: { + display: 'flex', + justifyContent: 'center', + alignItems: 'baseline', + marginBottom: theme.spacing.unit * 2 + }, + cardActions: { + [theme.breakpoints.up('sm')]: { + paddingBottom: theme.spacing.unit * 2 + } + }, + footer: { + marginTop: theme.spacing.unit * 8, + borderTop: `1px solid ${theme.palette.divider}`, + padding: `${theme.spacing.unit * 6}px 0` + } +}) + +export default theme diff --git a/client/src/utils/js-helpers.js b/client/src/utils/js-helpers.js new file mode 100644 index 000000000..7db4cb28a --- /dev/null +++ b/client/src/utils/js-helpers.js @@ -0,0 +1,70 @@ +export function isDefined (d) { + return d !== null && typeof d !== 'undefined' +} + +export function isNotDefined (d) { + return !isDefined(d) +} + +export const isArray = Array.isArray + +export function isObject (d) { + return isDefined(d) && typeof d === 'object' && !Array.isArray(d) +} + +export function isEmpty (obj) { + for (var key in obj) { + if (obj.hasOwnProperty(key)) return false + } + return true +} + +export function isString (value) { + return typeof value === 'string' || value instanceof String +} + +export function validObject (object, ...keys) { + return keys.reduce((a, b) => (a || {})[b], object) !== undefined +} + +export function mergeDeep (target, ...sources) { + if (!sources.length) return target + const source = sources.shift() + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }) + mergeDeep(target[key], source[key]) + } else { + Object.assign(target, { [key]: source[key] }) + } + } + } + return mergeDeep(target, ...sources) +} + +export function deleteCookie (c) { + document.cookie.split(';').forEach(function (c) { + document.cookie = c + .replace(/^ +/, '') + .replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/') + }) +} + +export function slugify (string) { + const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;' + const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------' + const p = new RegExp(a.split('').join('|'), 'g') + + return string + .toString() + .toLowerCase() + .replace(/\s+/g, '-') // Replace spaces with - + .replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters + .replace(/&/g, '-and-') // Replace & with 'and' + .replace(/[^\w-]+/g, '') // Remove all non-word characters + .replace(/--+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, '') // Trim - from end of text +} diff --git a/client/src/utils/local-storage.js b/client/src/utils/local-storage.js new file mode 100644 index 000000000..963ba0f36 --- /dev/null +++ b/client/src/utils/local-storage.js @@ -0,0 +1,9 @@ +const getItem = async name => window.localStorage.getItem(name) +const setItem = async (name, value) => window.localStorage.setItem(name, value) +const removeItem = async name => window.localStorage.removeItem(name) + +export { + getItem, + setItem, + removeItem +} diff --git a/client/src/views/callback/index.js b/client/src/views/callback/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/views/footer/index.js b/client/src/views/footer/index.js new file mode 100644 index 000000000..68bf35d2e --- /dev/null +++ b/client/src/views/footer/index.js @@ -0,0 +1,160 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { NavLink } from 'react-router-dom' +import { withStyles } from '@material-ui/core/styles' +import classNames from 'classnames' +import Typography from '@material-ui/core/Typography' +import Grid from '@material-ui/core/Grid' + +import aalogo from '../../assets/advanced-algos/aa-logo-vert.svg' + +const styles = theme => ({ + '@global': { + body: { + backgroundColor: theme.palette.common.white + } + }, + layout: { + width: 'auto', + marginTop: theme.spacing.unit * 7, + marginLeft: theme.spacing.unit * 3, + marginRight: theme.spacing.unit * 3, + [theme.breakpoints.up(1200 + theme.spacing.unit * 3 * 2)]: { + width: 1200, + marginLeft: 'auto', + marginRight: 'auto' + } + }, + footer: { + borderTop: `1px solid ${theme.palette.divider}`, + padding: `${theme.spacing.unit * 6}px 0` + }, + footerContainer: { + marginRight: theme.spacing.unit * 8, + marginLeft: theme.spacing.unit * 8 + }, + footerLink: { + textDecoration: 'none' + } +}) + +const Footer = ({ classes }) => { + return ( + + ) +} + +Footer.propTypes = { + classes: PropTypes.object.isRequired +} + +export default withStyles(styles)(Footer) diff --git a/client/src/views/header/LoggedIn.js b/client/src/views/header/LoggedIn.js new file mode 100644 index 000000000..bc6ef2d07 --- /dev/null +++ b/client/src/views/header/LoggedIn.js @@ -0,0 +1,47 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { graphql } from 'react-apollo' + +import LoggedInMenu from './LoggedInMenu' + +import { GetCurrentMember } from '../../graphql/members' + +import { isDefined } from '../../utils/js-helpers' + +export const LoggedIn = props => { + let { data, user, auth } = props + console.log('LoggedIn :', data, user) + let displayName = 'No Display Name' + + if (isDefined(user.alias)) { + displayName = user.alias + } + + if (isDefined(user.nickname)) { + displayName = user.nickname + } + + if (isDefined(user.firstName)) { + displayName = user.firstName + } + + if (isDefined(user.firstName) && isDefined(user.lastName)) { + displayName = user.firstName + ' ' + user.lastName + } + + return ( +
+

+ +

+
+ ) +} + +LoggedIn.propTypes = { + data: PropTypes.object, + user: PropTypes.object.isRequired, + auth: PropTypes.object.isRequired +} + +export default graphql(GetCurrentMember)(LoggedIn) diff --git a/client/src/views/header/LoggedInMenu.js b/client/src/views/header/LoggedInMenu.js new file mode 100644 index 000000000..574a8e0ed --- /dev/null +++ b/client/src/views/header/LoggedInMenu.js @@ -0,0 +1,77 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import Button from '@material-ui/core/Button' +import Menu from '@material-ui/core/Menu' +import MenuItem from '@material-ui/core/MenuItem' +import { Link } from 'react-router-dom' + +import ProfileIcon from '@material-ui/icons/Person' +import LogoutIcon from '@material-ui/icons/DirectionsWalk' + +const UserLink = props => +const LogoutLink = props => + +class LoggedInUserMenu extends Component { + constructor (props) { + super(props) + + this.handleMenu = this.handleMenu.bind(this) + this.handleClose = this.handleClose.bind(this) + this.handleLogout = this.handleLogout.bind(this) + + this.state = { + menu: null + } + } + + render () { + const { menuLabel } = this.props + return ( + + + + + +
Profile
+
+ + +
Logout
+
+
+
+ ) + } + + handleMenu (e) { + this.setState({ menu: e.currentTarget }) + } + + handleClose () { + this.setState({ menu: null }) + } + + handleLogout () { + this.props.auth.logout() + this.setState({ menu: null }) + } +} + +LoggedInUserMenu.propTypes = { + menuLabel: PropTypes.string.isRequired, + auth: PropTypes.object.isRequired +} + +export default LoggedInUserMenu diff --git a/client/src/views/header/LoggedOut.js b/client/src/views/header/LoggedOut.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/src/views/header/index.js b/client/src/views/header/index.js new file mode 100644 index 000000000..20a58b20e --- /dev/null +++ b/client/src/views/header/index.js @@ -0,0 +1,141 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' + +import { withStyles } from '@material-ui/core/styles' +import AppBar from '@material-ui/core/AppBar' +import Toolbar from '@material-ui/core/Toolbar' +import Typography from '@material-ui/core/Typography' +import IconButton from '@material-ui/core/IconButton' +import Button from '@material-ui/core/Button' + +// icons +import HomeIcon from '@material-ui/icons/Home' +import DashboardIcon from '@material-ui/icons/Dashboard' + +import { Link } from 'react-router-dom' + +import { getItem } from '../../utils/local-storage' + +// components +import { LoggedIn } from './LoggedIn' +import { LoggedOut } from './LoggedOut' + +import AALogo from '../../assets/advanced-algos/aa-logo-dark.svg' + +const AboutLink = props => +const TeamsLink = props => +const DashboardLink = props => +const HomeLink = props => + +const styles = theme => ({ + '@global': { + body: { + backgroundColor: theme.palette.common.white + } + }, + root: { + flexGrow: 1 + }, + colorDefault: { + color: '#000' + }, + flex: { + flexGrow: 1 + }, + toolbarTitle: { + flex: 1 + }, + menuButton: { + marginLeft: -12, + marginRight: 20 + }, + appBar: { + position: 'relative' + }, + img: { + margin: 20, + display: 'block', + maxWidth: 240, + maxHeight: 48 + } +}) + +class Header extends Component { + constructor (props) { + super(props) + this.state = { + user: null, + open: false + } + } + + async componentDidMount () { + const user = await getItem('user') + console.log('NavBar CDM: ', user, this.state) + this.setState({ user }) + } + + render () { + let { classes, auth } = this.props + + let user = JSON.parse(this.state.user) + + return ( +
+ + + Advanced Algos + + Teams + + + + + + + + + {this.state.user !== undefined && this.state.user !== null ? ( + + + + + + + ) : ( + + )} + + +
+ ) + } +} + +Header.propTypes = { + classes: PropTypes.object.isRequired, + auth: PropTypes.object.isRequired +} + +export default withStyles(styles)(Header) diff --git a/client/src/views/home/index.js b/client/src/views/home/index.js new file mode 100644 index 000000000..60ee1ecd5 --- /dev/null +++ b/client/src/views/home/index.js @@ -0,0 +1,61 @@ +import React from 'react' +import PropTypes from 'prop-types' + +import CssBaseline from '@material-ui/core/CssBaseline' +import Typography from '@material-ui/core/Typography' +import { withStyles } from '@material-ui/core/styles' + +const styles = theme => ({ + '@global': { + body: { + backgroundColor: theme.palette.common.white + } + }, + layout: { + width: 'auto', + marginLeft: theme.spacing.unit * 3, + marginRight: theme.spacing.unit * 3, + [theme.breakpoints.up(900 + theme.spacing.unit * 3 * 2)]: { + width: 900, + marginLeft: 'auto', + marginRight: 'auto' + } + }, + heroContent: { + maxWidth: 600, + margin: '0 auto', + padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px` + } +}) + +const Home = ({ classes }) => ( + + +
+
+ + Collaborate. Innovate. Evolve. + + + Welcome to Advanced Algos + +
+
+
+) + +Home.propTypes = { + classes: PropTypes.object.isRequired +} + +export default withStyles(styles)(Home) diff --git a/client/src/views/index.js b/client/src/views/index.js new file mode 100644 index 000000000..c3cf45256 --- /dev/null +++ b/client/src/views/index.js @@ -0,0 +1,6 @@ +import Header from './header' +import Footer from './footer' +import Home from './home' +import Callback from './callback' + +export { Header, Footer, Home, Callback }