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 @@
+
\ 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 @@
+
\ 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 (
+
+
+
+
+ )
+ }
+
+ 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 (
+
+
+
+
+
+ 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 }