diff --git a/README.md b/README.md index a8790edfb..5650f6db0 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ App.js | | | | | |- UserTabs | | | | -| | | |- Tab 1 - UserUpdate +| | | |- Tab 1 - ProfileSheet | | | | | | | |- Tab 2 - ManageImages | | diff --git a/client/package-lock.json b/client/package-lock.json index 1aca4e3dc..830e87ede 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -4628,6 +4628,11 @@ "schema-utils": "0.3.0" } }, + "file-type": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz", + "integrity": "sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ==" + }, "filename-regex": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", @@ -4647,6 +4652,44 @@ "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz", "integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g==" }, + "filestack-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/filestack-js/-/filestack-js-1.5.1.tgz", + "integrity": "sha512-NXYZhKl0JySNNpQNWtKdyMfQAiMw6VF8Mx+XWHGg+gh3By3ITjR0wTorjM02CVr/6Od/DXQCN1e+6np1Zex9Nw==", + "requires": { + "abab": "1.0.4", + "bowser": "1.9.4", + "file-type": "8.1.0", + "filestack-loader": "3.0.4", + "is-svg": "3.0.0", + "isutf8": "2.0.2", + "mime-types": "2.1.20", + "spark-md5": "3.0.0", + "superagent": "3.8.3", + "tcomb-validation": "3.4.1", + "tslib": "1.9.3" + }, + "dependencies": { + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "requires": { + "html-comment-regex": "1.1.1" + } + } + } + }, + "filestack-loader": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/filestack-loader/-/filestack-loader-3.0.4.tgz", + "integrity": "sha512-b6uOCWHd1gM0+5KBA1rA4qfEgTqyTr5umLM4bBWT4z98WUwxa6KzCiq+z0VnR4rN+NCx6kyZ/wLXjGcPU32TxQ==" + }, + "filestack-react": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filestack-react/-/filestack-react-2.0.0.tgz", + "integrity": "sha512-+lOqPx2XYsvm3pJutjlDTyG8fdrJAefN0QLi8crJfwI1dJffOjK84dnQJHQ9aQzxF37A1cHI8ZbPscv7w6bRTA==" + }, "fill-range": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", @@ -6516,6 +6559,11 @@ "handlebars": "4.0.11" } }, + "isutf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/isutf8/-/isutf8-2.0.2.tgz", + "integrity": "sha512-9lNbG6k+PE6vuFnPTHjhtX79/LDMQrXoFx1MOEjXXCyqFCSahWKAzuAb9G5ct+lfS12aQxNmC0tiZuyujvr4Uw==" + }, "iterall": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", @@ -10805,6 +10853,11 @@ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, + "spark-md5": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spark-md5/-/spark-md5-3.0.0.tgz", + "integrity": "sha1-NyIifFTi+vJLHcbZM8wUTm9xv+8=" + }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -11170,6 +11223,19 @@ "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=" }, + "tcomb": { + "version": "3.2.27", + "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.27.tgz", + "integrity": "sha512-XWdJW7F/M3YzXhDEUP8ycmNWoYymBtsHwCHoda0YF44RthJsls95TqDrmpAlC1sB/KXaCvkdBlcNRq+AaV6klA==" + }, + "tcomb-validation": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tcomb-validation/-/tcomb-validation-3.4.1.tgz", + "integrity": "sha512-urVVMQOma4RXwiVCa2nM2eqrAomHROHvWPuj6UkDGz/eb5kcy0x6P0dVt6kzpUZtYMNoAqJLWmz1BPtxrtjtrA==", + "requires": { + "tcomb": "3.2.27" + } + }, "term-size": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", @@ -11340,6 +11406,11 @@ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" }, + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", diff --git a/client/package.json b/client/package.json index 5b58271ac..0bd9cf86c 100644 --- a/client/package.json +++ b/client/package.json @@ -12,6 +12,8 @@ "apollo-link-error": "^1.1.0", "apollo-link-http": "^1.5.4", "auth0-lock": "^11.9.0", + "filestack-js": "^1.5.1", + "filestack-react": "^2.0.0", "graphql": "^14.0.0", "graphql-tag": "^2.9.2", "react": "^16.4.2", diff --git a/client/public/index.html b/client/public/index.html index 59bf85d0a..d5814b15e 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -32,6 +32,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> + @@ -52,15 +53,5 @@
- diff --git a/client/src/App.js b/client/src/App.js index 37711c7ae..fc381cad5 100644 --- a/client/src/App.js +++ b/client/src/App.js @@ -11,7 +11,8 @@ import { Route, BrowserRouter, Switch } from 'react-router-dom' // Material UI -import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles' +import { MuiThemeProvider } from '@material-ui/core/styles' +import theme from './theme' // Components @@ -26,11 +27,16 @@ import Modules from './components/Modules' import Contact from './components/Contact' import Logout from './components/Logout' import Post from './components/Post' -import Footer from './components/Footer' +// import Footer from './components/Footer' import Callback from './components/Callback' import Auth from './auth/Auth' +import { +// NavBar, + Footer +} from './views' + // Apollo Client Setup const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql', changeOrigin: true }) @@ -70,15 +76,6 @@ export const client = new ApolloClient({ export const auth = new Auth(result => console.log('auth result', result), client) -/* Here we change the default Material UI theme for Advanced Algos brand colors. */ - -const theme = createMuiTheme({ - palette: { - primary: { main: '#303036' }, // DARK. - secondary: { main: '#CC5835' } // RUSTED_RED. - } -}) - class App extends Component { render () { return ( diff --git a/client/src/assets/logos/advanced-algos/aa-logo-dark.svg b/client/src/assets/logos/advanced-algos/aa-logo-dark.svg new file mode 100644 index 000000000..479feeb86 --- /dev/null +++ b/client/src/assets/logos/advanced-algos/aa-logo-dark.svg @@ -0,0 +1 @@ +aa-logo-dark \ No newline at end of file diff --git a/client/src/assets/logos/advanced-algos/aa-logo-vert.svg b/client/src/assets/logos/advanced-algos/aa-logo-vert.svg new file mode 100644 index 000000000..442e9a7fd --- /dev/null +++ b/client/src/assets/logos/advanced-algos/aa-logo-vert.svg @@ -0,0 +1 @@ +aa-logo-vert \ No newline at end of file diff --git a/client/src/assets/logos/advanced-algos/aa-logo.png b/client/src/assets/logos/advanced-algos/aa-logo.png new file mode 100644 index 000000000..9701ee1c0 Binary files /dev/null and b/client/src/assets/logos/advanced-algos/aa-logo.png differ diff --git a/client/src/assets/logos/advanced-algos/favicon/android-chrome-192x192.png b/client/src/assets/logos/advanced-algos/favicon/android-chrome-192x192.png new file mode 100644 index 000000000..150043a06 Binary files /dev/null and b/client/src/assets/logos/advanced-algos/favicon/android-chrome-192x192.png differ diff --git a/client/src/assets/logos/advanced-algos/favicon/android-chrome-512x512.png b/client/src/assets/logos/advanced-algos/favicon/android-chrome-512x512.png new file mode 100644 index 000000000..d98b9ce8d Binary files /dev/null and b/client/src/assets/logos/advanced-algos/favicon/android-chrome-512x512.png differ diff --git a/client/src/assets/logos/advanced-algos/favicon/apple-touch-icon.png b/client/src/assets/logos/advanced-algos/favicon/apple-touch-icon.png new file mode 100644 index 000000000..95ec62746 Binary files /dev/null and b/client/src/assets/logos/advanced-algos/favicon/apple-touch-icon.png differ diff --git a/client/src/assets/logos/advanced-algos/favicon/browserconfig.xml b/client/src/assets/logos/advanced-algos/favicon/browserconfig.xml new file mode 100644 index 000000000..b3930d0f0 --- /dev/null +++ b/client/src/assets/logos/advanced-algos/favicon/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/client/src/assets/logos/advanced-algos/favicon/favicon-16x16.png b/client/src/assets/logos/advanced-algos/favicon/favicon-16x16.png new file mode 100644 index 000000000..99bda00af Binary files /dev/null and b/client/src/assets/logos/advanced-algos/favicon/favicon-16x16.png differ diff --git a/client/src/assets/logos/advanced-algos/favicon/favicon-32x32.png b/client/src/assets/logos/advanced-algos/favicon/favicon-32x32.png new file mode 100644 index 000000000..08c2369b0 Binary files /dev/null and b/client/src/assets/logos/advanced-algos/favicon/favicon-32x32.png differ diff --git a/client/src/assets/logos/advanced-algos/favicon/favicon.ico b/client/src/assets/logos/advanced-algos/favicon/favicon.ico new file mode 100644 index 000000000..cd2c5d6af Binary files /dev/null and b/client/src/assets/logos/advanced-algos/favicon/favicon.ico differ diff --git a/client/src/assets/logos/advanced-algos/favicon/html_code.html b/client/src/assets/logos/advanced-algos/favicon/html_code.html new file mode 100644 index 000000000..a66b72bb6 --- /dev/null +++ b/client/src/assets/logos/advanced-algos/favicon/html_code.html @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/client/src/assets/logos/advanced-algos/favicon/mstile-150x150.png b/client/src/assets/logos/advanced-algos/favicon/mstile-150x150.png new file mode 100644 index 000000000..78a06c05a Binary files /dev/null and b/client/src/assets/logos/advanced-algos/favicon/mstile-150x150.png differ diff --git a/client/src/assets/logos/advanced-algos/favicon/safari-pinned-tab.svg b/client/src/assets/logos/advanced-algos/favicon/safari-pinned-tab.svg new file mode 100644 index 000000000..db3ba7cde --- /dev/null +++ b/client/src/assets/logos/advanced-algos/favicon/safari-pinned-tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/logos/advanced-algos/favicon/site.webmanifest b/client/src/assets/logos/advanced-algos/favicon/site.webmanifest new file mode 100644 index 000000000..002445914 --- /dev/null +++ b/client/src/assets/logos/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/components/DescendentsTree.js b/client/src/components/DescendentsTree.js new file mode 100644 index 000000000..c381bc70c --- /dev/null +++ b/client/src/components/DescendentsTree.js @@ -0,0 +1,258 @@ +import React, { Component } from 'react' +import {graphql, compose} from 'react-apollo' +import {getDescendentsQuery} from '../queries/queries' + +// Materia UI + +import { withStyles } from '@material-ui/core/styles' +import Card from '@material-ui/core/Card' +import CardActionArea from '@material-ui/core/CardActionArea' +import CardActions from '@material-ui/core/CardActions' +import CardContent from '@material-ui/core/CardContent' +import CardMedia from '@material-ui/core/CardMedia' +import Typography from '@material-ui/core/Typography' +import Grid from '@material-ui/core/Grid' + +// Material UI Nestled Lists +import ListSubheader from '@material-ui/core/ListSubheader'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import Collapse from '@material-ui/core/Collapse'; +import ExpandLess from '@material-ui/icons/ExpandLess'; +import ExpandMore from '@material-ui/icons/ExpandMore'; +import StarBorder from '@material-ui/icons/StarBorder'; + +// Images +import PortraitImage from '../img/portrait.jpg' + +const styles = theme => ({ + card: { + maxWidth: 300, + minWidth: 300, + paddingTop: '30' + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9, + marginTop: '30', + minWidth: 300 + }, + typography: { + maxWidth: 345, + minWidth: 300, + paddingTop: '30' + }, + appBar: { + position: 'relative' + }, + flex: { + flex: 1 + }, + grid: { + paddingTop: '30', + marginTop: 30 + }, + list: { + width: '80%', + marginLeft: '10%', + marginTop: 25 + }, + button: { + margin: theme.spacing.unit, + marginTop: theme.spacing.unit * 3 + }, + nested: { + paddingLeft: theme.spacing.unit * 4, + }, +}) + +class DescendentsTree extends Component { + + constructor (props) { + super(props) + + this.state = { + id: '', + open: true + } + } + + handleClick = () => { + this.setState(state => ({ open: !state.open })); +}; + + displayIcons(amount) { + + switch (amount) { + case 1: return ( +
+ + + +
+ ) + case 2: return ( +
+ + + + + + +
+ ) + case 3: return ( +
+ + + + + + + + + +
+ ) + default: return(
) + } + } + + displayNestedListItems(node, level) { + + const { classes } = this.props + return node.descendents.map(descendent => { + return ( +
+ + {this.displayIcons(level)} + + + + + + {descendent.alias} + + + + + + + + + + + {this.displayNestedList(descendent, level)} +
+ ) + } + ) + } + + displayNestedList(descendent, level) { + const primaryText = "Children of " + descendent.alias + if (descendent.descendents !== undefined) { + if (descendent.descendents.length > 0) { + return( + + { + this.setState(state => ({ [descendent.alias]: !state[descendent.alias] })); + } + }> + + {this.state[descendent.alias] ? : } + + + {this.displayNestedListItems(descendent, level + 1)} + + + ) + } else { + return (
) + } + } + } + + + displayTopList () { + let data = this.props.data + const { classes } = this.props + + if (data.loading) { + return (
Loading Descendents...
) + } else { + if (data.descendents === undefined) { + return (
No Descendents to Display
) + } else { + return data.descendents.map(descendent => { + return ( +
+ + + {this.displayIcons(1)} + + + + + + {descendent.alias} + + + + + + + + + + + + {this.displayNestedList(descendent, 1)} + +
+ ) + }) + } + } + } + + render () { + const { classes } = this.props + return ( +
+ } + className={classes.list} + > + {this.displayTopList()} + +
+ ) + } +} + +export default compose( + graphql(getDescendentsQuery, { // What follows is the way to pass a parameter to a query. + options: (props) => { + return { + variables: { + name: 'getDescendentsQuery', + id: props.userId + } + } + }}), + withStyles(styles) +)(DescendentsTree) // This technique binds more than one query to a single component. diff --git a/client/src/components/Footer.js b/client/src/components/Footer.js deleted file mode 100644 index fb7254f07..000000000 --- a/client/src/components/Footer.js +++ /dev/null @@ -1,84 +0,0 @@ -import React, { Component } from 'react' -import { NavLink } from 'react-router-dom' -import { compose } from 'recompose' -import { withStyles } from '@material-ui/core/styles' -import Button from '@material-ui/core/Button' -import Grid from '@material-ui/core/Grid' - -// Images -import AALogo from '../img/aa-logo.png' - -const styles = theme => ({ - root: { - flexGrow: 1, - padding: 100 - }, - img: { - margin: 'auto', - display: 'block', - maxWidth: 120, - maxHeight: 24 - }, - button: { - margin: theme.spacing.unit - } -}) - -class Footer extends Component { - - constructor (props) { - super(props) - this.state = { - selected: '', - user: null - } - } - render () { - const { classes } = this.props - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - ) - } -} - -export default compose( - withStyles(styles) -)(Footer) diff --git a/client/src/components/Home.js b/client/src/components/Home.js index 73572c700..154667baf 100644 --- a/client/src/components/Home.js +++ b/client/src/components/Home.js @@ -2,9 +2,9 @@ import React, { Component } from 'react' import { compose } from 'recompose' import { withStyles } from '@material-ui/core/styles' import {LineChart} from 'react-easy-chart' -import Paper from '@material-ui/core/Paper' import Grid from '@material-ui/core/Grid' import Typography from '@material-ui/core/Typography' +import CssBaseline from '@material-ui/core/CssBaseline' const styles = theme => ({ root: { @@ -19,6 +19,26 @@ const styles = theme => ({ width: '100%', marginTop: 20, marginBottom: 20 + }, + '@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` } }) @@ -27,41 +47,63 @@ class Home extends Component { render () { const { classes } = this.props return ( - + + +
+
+ + Collaborate. Innovate. Evolve. + + + Welcome to Advanced Algos Users Module + +
+
+ - Welcome to the Users Module - + Intro + - This module is responsible for managing users across the Advanced Algos system. Here you can browse the Users Directory, search for users and get to their profiles. You can create your own User profile if you sign up, or manage it if you already have one by logging in. - + This module is responsible for managing users across the Advanced Algos system. Here you can browse the Users Directory, search for users and get to their profiles. You can create your own User profile if you sign up, or manage it if you already have one by logging in. + - Development Status - + Development Status + - This module is is currently under heavy development. Implementation started 1st of September 2018 and this is the result of the 1st sprint of 2 weeks of work. - + This module is is currently under heavy development. Implementation started 1st of September 2018 and this is the result of the 1st sprint of 2 weeks of work. + - What can you do? - + What can you do? + - Currently you can use the Browse section to list all registered users. Clicking on View Profile you can see some more info about that user. You can signup with your Github account and manage your profile information while logged in. You will find all features under develpment with a dialog indicating it is not ready. - + Currently you can use the Browse section to list all registered users. Clicking on View Profile you can see some more info about that user. You can signup with your Github account and manage your profile information while logged in. You will find all features under develpment with a dialog indicating it is not ready. + - What is next? - + What is next? + - The major features for the next sprint are: - + The major features for the next sprint are: +
  • 1. Managing profile images.
  • 2. Making sure that Auth0 integration server side is working well.
  • 3. Making sure that mutations can not be impersonated.
- Below, you can see some usage statistics of this module, in this case, the total amount of users over time. [data is not real yet.] - + Below, you can see some usage statistics of this module, in this case, the total amount of users over time. [data is not real yet.] + + />
-
+ ) } diff --git a/client/src/components/NavBar.js b/client/src/components/NavBar.js index e53b65521..f3139a725 100644 --- a/client/src/components/NavBar.js +++ b/client/src/components/NavBar.js @@ -20,7 +20,7 @@ import { Link } from 'react-router-dom' import LoggedInUser from './LoggedInUser' // Images -import AALogo from '../img/aa-logo-dark-8.png' +import AALogo from '../assets/logos/advanced-algos/aa-logo-dark.svg' const ModulesLink = props => const AboutLink = props => @@ -43,8 +43,8 @@ const styles = { img: { margin: 20, display: 'block', - maxWidth: 120, - maxHeight: 24 + maxWidth: 240, + maxHeight: 48 } } diff --git a/client/src/components/ProfileImages.js b/client/src/components/ProfileImages.js new file mode 100644 index 000000000..9df04d93c --- /dev/null +++ b/client/src/components/ProfileImages.js @@ -0,0 +1,322 @@ +import React from 'react'; +import {graphql, compose} from 'react-apollo' +import {updateUserImagesMutation, getUsersQuery} from '../queries/queries' +import Avatar from '@material-ui/core/Avatar'; +import classNames from 'classnames'; + +import ReactFilestack from 'filestack-react' + +// Material-ui + +import { withStyles } from '@material-ui/core/styles' +import Button from '@material-ui/core/Button'; +import Paper from '@material-ui/core/Paper' +import Grid from '@material-ui/core/Grid' +import Typography from '@material-ui/core/Typography' + +const options = { + accept: ['image/jpeg', 'image/jpg', 'image/png'], + maxFiles: 1, + imageDim: [300, 300], + maxSize: 0.25 * 1024 * 1024, // 1/4 of 1 Mb + storeTo: { + location: 's3', + }, +}; + +const styles = theme => ({ + + button: { + margin: theme.spacing.unit, + marginTop: theme.spacing.unit * 3 + }, + row: { + display: 'flex', + justifyContent: 'center', + }, + avatar: { + margin: 10, + }, + bigAvatar: { + width: 60, + height: 60, + }, + root: { + width: '50%', + flexGrow: 1, + padding: 10, + marginLeft: '25%', + marginTop: '2%' + }, + typography: { + width: '80%', + marginLeft: '10%', + marginTop: 40 + } +}) + +class ProfileImages extends React.Component { + + constructor (props) { + super(props) + + this.defaultValuesSet = false + + this.state = { + avatarHandle: '', + avatarChangeDate: '', + newAvatarHandle: '', + saveDisabled: true, + uploadToolDisabled: false, + showNewAvatar: false, + needToWait: false, + removeCurrentAvatar: false + } + } + + submitForm (e) { + e.preventDefault() + this.props.updateUserImagesMutation({ + variables: { + id: this.state.id, + avatarHandle: this.state.newAvatarHandle, + avatarChangeDate: this.state.avatarChangeDate + }, + refetchQueries: [{ query: getUsersQuery}] // This allow us to re run whatever queries are necesary after the mutation. + }) + + this.setState({saveDisabled: true, removeCurrentAvatar:true}) + + /* Before we are done, we need to update the state of the local storage. */ + + let user = JSON.parse(localStorage.getItem('loggedInUser')) + + user.avatarHandle = this.state.newAvatarHandle + user.avatarChangeDate = this.state.avatarChangeDate + + localStorage.setItem('loggedInUser', JSON.stringify(user)) + } + + componentWillMount () { + if (this.defaultValuesSet === false) { + let userData = localStorage.getItem('loggedInUser') + + if (userData === 'undefined') { return } + + let user = JSON.parse(userData) + this.defaultValuesSet = true + + /* To avoid console warning, we need to take care of the fields that are null. */ + + if (user.avatarHandle === null) { user.avatarHandle = '' } + if (user.avatarChangeDate === null) { user.avatarChangeDate = '' } + + /* Here we will check that the user does not change the avatar more often than once a day. */ + + let avatarChangeDate = (new Date(user.avatarChangeDate)).valueOf() + let now = (new Date()).valueOf() + let timePassed = now - avatarChangeDate + let oneDay = 1000 * 60 * 60 * 24 + + + + if (timePassed < oneDay) { + this.setState({ + needToWait: true + }) + } else { + this.setState({ + needToWait: false + }) + } + + /* Now we are ready to set the initial state. */ + + this.setState({ + id: user.id, + avatarHandle: user.avatarHandle, + avatarChangeDate: user.avatarChangeDate + }) + } + } + + onSuccess = (uploadResults) => { + console.log('onSuccess') + console.log(uploadResults) + + const handle = uploadResults.filesUploaded[0].handle + + let localDate = new Date() + let currentDate = new Date(Date.UTC( + localDate.getUTCFullYear(), + localDate.getUTCMonth(), + localDate.getUTCDate(), + localDate.getUTCHours(), + localDate.getUTCMinutes(), + localDate.getUTCSeconds(), + localDate.getUTCMilliseconds()) + ) + + currentDate.toISOString() + + this.setState( + { + newAvatarHandle: handle, + avatarChangeDate: currentDate, + showNewAvatar: true, + uploadToolDisabled: true, + saveDisabled: false + } + ) + } + + onError = (err) => { + console.log('onError') + console.log(err) + } + + onRemoveSuccess = (err) => { + console.log('onRemoveSuccess') + console.log(err) + } + + showCurrentAvatar() { + + const { classes } = this.props + + if (this.state.removeCurrentAvatar === false) { + + return ( +
+ + This is your current Avatar: + + + + + + + +
+ ) + + } else { + + return ( +
+ +
+ ) + } + + + } + + showNewAvatar() { + if (this.state.showNewAvatar === true) { + const { classes } = this.props + return ( +
+ + This is how your new avatar will look like. Press save to proceed with the change. + + + + + + + +
+ ) + } + } + + showUploadTool() { + const { classes } = this.props + if (this.state.needToWait === true) { + return ( +
+ + You updated your Avatar in the last 24 hours. As a protection against denial of service attacks, the system does not allow users to update their Avatar in less than 24 hs since their last update. Please come back tomorrow. + +
+ ) + } else { + return( +
+ + If you want, you can choose a new one by picking an image from your computer, camera or the web, and crop it with the following tool. + + + + + + +
+ ) + } + } + + render() { + const { classes } = this.props + return ( + + + Profile Images + +
+ + {this.showCurrentAvatar()} + {this.showUploadTool()} + {this.showNewAvatar()} + + + + + + +
+
+ + ); + } +} + +export default compose( + graphql(updateUserImagesMutation, {name: 'updateUserImagesMutation'}), + withStyles(styles) +)(ProfileImages) // This technique binds more than one query to a single component. diff --git a/client/src/components/UserUpdate.js b/client/src/components/ProfileSheet.js similarity index 99% rename from client/src/components/UserUpdate.js rename to client/src/components/ProfileSheet.js index a3c2f2143..20510a91b 100644 --- a/client/src/components/UserUpdate.js +++ b/client/src/components/ProfileSheet.js @@ -62,7 +62,7 @@ const styles = theme => ({ } }) -class UserUpdate extends Component { +class ProfileSheet extends Component { constructor (props) { super(props) @@ -467,4 +467,4 @@ export default compose( graphql(getRolesQuery, {name: 'getRolesQuery'}), graphql(updateUserMutation, {name: 'updateUserMutation'}), withStyles(styles) -)(UserUpdate) // This technique binds more than one query to a single component. +)(ProfileSheet) // This technique binds more than one query to a single component. diff --git a/client/src/components/Search.js b/client/src/components/Search.js index 9f9c9c734..90f15f758 100644 --- a/client/src/components/Search.js +++ b/client/src/components/Search.js @@ -1,48 +1,37 @@ -import React from 'react'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; +import React, { Component } from 'react' +import UserSearch from './UserSearch' +import {compose} from 'react-apollo' -class Search extends React.Component { - state = { - open: true, - }; +// Materia UI - handleClickOpen = () => { - this.setState({ open: true }); - }; +import { withStyles } from '@material-ui/core/styles' +import Paper from '@material-ui/core/Paper' - handleClose = () => { - this.setState({ open: false }); - }; +const styles = theme => ({ + root: { + flexGrow: 1, + padding: 60, + margin: 2 + } +}) - render() { +class Search extends Component { + + constructor (props) { + super(props) + this.state = { + } + } + render () { + const { classes } = this.props return ( -
- - {"Section under development."} - - - The functionality of this section has not been developed yet. We expect to have it for October 2018. Thanks for your understanding. - - - - - - -
- ); + + + + ) } } -export default Search; +export default compose( + withStyles(styles) +)(Search) diff --git a/client/src/components/UserImages.js b/client/src/components/UserImages.js deleted file mode 100644 index 559d91787..000000000 --- a/client/src/components/UserImages.js +++ /dev/null @@ -1,48 +0,0 @@ -import React from 'react'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; - -class UserImages extends React.Component { - state = { - open: true, - }; - - handleClickOpen = () => { - this.setState({ open: true }); - }; - - handleClose = () => { - this.setState({ open: false }); - }; - - render() { - return ( -
- - {"Section under development."} - - - The functionality of this section has not been developed yet. We expect to have it for October 2018. Thanks for your understanding. - - - - - - -
- ); - } -} - -export default UserImages; diff --git a/client/src/components/UserList.js b/client/src/components/UserList.js index a9db8e7a1..9c28d8589 100644 --- a/client/src/components/UserList.js +++ b/client/src/components/UserList.js @@ -143,7 +143,9 @@ class UserList extends Component { + + diff --git a/client/src/components/UserProfile.js b/client/src/components/UserProfile.js index 65bf487af..489322eca 100644 --- a/client/src/components/UserProfile.js +++ b/client/src/components/UserProfile.js @@ -6,7 +6,6 @@ import {getUserProfileQuery} from '../queries/queries' import { withStyles } from '@material-ui/core/styles' import Grid from '@material-ui/core/Grid' -import Paper from '@material-ui/core/Paper' import Card from '@material-ui/core/Card' import CardActionArea from '@material-ui/core/CardActionArea' import CardContent from '@material-ui/core/CardContent' @@ -24,15 +23,14 @@ const styles = theme => ({ margin: 2 }, card: { - maxWidth: 800, - minWidth: 800, + width: '80%', paddingTop: '30' }, media: { height: 0, paddingTop: '56.25%', // 16:9, marginTop: '30', - minWidth: 800 + minWidth: '80%' }, grid: { paddingTop: '30', @@ -92,13 +90,10 @@ class UserProfile extends Component { } render () { - const { classes } = this.props return ( - - - {this.displayUserProfile()} - - + + {this.displayUserProfile()} + ) } } diff --git a/client/src/components/UserSearch.js b/client/src/components/UserSearch.js new file mode 100644 index 000000000..688bef074 --- /dev/null +++ b/client/src/components/UserSearch.js @@ -0,0 +1,267 @@ +import React, { Component } from 'react' +import {graphql, compose} from 'react-apollo' +import {getUsersBySearchFields} from '../queries/queries' + +// Materia UI + +import { withStyles } from '@material-ui/core/styles' +import Card from '@material-ui/core/Card' +import CardActionArea from '@material-ui/core/CardActionArea' +import CardActions from '@material-ui/core/CardActions' +import CardContent from '@material-ui/core/CardContent' +import CardMedia from '@material-ui/core/CardMedia' +import Button from '@material-ui/core/Button' +import Typography from '@material-ui/core/Typography' +import Grid from '@material-ui/core/Grid' + +import TextField from '@material-ui/core/TextField' + +// Images +import PortraitImage from '../img/portrait.jpg' + +const styles = theme => ({ + card: { + maxWidth: 150, + minWidth: 150, + paddingTop: '30', + marginBottom: 40 + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9, + marginTop: '30', + minWidth: 300 + }, + typography: { + maxWidth: 345, + minWidth: 300, + paddingTop: '30' + }, + appBar: { + position: 'relative' + }, + flex: { + flex: 1 + }, + grid: { + paddingTop: '30', + marginTop: 30 + }, + inputField: { + width: '80%', + marginLeft: '10%', + marginTop: 25 + }, + formTypography: { + width: '80%', + marginLeft: '10%', + marginTop: 40 + }, + button: { + margin: theme.spacing.unit, + marginTop: theme.spacing.unit * 3 + } +}) + +class UserSearch extends Component { + + constructor (props) { + super(props) + + this.state = { + alias: '', + firstName: '', + middleName: '', + lastName: '' + } + } + + submitForm (e) { + e.preventDefault() + + this.props.getUsersBySearchFields.variables.alias = this.state.alias + this.props.getUsersBySearchFields.variables.firstName = this.state.firstName + this.props.getUsersBySearchFields.variables.middleName = this.state.middleName + this.props.getUsersBySearchFields.variables.lastName = this.state.lastName + + this.props.getUsersBySearchFields.refetch({}) + } + + handleTextField (e) { + /* + We will do some basic input validations here. If something is wrong we will turn on the error sign on the textfield. + */ + + let value = e.target.value + + switch (e.target.id) { + case 'alias': + if (value.length > 30) { + this.setState({aliasError: true}) + } else { + this.setState({aliasError: false, alias: value}) + } + break + case 'firstName': + if (value.length > 30) { + this.setState({firstNameError: true}) + } else { + this.setState({firstNameError: false, firstName: value}) + } + break + case 'middleName': + if (value.length > 30) { + this.setState({middleNameError: true}) + } else { + this.setState({middleNameError: false, middleName: value}) + } + break + case 'lastName': + if (value.length > 50) { + this.setState({lastNameError: true}) + } else { + this.setState({lastNameError: false, lastName: value}) + } + break + default: + } + } + + selectText () { + if (this.props.selectButton === true) { + return ({this.props.selectText}) + } else { + return (
) + } + } + + selectButton (user) { + if (this.props.selectButton === true) { + return ( + + ) + } else { + return (
) + } + } + + displayUsers () { + let data = this.props.getUsersBySearchFields + const { classes } = this.props + + if (data.loading) { + return (
Loading Users...
) + } else { + if (data.usersSearch === undefined) { + return (
No Users to Display
) + } else { + return data.usersSearch.map(user => { + return ( + + + { + this.setState({ selected: user.id}) + } + }> + + + + + {user.alias} + + + + + + + {this.selectButton(user)} + + + + + + + ) + }) + } + } + } + + render () { + const { classes } = this.props + return ( +
+
+ + + Use any of these fileds to search for users. {this.selectText()} + + + + + + + + + + + + + + + + + + + {this.displayUsers()} + +
+ ) + } +} + +export default compose( + graphql(getUsersBySearchFields, {name: 'getUsersBySearchFields'}), + withStyles(styles) +)(UserSearch) // This technique binds more than one query to a single component. diff --git a/client/src/components/UserTabs.js b/client/src/components/UserTabs.js index 32a5d258c..28b3ae94c 100644 --- a/client/src/components/UserTabs.js +++ b/client/src/components/UserTabs.js @@ -4,14 +4,18 @@ import { withStyles } from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; -import UpdateIcon from '@material-ui/icons/Create'; -import ImageIcon from '@material-ui/icons/Wallpaper'; +import ProfileSheetIcon from '@material-ui/icons/Create'; +import ProfileImagesIcon from '@material-ui/icons/Wallpaper'; +import YourReferrerIcon from '@material-ui/icons/AccessibilityNew'; +import DescendentsIcon from '@material-ui/icons/DeviceHub'; import Typography from '@material-ui/core/Typography'; // Components -import UserUpdate from './UserUpdate'; -import UserImages from './UserImages'; +import ProfileSheet from './ProfileSheet'; +import ProfileImages from './ProfileImages'; +import YourReferrer from './YourReferrer'; +import YourDescendents from './YourDescendents'; function TabContainer(props) { return ( @@ -59,12 +63,16 @@ class UserTabs extends React.Component { indicatorColor="primary" textColor="primary" > - } /> - } /> + } /> + } /> + } /> + } /> - {value === 0 && } - {value === 1 && } + {value === 0 && } + {value === 1 && } + {value === 2 && } + {value === 3 && }
); } diff --git a/client/src/components/YourDescendents.js b/client/src/components/YourDescendents.js new file mode 100644 index 000000000..d069f9311 --- /dev/null +++ b/client/src/components/YourDescendents.js @@ -0,0 +1,85 @@ +import React, { Component } from 'react' +import {compose} from 'react-apollo' + +// Materia UI + +import { withStyles } from '@material-ui/core/styles' +import Typography from '@material-ui/core/Typography' +import Grid from '@material-ui/core/Grid' +import Paper from '@material-ui/core/Paper' + +// components +import DescendentsTree from './DescendentsTree' + +const styles = theme => ({ + root: { + width: '50%', + flexGrow: 1, + padding: 10, + marginLeft: '25%', + marginTop: '2%' + }, + typography: { + width: '80%', + marginLeft: '10%', + marginTop: 40, + marginBottom: 40 + }, + grid: { + paddingTop: '30', + marginTop: 30 + } +}) + +class YourDescendents extends Component { + + constructor (props) { + super(props) + + this.defaultValuesSet = false + + this.state = { + id: '' + } + } + + componentWillMount () { + if (this.defaultValuesSet === false) { + let userData = localStorage.getItem('loggedInUser') + + if (userData === 'undefined') { return } + + let user = JSON.parse(userData) + this.defaultValuesSet = true + + /* Now we are ready to set the initial state. */ + this.setState({ + id: user.id + }) + } + } + + render () { + const { classes } = this.props + + return ( + + + Your Descendents + + + + These are your decendents within the project. Your children referred you as the one who brought them to the project, while + your grandchildren referred your children and so on. + + + + + + ) + } +} + +export default compose( + withStyles(styles) +)(YourDescendents) // This technique binds more than one query to a single component. diff --git a/client/src/components/YourReferrer.js b/client/src/components/YourReferrer.js new file mode 100644 index 000000000..946f2e4e7 --- /dev/null +++ b/client/src/components/YourReferrer.js @@ -0,0 +1,146 @@ +import React, { Component } from 'react' +import {graphql, compose} from 'react-apollo' +import {updateReferrerMutation} from '../queries/queries' + +// components + +import UserSearch from './UserSearch' +import UserProfile from './UserProfile' + +// Material-ui + +import { withStyles } from '@material-ui/core/styles' +import Paper from '@material-ui/core/Paper' +import Typography from '@material-ui/core/Typography' + +const styles = theme => ({ + root: { + width: '50%', + flexGrow: 1, + padding: 10, + marginLeft: '25%', + marginTop: '2%' + }, + typography: { + width: '80%', + marginLeft: '10%', + marginTop: 40, + marginBottom: 40 + }, + button: { + margin: theme.spacing.unit, + marginTop: theme.spacing.unit * 3 + }, + inputField: { + width: '80%', + marginLeft: '10%', + marginTop: 25 + } +}) + +class YourReferrer extends Component { + + constructor (props) { + super(props) + + this.defaultValuesSet = false + + this.state = { + id: '', + referrerId: '', + firstNameError: false, + middleNameError: false, + lastNameError: false, + aliasError: false + } + } + + userSelected (referrerUser) { + this.setState({referrerId: referrerUser.id}) + + this.props.updateReferrerMutation({ + variables: { + id: this.state.id, + referrerId: referrerUser.id + } + }) + + /* Before we are done, we need to update the state of the local storage. */ + + let user = JSON.parse(localStorage.getItem('loggedInUser')) + + user.referrerId = referrerUser.id + + localStorage.setItem('loggedInUser', JSON.stringify(user)) + } + + renderMode () { + const { classes } = this.props + + if (this.state.referrerId !== null) { + return ( +
+ + This is the user who introduced you to the project. + + + + + + This information can not be changed. + +
+ ) + } else { + return ( +
+ + We know that a group often becomes more intelligent when the number of people in the group increases. + To incentivize members of the community to invite more people to join we have developed this referral + program in which you can specify who referred you to the project, and see the members who pointed at + you as their referrer. Later this information will be available at your user profile and add to your + overall reputation within the project. + + + +
+ ) + } + } + + componentWillMount () { + if (this.defaultValuesSet === false) { + let userData = localStorage.getItem('loggedInUser') + + if (userData === 'undefined') { return } + + let user = JSON.parse(userData) + this.defaultValuesSet = true + + /* Now we are ready to set the initial state. */ + + this.setState({ + id: user.id, + referrerId: user.referrerId + }) + } + } + + render () { + const { classes } = this.props + return ( + + + Your Referrer + + + {this.renderMode()} + + ) + } +} + +export default compose( + graphql(updateReferrerMutation, {name: 'updateReferrerMutation'}), + withStyles(styles) +)(YourReferrer) // This technique binds more than one query to a single component. diff --git a/client/src/components/home/ChartUsers.js b/client/src/components/home/ChartUsers.js deleted file mode 100644 index d9eca93ce..000000000 --- a/client/src/components/home/ChartUsers.js +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react'; - -import { - XYPlot, - XAxis, - YAxis, - HorizontalGridLines, - VerticalGridLines, - LineSeries -} from 'index'; - -const data = []; - -for (let i = 0; i < 20; i++) { - const series = []; - for (let j = 0; j < 100; j++) { - series.push({x: j, y: (i / 10 + 1) * Math.sin(Math.PI * (i + j) / 50)}); - } - data.push({color: i, key: i, data: series, opacity: 0.8}); -} - -export default class Example extends React.Component { - render() { - return ( - - - - - - {data.map(props => )} - - ); - } -} diff --git a/client/src/components/index.js b/client/src/components/index.js new file mode 100644 index 000000000..195ec74ff --- /dev/null +++ b/client/src/components/index.js @@ -0,0 +1,21 @@ +import NavBar from './navbar' +import Footer from './footer' +import Callback from './callback' +import Home from './home' +import About from './about' +import Dashboard from './dashboard' +import Teams from './teams' + +import { theme, globalStyles } from './theme' + +export { + NavBar, + Footer, + Callback, + Home, + About, + Dashboard, + Teams, + theme, + globalStyles +} diff --git a/client/src/components/index0.js b/client/src/components/index0.js new file mode 100644 index 000000000..195ec74ff --- /dev/null +++ b/client/src/components/index0.js @@ -0,0 +1,21 @@ +import NavBar from './navbar' +import Footer from './footer' +import Callback from './callback' +import Home from './home' +import About from './about' +import Dashboard from './dashboard' +import Teams from './teams' + +import { theme, globalStyles } from './theme' + +export { + NavBar, + Footer, + Callback, + Home, + About, + Dashboard, + Teams, + theme, + globalStyles +} diff --git a/client/src/queries/queries.js b/client/src/queries/queries.js index 30eef63e6..2071246a1 100644 --- a/client/src/queries/queries.js +++ b/client/src/queries/queries.js @@ -16,7 +16,6 @@ const getUsersQuery = gql` } } ` - const getUserProfileQuery = gql` query($id: ID){ user (id: $id){ @@ -26,8 +25,7 @@ query($id: ID){ middleName lastName bio - email - emailVerified + avatarHandle role { id name @@ -35,11 +33,11 @@ query($id: ID){ } } ` - const getUserByAuthIdQuery = gql` query($authId: String){ userByAuthId (authId: $authId){ id + referrerId alias firstName middleName @@ -50,13 +48,60 @@ query($authId: String){ isDeveloper isDataAnalyst isTrader + avatarHandle + avatarChangeDate role { id } } } ` - +const getUsersBySearchFields = gql` +query( + $alias: String, + $firstName: String, + $middleName: String, + $lastName: String +){ + usersSearch (alias: $alias, firstName: $firstName, middleName: $middleName, lastName: $lastName){ + id + alias + firstName + middleName + lastName + } +} +` +const getDescendentsQuery = gql` +query( + $id: String +){ + descendents (id: $id){ + id + alias + firstName + middleName + lastName + referrerId + descendents { + id + alias + firstName + middleName + lastName + referrerId + descendents { + id + alias + firstName + middleName + lastName + referrerId + } + } + } +} +` const getRolesQuery = gql` { roles { @@ -65,7 +110,6 @@ const getRolesQuery = gql` } } ` - const updateUserMutation = gql` mutation( $id: ID!, @@ -96,4 +140,48 @@ mutation( } } ` -export {getUsersQuery, getUserByAuthIdQuery, getRolesQuery, updateUserMutation, getUserProfileQuery} +const updateReferrerMutation = gql` +mutation( + $id: ID!, + $referrerId:String! +) + { + updateUserReferrer ( + id: $id, + referrerId:$referrerId + ) + { + id + referrerId + } +} +` +const updateUserImagesMutation = gql` +mutation( + $id: ID!, + $avatarHandle:String, + $avatarChangeDate:String +) + { + updateUserImages ( + id: $id, + avatarHandle:$avatarHandle, + avatarChangeDate: $avatarChangeDate + ) + { + id + alias + } +} +` +export { + getUsersQuery, + getUserByAuthIdQuery, + getRolesQuery, + getUserProfileQuery, + getUsersBySearchFields, + getDescendentsQuery, + updateUserMutation, + updateReferrerMutation, + updateUserImagesMutation +} diff --git a/client/src/theme.js b/client/src/theme.js new file mode 100644 index 000000000..1d7900e4a --- /dev/null +++ b/client/src/theme.js @@ -0,0 +1,70 @@ +import { createMuiTheme } from '@material-ui/core/styles' + +export const theme = createMuiTheme({ + palette: { + primary: { main: '#303036' }, // DARK. + secondary: { main: '#CC5835' } // RUSTED_RED. + }, + 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/views/footer/index.js b/client/src/views/footer/index.js new file mode 100644 index 000000000..1ebd618eb --- /dev/null +++ b/client/src/views/footer/index.js @@ -0,0 +1,129 @@ +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/logos/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 ( +
+ + + + Pages + + + + Home + + + + + Users + + + + + About + + + + + + + Support + + + + + Documentation + + + + + Telegram + + + + + + Modules + + + + Teams + + + + + Users + + + + + KeyVault + + + + + + + + + +
+ ) +} + +Footer.propTypes = { + classes: PropTypes.object.isRequired +} + +export default withStyles(styles)(Footer) diff --git a/client/src/views/index.js b/client/src/views/index.js new file mode 100644 index 000000000..f6e4afc13 --- /dev/null +++ b/client/src/views/index.js @@ -0,0 +1,21 @@ +// import NavBar from './navbar' +import Footer from './footer' +// import Callback from './callback' +// import Home from './home' +// import About from './about' +// import Dashboard from './dashboard' +// import Teams from './teams' + +// import { theme, globalStyles } from './theme' + +export { +// NavBar, + Footer +// Callback, +// Home, +// About, +// Dashboard, +// Teams, +// theme, +// globalStyles +} diff --git a/client/src/views/navbar/LoggedIn.js b/client/src/views/navbar/LoggedIn.js new file mode 100644 index 000000000..e8408d1b6 --- /dev/null +++ b/client/src/views/navbar/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) // This binds the querty to the component diff --git a/client/src/views/navbar/LoggedInMenu.js b/client/src/views/navbar/LoggedInMenu.js new file mode 100644 index 000000000..574a8e0ed --- /dev/null +++ b/client/src/views/navbar/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/navbar/LoggedOut.js b/client/src/views/navbar/LoggedOut.js new file mode 100644 index 000000000..ceb40ac28 --- /dev/null +++ b/client/src/views/navbar/LoggedOut.js @@ -0,0 +1,15 @@ +import React from 'react' +import PropTypes from 'prop-types' +import Button from '@material-ui/core/Button' + +export const LoggedOut = ({ auth }) => ( +
auth.login()}> + +
+) + +LoggedOut.propTypes = { + auth: PropTypes.object.isRequired +} + +export default LoggedOut // This binds the querty to the component diff --git a/client/src/views/navbar/index.js b/client/src/views/navbar/index.js new file mode 100644 index 000000000..f144156e0 --- /dev/null +++ b/client/src/views/navbar/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/logos/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 NavBar 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 ? ( + + + + + + + ) : ( + + )} + + +
+ ) + } +} + +NavBar.propTypes = { + classes: PropTypes.object.isRequired, + auth: PropTypes.object.isRequired +} + +export default withStyles(styles)(NavBar) diff --git a/server/models/user.js b/server/models/user.js index 372af6bbb..b00c278e6 100644 --- a/server/models/user.js +++ b/server/models/user.js @@ -6,6 +6,8 @@ const Schema = mongoose.Schema const userSchema = new Schema({ authId: String, + referrerId: String, + creationDate: String, alias: String, firstName: String, middleName: String, @@ -16,7 +18,9 @@ const userSchema = new Schema({ isDeveloper: Boolean, isTrader: Boolean, isDataAnalyst: Boolean, - roleId: String + roleId: String, + avatarHandle: String, + avatarChangeDate: String }) global.UserSchema = global.UserSchema || mongoose.model('User', userSchema) diff --git a/server/schema/schema.js b/server/schema/schema.js index 584f19f0a..99049d0b8 100644 --- a/server/schema/schema.js +++ b/server/schema/schema.js @@ -30,6 +30,7 @@ const UserType = new GraphQLObjectType({ fields: () => ({ id: { type: GraphQLID}, authId: { type: GraphQLString}, + referrerId: {type: GraphQLString}, alias: {type: GraphQLString}, firstName: {type: GraphQLString}, middleName: {type: GraphQLString}, @@ -40,6 +41,8 @@ const UserType = new GraphQLObjectType({ isDeveloper: {type: GraphQLInt}, isTrader: {type: GraphQLInt}, isDataAnalyst: {type: GraphQLInt}, + avatarHandle: {type: GraphQLString}, + avatarChangeDate: {type: GraphQLString}, role: { type: RoleType, resolve (parent, args) { @@ -49,6 +52,24 @@ const UserType = new GraphQLObjectType({ }) }) +const DescendentType = new GraphQLObjectType({ + name: 'Descendent', + fields: () => ({ + id: { type: GraphQLID}, + referrerId: {type: GraphQLString}, + alias: {type: GraphQLString}, + firstName: {type: GraphQLString}, + middleName: {type: GraphQLString}, + lastName: {type: GraphQLString}, + descendents: { + type: new GraphQLList(DescendentType), + resolve (parent, args) { + return User.find({referrerId: parent.id}) + } + } + }) +}) + const RoleType = new GraphQLObjectType({ name: 'Role', fields: () => ({ @@ -127,6 +148,33 @@ const RootQuery = new GraphQLObjectType({ resolve (parent, args) { return roles } + }, + usersSearch: { + type: new GraphQLList(UserType), + args: {alias: {type: GraphQLString}, firstName: {type: GraphQLString}, middleName: {type: GraphQLString}, lastName: {type: GraphQLString}}, + resolve (parent, args) { + if (INFO_LOG === true) { console.log('[INFO] ' + MODULE_NAME + ' -> RootQuery -> usersSearch -> resolve -> Entering function.') } + + let mongoQuery = { $or: [] } + + if (args.alias !== null && args.alias !== '') { mongoQuery.$or.push({alias: args.alias}) } + if (args.firstName !== null && args.firstName !== '') { mongoQuery.$or.push({firstName: args.firstName}) } + if (args.middleName !== null && args.middleName !== '') { mongoQuery.$or.push({middleName: args.middleName}) } + if (args.lastName !== null && args.lastName !== '') { mongoQuery.$or.push({lastName: args.lastName}) } + + if (mongoQuery.$or.length === 0) { mongoQuery = {} } + + return User.find(mongoQuery) + } + }, + descendents: { + type: new GraphQLList(DescendentType), + args: {id: {type: GraphQLString}}, + resolve (parent, args) { + if (INFO_LOG === true) { console.log('[INFO] ' + MODULE_NAME + ' -> RootQuery -> descendents -> resolve -> Entering function.') } + + return User.find({referrerId: args.id}) + } } } }) @@ -195,6 +243,45 @@ const Mutation = new GraphQLObjectType({ roleId: args.roleId } + return User.update(key, updatedUser) + } + }, + updateUserImages: { + type: UserType, + args: { + id: {type: new GraphQLNonNull(GraphQLID)}, + avatarHandle: {type: GraphQLString}, + avatarChangeDate: {type: GraphQLString} + }, + resolve (parent, args) { + let key = { + _id: args.id + } + + let updatedUser = { + avatarHandle: args.avatarHandle, + avatarChangeDate: args.avatarChangeDate + } + + return User.update(key, updatedUser) + } + }, + updateUserReferrer: { + type: UserType, + args: { + id: {type: new GraphQLNonNull(GraphQLID)}, + referrerId: {type: GraphQLString} + }, + resolve (parent, args) { + let key = { + _id: args.id, + referrerId: null + } + + let updatedUser = { + referrerId: args.referrerId + } + return User.update(key, updatedUser) } } @@ -345,9 +432,21 @@ function authenticate (encodedToken, callBackFunction) { return } + let localDate = new Date() + let creationDate = new Date(Date.UTC( + localDate.getUTCFullYear(), + localDate.getUTCMonth(), + localDate.getUTCDate(), + localDate.getUTCHours(), + localDate.getUTCMinutes(), + localDate.getUTCSeconds(), + localDate.getUTCMilliseconds()) + ) + let newUser = new User({ alias: alias, authId: authId, + creationDate: creationDate.toISOString(), email: email, emailVerified: emailVerified, roleId: '1'