Merge Complete
No files matched your search
@@ -0,0 +1,18 @@
|
||||
import React from 'react'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
const BannerTopBar = ({
|
||||
size,
|
||||
backgroundUrl,
|
||||
title,
|
||||
text
|
||||
}) => (
|
||||
<div className={`bannerTopBar ${size}`} style={backgroundUrl ? { backgroundImage: `url(${backgroundUrl})` } : {}}>
|
||||
<div className='caption'>
|
||||
<Typography className='title' variant='h1' align='center'>{title}</Typography>
|
||||
<Typography className='text' variant='h2' align='center'>{text}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default BannerTopBar
|
||||
@@ -0,0 +1,24 @@
|
||||
import React, { Component } from 'react'
|
||||
import UserList from './UserList'
|
||||
import BannerTopBar from './BannerTopBar'
|
||||
|
||||
class Browse extends Component {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BannerTopBar
|
||||
size='medium'
|
||||
title='Users Directory'
|
||||
text='Everyone involved in Advanced Algos is here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/users.jpg'
|
||||
/>
|
||||
<div className='container'>
|
||||
<UserList />
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Browse
|
||||
@@ -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 UserDefaultPicture from '../img/user-default-pic.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 (
|
||||
<div>
|
||||
<ListItemIcon>
|
||||
<StarBorder />
|
||||
</ListItemIcon>
|
||||
</div>
|
||||
)
|
||||
case 2: return (
|
||||
<div>
|
||||
<ListItemIcon>
|
||||
<StarBorder />
|
||||
</ListItemIcon>
|
||||
<ListItemIcon>
|
||||
<StarBorder />
|
||||
</ListItemIcon>
|
||||
</div>
|
||||
)
|
||||
case 3: return (
|
||||
<div>
|
||||
<ListItemIcon>
|
||||
<StarBorder />
|
||||
</ListItemIcon>
|
||||
<ListItemIcon>
|
||||
<StarBorder />
|
||||
</ListItemIcon>
|
||||
<ListItemIcon>
|
||||
<StarBorder />
|
||||
</ListItemIcon>
|
||||
</div>
|
||||
)
|
||||
default: return(<div/>)
|
||||
}
|
||||
}
|
||||
|
||||
displayNestedListItems(node, level) {
|
||||
|
||||
const { classes } = this.props
|
||||
return node.descendents.map(descendent => {
|
||||
return (
|
||||
<div key={descendent.alias}>
|
||||
<ListItem button className={classes.nested}>
|
||||
{this.displayIcons(level)}
|
||||
<Card className={classes.card}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
image={UserDefaultPicture}
|
||||
title='User Profile'
|
||||
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant='headline' component='h2'>
|
||||
{descendent.alias}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<Grid container justify='center' spacing={8}>
|
||||
<Grid item />
|
||||
</Grid>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</ListItem>
|
||||
{this.displayNestedList(descendent, level)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
displayNestedList(descendent, level) {
|
||||
const primaryText = "Children of " + descendent.alias
|
||||
if (descendent.descendents !== undefined) {
|
||||
if (descendent.descendents.length > 0) {
|
||||
return(
|
||||
<List component="div" disablePadding>
|
||||
<ListItem button onClick={ () => {
|
||||
this.setState(state => ({ [descendent.alias]: !state[descendent.alias] }));
|
||||
}
|
||||
}>
|
||||
<ListItemText inset primary={primaryText} />
|
||||
{this.state[descendent.alias] ? <ExpandLess /> : <ExpandMore />}
|
||||
</ListItem>
|
||||
<Collapse in={this.state[descendent.alias]} timeout="auto" unmountOnExit>
|
||||
{this.displayNestedListItems(descendent, level + 1)}
|
||||
</Collapse>
|
||||
</List>
|
||||
)
|
||||
} else {
|
||||
return (<div/>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
displayTopList () {
|
||||
let data = this.props.data
|
||||
const { classes } = this.props
|
||||
|
||||
if (data.loading) {
|
||||
return (<div> Loading Descendents... </div>)
|
||||
} else {
|
||||
if (data.users_Descendents === undefined) {
|
||||
return (<div> No Descendents to Display </div>)
|
||||
} else {
|
||||
return data.users_Descendents.map(descendent => {
|
||||
return (
|
||||
<div key={descendent.alias}>
|
||||
|
||||
<ListItem button>
|
||||
{this.displayIcons(1)}
|
||||
<Card className={classes.card}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
image={UserDefaultPicture}
|
||||
title='User Profile'
|
||||
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant='headline' component='h2'>
|
||||
{descendent.alias}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<Grid container justify='center' spacing={8}>
|
||||
<Grid item />
|
||||
</Grid>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</ListItem>
|
||||
|
||||
{this.displayNestedList(descendent, 1)}
|
||||
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<div>
|
||||
<List
|
||||
component="nav"
|
||||
subheader={<ListSubheader component="div"></ListSubheader>}
|
||||
className={classes.list}
|
||||
>
|
||||
{this.displayTopList()}
|
||||
</List>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,41 @@
|
||||
import React, { Component } from 'react'
|
||||
import BannerTopBar from './BannerTopBar'
|
||||
import { Typography } from '@material-ui/core'
|
||||
|
||||
class Home extends Component {
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BannerTopBar
|
||||
size='big'
|
||||
title='Users Module'
|
||||
text='Responsible for all human users of the Advanced Algos Platform.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/users.jpg'
|
||||
/>
|
||||
<div className='homePage container'>
|
||||
<Typography variant='h1' align='center' className='title'>Welcome to the Users Module!</Typography>
|
||||
<Typography variant='h2' align='center' className='subtitle'>The human component of the Advanced Algos Ecosystem.</Typography>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
The Advanced Algos Ecosystem is made out of humans, financial beings and other entities such as teams.
|
||||
</Typography>
|
||||
<Typography align='justify'>
|
||||
Humans are either <strong>users</strong> or <strong>guests</strong>. If you haven’t signed up yet or you are logged off, then you are acting as a guest, with limited features. Please sign-up or log-in for a richer experience.
|
||||
</Typography>
|
||||
</div>
|
||||
<div className='column'>
|
||||
<Typography align='justify'>
|
||||
Users may have different roles, such as developer, trader, analyst, etc. Each of these roles usually have personalized features throughout the system.
|
||||
</Typography>
|
||||
<Typography align='justify'>
|
||||
The Users Module allows you to handle all of your details as a user of the system as well as finding other people in the ecosystem.
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Home
|
||||
@@ -0,0 +1,196 @@
|
||||
import React, { Component } from 'react'
|
||||
import {graphql, compose} from 'react-apollo'
|
||||
import {getUserByAuthIdQuery} from '../queries/queries'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Button from '@material-ui/core/Button'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
|
||||
// icons
|
||||
import UserIcon from '@material-ui/icons/Person'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
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'
|
||||
|
||||
const ProfileSheetLink = props => <Link to='/users/profile-sheet' {...props} />
|
||||
const ProfileImagesLink = props => <Link to='/users/profile-images' {...props} />
|
||||
const YourReferrerLink = props => <Link to='/users/referrer' {...props} />
|
||||
const YourDescendentsLink = props => <Link to='/users/descendents' {...props} />
|
||||
|
||||
const UserLink = props => <Link to='/users/user' {...props} />
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1
|
||||
},
|
||||
flex: {
|
||||
flexGrow: 1,
|
||||
marginLeft: 30
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: -12,
|
||||
marginRight: 20
|
||||
},
|
||||
cssRoot: {
|
||||
color: '#FFFFFF',
|
||||
backgroundColor: theme.palette.secondary,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.dark
|
||||
},
|
||||
whiteSpace: 'nowrap',
|
||||
paddingRight: 2 * theme.spacing.unit,
|
||||
paddingLeft: 2 * theme.spacing.unit
|
||||
},
|
||||
button: {
|
||||
margin: theme.spacing.unit
|
||||
},
|
||||
leftIcon: {
|
||||
marginRight: theme.spacing.unit
|
||||
},
|
||||
rightIcon: {
|
||||
marginLeft: theme.spacing.unit
|
||||
},
|
||||
iconSmall: {
|
||||
fontSize: 20
|
||||
}
|
||||
})
|
||||
|
||||
class LoggedInUser extends Component {
|
||||
|
||||
displayLoggedInUser () {
|
||||
const user = this.props.data.users_UserByAuthId
|
||||
const { classes, match } = this.props
|
||||
|
||||
if (user) {
|
||||
let displayName = 'No Display Name'
|
||||
|
||||
if (
|
||||
user.alias !== null &&
|
||||
user.alias !== ''
|
||||
) { displayName = user.alias }
|
||||
|
||||
if (
|
||||
user.firstName !== null &&
|
||||
user.firstName !== ''
|
||||
) { displayName = user.firstName }
|
||||
|
||||
if (
|
||||
user.firstName !== null &&
|
||||
user.firstName !== '' &&
|
||||
user.lastName !== null &&
|
||||
user.lastName !== ''
|
||||
) { displayName = user.firstName + ' ' + user.lastName }
|
||||
|
||||
let authArray = this.props.authId.split('|')
|
||||
let identityProvider = authArray[0]
|
||||
|
||||
return (
|
||||
<li className='menu-item-has-children'>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Manage Your Profile'
|
||||
component={UserLink}
|
||||
to={`${match.url}/user`}>
|
||||
<UserIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Your Profile
|
||||
</Button>
|
||||
<ul>
|
||||
<li>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Profile Sheet'
|
||||
component={ProfileSheetLink}
|
||||
to={`${match.url}/profile-sheet`}>
|
||||
<ProfileSheetIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Profile Sheet
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Profile Images'
|
||||
component={ProfileImagesLink}
|
||||
to={`${match.url}/profile-images`}>
|
||||
<ProfileImagesIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Profile Images
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Your Referrer'
|
||||
component={YourReferrerLink}
|
||||
to={`${match.url}/referrer`}>
|
||||
<YourReferrerIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Your Referrer
|
||||
</Button>
|
||||
</li>
|
||||
<li>
|
||||
<Button
|
||||
variant='text'
|
||||
size='small'
|
||||
className={classNames(classes.button, classes.cssRoot)}
|
||||
title='Your Descendents'
|
||||
component={YourDescendentsLink}
|
||||
to={`${match.url}/descendents`}>
|
||||
<DescendentsIcon className={classNames(classes.leftIcon, classes.iconSmall)} />
|
||||
Your Descendents
|
||||
</Button>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.loggedInUserStored !== true) {
|
||||
let user = this.props.data.users_UserByAuthId
|
||||
|
||||
if (user !== undefined) {
|
||||
localStorage.setItem('loggedInUser', JSON.stringify(this.props.data.users_UserByAuthId))
|
||||
this.loggedInUserStored = true
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.displayLoggedInUser()}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(getUserByAuthIdQuery, { // What follows is the way to pass a parameter to a query.
|
||||
options: (props) => {
|
||||
return {
|
||||
variables: {
|
||||
authId: props.authId
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
withStyles(styles)
|
||||
)(LoggedInUser)
|
||||
@@ -0,0 +1,42 @@
|
||||
import React, { Component } from 'react'
|
||||
import { compose } from 'recompose'
|
||||
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: '5%',
|
||||
marginBottom: '10%'
|
||||
},
|
||||
typography: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 40,
|
||||
marginBottom: 40
|
||||
}
|
||||
})
|
||||
|
||||
class ProfileImages extends Component {
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Paper className={classes.root}>
|
||||
<Typography className={classes.typography} variant='h5' gutterBottom>
|
||||
Comming Soon
|
||||
</Typography>
|
||||
</Paper>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withStyles(styles)
|
||||
)(ProfileImages)
|
||||
@@ -0,0 +1,525 @@
|
||||
import React, { Component } from 'react'
|
||||
import {graphql, compose} from 'react-apollo'
|
||||
import {getRolesQuery, updateUserMutation, getUsersQuery} from '../queries/queries'
|
||||
|
||||
// Material-ui
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import InputLabel from '@material-ui/core/InputLabel'
|
||||
import Input from '@material-ui/core/Input'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel'
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import FormGroup from '@material-ui/core/FormGroup'
|
||||
import Select from '@material-ui/core/Select'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
|
||||
// Images
|
||||
import GithubLogo from '../img/github-logo.png'
|
||||
import Auth0Logo from '../img/auth0-logo.png'
|
||||
import AALogo from '../img/aa-logo.png'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
width: '100%',
|
||||
flexGrow: 1,
|
||||
padding: 10,
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%'
|
||||
},
|
||||
inputField: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 25
|
||||
},
|
||||
typography: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
marginTop: 40
|
||||
},
|
||||
checkbox: {
|
||||
width: 150,
|
||||
marginLeft: '0%',
|
||||
marginTop: 20
|
||||
},
|
||||
selectEmpty: {
|
||||
marginTop: theme.spacing.unit * 2
|
||||
},
|
||||
button: {
|
||||
margin: theme.spacing.unit,
|
||||
marginTop: theme.spacing.unit * 3
|
||||
},
|
||||
img: {
|
||||
display: 'block',
|
||||
marginTop: 20,
|
||||
maxWidth: 120,
|
||||
maxHeight: 24
|
||||
}
|
||||
})
|
||||
|
||||
class ProfileSheet extends Component {
|
||||
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.defaultValuesSet = false
|
||||
|
||||
this.state = {
|
||||
id: '',
|
||||
firstName: '',
|
||||
middleName: '',
|
||||
lastName: '',
|
||||
bio: '',
|
||||
email: '',
|
||||
emailVerified: 0,
|
||||
isDeveloper: 0,
|
||||
isTrader: 0,
|
||||
isDataAnalyst: 0,
|
||||
roleId: '1',
|
||||
firstNameError: false,
|
||||
middleNameError: false,
|
||||
lastNameError: false,
|
||||
bioError: false,
|
||||
updated: false
|
||||
}
|
||||
}
|
||||
|
||||
displayRoles () {
|
||||
var data = this.props.getRolesQuery // When there is more than one query binded to a single componente 'data' is replaced by thename of the query given below at the binding operation.
|
||||
if (data.loading) {
|
||||
return (
|
||||
<MenuItem><em>Loading roles</em></MenuItem>
|
||||
)
|
||||
} else {
|
||||
return data.users_Roles.map(role => {
|
||||
return (
|
||||
<MenuItem key={role.id} value={role.id}>{ role.name }</MenuItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
submitForm (e) {
|
||||
e.preventDefault()
|
||||
this.props.updateUserMutation({
|
||||
variables: {
|
||||
firstName: this.state.firstName,
|
||||
middleName: this.state.middleName,
|
||||
lastName: this.state.lastName,
|
||||
bio: this.state.bio,
|
||||
isDeveloper: this.state.isDeveloper,
|
||||
isTrader: this.state.isTrader,
|
||||
isDataAnalyst: this.state.isDataAnalyst,
|
||||
roleId: this.state.roleId
|
||||
},
|
||||
refetchQueries: [{ query: getUsersQuery}] // This allow us to re run whatever queries are necesary after the mutation.
|
||||
})
|
||||
|
||||
/* Before we are done, we need to update the state of the local storage. */
|
||||
|
||||
let user = JSON.parse(localStorage.getItem('loggedInUser'))
|
||||
|
||||
user.firstName = this.state.firstName
|
||||
user.middleName = this.state.middleName
|
||||
user.lastName = this.state.lastName
|
||||
user.bio = this.state.bio
|
||||
user.isDeveloper = this.state.isDeveloper
|
||||
user.isTrader = this.state.isTrader
|
||||
user.isDataAnalyst = this.state.isDataAnalyst
|
||||
user.role.id = this.state.roleId
|
||||
|
||||
localStorage.setItem('loggedInUser', JSON.stringify(user))
|
||||
|
||||
this.setState({updated: true})
|
||||
}
|
||||
|
||||
handleCheckBoxes (e) {
|
||||
let fieldValue
|
||||
if (e.target.checked === true) {
|
||||
fieldValue = 1
|
||||
} else {
|
||||
fieldValue = 0
|
||||
|
||||
/* When the user unchecks one of the checkboxes, we need to make sure that that Role is not the one selected at the SELECT,
|
||||
and if it is, then we need to automatically select another one */
|
||||
|
||||
switch (e.target.id) {
|
||||
case 'isDeveloper':
|
||||
if (this.state.roleId === '2') {
|
||||
this.setState({ roleId: '1'})
|
||||
}
|
||||
break
|
||||
|
||||
case 'isTrader':
|
||||
if (this.state.roleId === '3') {
|
||||
this.setState({ roleId: '1'})
|
||||
}
|
||||
break
|
||||
|
||||
case 'isDataAnalyst':
|
||||
if (this.state.roleId === '4') {
|
||||
this.setState({ roleId: '1'})
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
this.setState({ [e.target.id]: fieldValue})
|
||||
}
|
||||
|
||||
handleSelect (e) {
|
||||
/*
|
||||
Whenever the select component changes its value, we need to activate the type of role selected.
|
||||
To do so, we change the state of the corresponding type of role here.
|
||||
*/
|
||||
|
||||
switch (e.target.value) {
|
||||
case '2':
|
||||
this.setState({ isDeveloper: 1})
|
||||
break
|
||||
case '3':
|
||||
this.setState({ isTrader: 1})
|
||||
break
|
||||
case '4':
|
||||
this.setState({ isDataAnalyst: 1})
|
||||
break
|
||||
default:
|
||||
}
|
||||
|
||||
this.setState({ roleId: e.target.value })
|
||||
}
|
||||
|
||||
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 '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
|
||||
case 'bio':
|
||||
if (value.length > 250) {
|
||||
this.setState({bioError: true})
|
||||
} else {
|
||||
this.setState({bioError: false, bio: value})
|
||||
}
|
||||
break
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
rightCheckboxValue (stateValue) {
|
||||
if (stateValue === 1) {
|
||||
return true
|
||||
} else { return false }
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
if (this.defaultValuesSet === false) {
|
||||
let authUser = JSON.parse(localStorage.getItem('user'))
|
||||
let authId = authUser.authId
|
||||
let userData = localStorage.getItem('loggedInUser')
|
||||
|
||||
if (userData === 'undefined' || userData === null) { 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.firstName === null) { user.firstName = '' }
|
||||
if (user.middleName === null) { user.middleName = '' }
|
||||
if (user.lastName === null) { user.lastName = '' }
|
||||
if (user.bio === null) { user.bio = '' }
|
||||
|
||||
/* Now we are ready to set the initial state. */
|
||||
|
||||
this.setState({
|
||||
id: user.id,
|
||||
alias: user.alias,
|
||||
email: user.email,
|
||||
emailVerified: user.emailVerified,
|
||||
firstName: user.firstName,
|
||||
middleName: user.middleName,
|
||||
lastName: user.lastName,
|
||||
bio: user.bio,
|
||||
isDeveloper: user.isDeveloper,
|
||||
isTrader: user.isTrader,
|
||||
isDataAnalyst: user.isDataAnalyst,
|
||||
roleId: user.role.id,
|
||||
authId: authId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
displayIdentityProvider () {
|
||||
const { classes } = this.props
|
||||
let authId = this.state.authId
|
||||
|
||||
if (authId !== undefined && authId !== null) {
|
||||
let authArray = this.state.authId.split('|')
|
||||
let identityProvider = authArray[0]
|
||||
|
||||
switch (identityProvider) {
|
||||
case 'github':
|
||||
return (
|
||||
<img className={classes.img} src={GithubLogo} alt='Github' />
|
||||
)
|
||||
break
|
||||
case 'auth0':
|
||||
return (
|
||||
<img className={classes.img} src={Auth0Logo} alt='Auth0' />
|
||||
)
|
||||
break
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
displayButtonTitle () {
|
||||
if (this.state.updated === true) {
|
||||
return 'Updated'
|
||||
} else {
|
||||
return 'Update'
|
||||
}
|
||||
}
|
||||
|
||||
addForm () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
|
||||
<form onSubmit={this.submitForm.bind(this)}>
|
||||
|
||||
<Typography className={classes.typography} variant='h5' gutterBottom>
|
||||
Profile Sheet
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
Use this form to control the information you keep at the Advanced Algos system about yourself.
|
||||
</Typography>
|
||||
|
||||
<Grid container justify='center' >
|
||||
<Grid item>
|
||||
{this.displayIdentityProvider()}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
This is your basic information we have gotten from the identity provider you used to sign up. This information can not be changed.
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
id='alias'
|
||||
type='text'
|
||||
value={this.state.alias}
|
||||
label='Alias'
|
||||
className={classes.inputField}
|
||||
disabled />
|
||||
|
||||
<TextField
|
||||
id='email'
|
||||
type='text'
|
||||
value={this.state.email}
|
||||
label='Email'
|
||||
className={classes.inputField}
|
||||
disabled />
|
||||
|
||||
<FormControlLabel
|
||||
disabled
|
||||
className={classes.inputField}
|
||||
control={
|
||||
<Checkbox
|
||||
id='emailVerified'
|
||||
checked={this.rightCheckboxValue(this.state.emailVerified)}
|
||||
color='primary'
|
||||
/>
|
||||
}
|
||||
label='Email Verified'
|
||||
/>
|
||||
|
||||
<Grid container justify='center' >
|
||||
<Grid item>
|
||||
<img className={classes.img} src={AALogo} alt='Advanced Algos' />
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
Complete your profile with the following optional information. Providing your real name might help other users trust you more.
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
error={this.state.firstNameError}
|
||||
id='firstName'
|
||||
type='text'
|
||||
value={this.state.firstName}
|
||||
label='First Name'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
error={this.state.middleNameError}
|
||||
id='middleName'
|
||||
type='text'
|
||||
value={this.state.middleName}
|
||||
label='Middle Name'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
error={this.state.lastNameError}
|
||||
id='lastName'
|
||||
type='text'
|
||||
value={this.state.lastName}
|
||||
label='Last Name'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
If you wish, you can add a short paragraph with your Bio or anything you would like to communicate to whoever finds your user profile.
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
error={this.state.bioError}
|
||||
id='bio'
|
||||
type='text'
|
||||
value={this.state.bio}
|
||||
label='Bio'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
Check the following options to enable specialized tools designed for each role. You can allways come back and change these settings later.
|
||||
</Typography>
|
||||
|
||||
<FormGroup row className={classes.inputField}>
|
||||
<Grid container justify='center' >
|
||||
<Grid item>
|
||||
<FormControlLabel
|
||||
className={classes.checkbox}
|
||||
control={
|
||||
<Checkbox
|
||||
id='isDeveloper'
|
||||
onChange={this.handleCheckBoxes.bind(this)}
|
||||
checked={this.rightCheckboxValue(this.state.isDeveloper)}
|
||||
color='secondary'
|
||||
/>
|
||||
}
|
||||
label='Developer'
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FormControlLabel
|
||||
className={classes.checkbox}
|
||||
control={
|
||||
<Checkbox
|
||||
id='isTrader'
|
||||
onChange={this.handleCheckBoxes.bind(this)}
|
||||
checked={this.rightCheckboxValue(this.state.isTrader)}
|
||||
color='secondary'
|
||||
/>
|
||||
}
|
||||
label='Trader'
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<FormControlLabel
|
||||
className={classes.checkbox}
|
||||
control={
|
||||
<Checkbox
|
||||
id='isDataAnalyst'
|
||||
onChange={this.handleCheckBoxes.bind(this)}
|
||||
checked={this.rightCheckboxValue(this.state.isDataAnalyst)}
|
||||
color='secondary'
|
||||
/>
|
||||
}
|
||||
label='Data Analyst'
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</FormGroup>
|
||||
|
||||
<p className={classes.typography}>Your current role determines how the system is going to optimize its user interface to best serves your current needs.</p>
|
||||
|
||||
<FormControl className={classes.inputField}>
|
||||
<InputLabel shrink htmlFor='select'>
|
||||
Current Role
|
||||
</InputLabel>
|
||||
<Select
|
||||
id='select'
|
||||
ref='select'
|
||||
value={this.state.roleId}
|
||||
onChange={this.handleSelect.bind(this)}
|
||||
input={<Input name='Role' id='role-label-placeholder' />}
|
||||
displayEmpty
|
||||
name='select'
|
||||
className={classes.selectEmpty}
|
||||
>
|
||||
{ this.displayRoles() }
|
||||
</Select>
|
||||
<FormHelperText>Select from the list your current role</FormHelperText>
|
||||
</FormControl>
|
||||
|
||||
<Grid container justify='center' >
|
||||
<Grid item>
|
||||
<Button
|
||||
variant='contained'
|
||||
color='secondary'
|
||||
className={classes.button}
|
||||
onClick={this.submitForm.bind(this)}
|
||||
disabled={this.state.updated}
|
||||
>{this.displayButtonTitle()}</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className='container'>
|
||||
{this.addForm()}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(getRolesQuery, {name: 'getRolesQuery'}),
|
||||
graphql(updateUserMutation, {name: 'updateUserMutation'}),
|
||||
withStyles(styles)
|
||||
)(ProfileSheet) // This technique binds more than one query to a single component.
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, { Component } from 'react'
|
||||
import UserSearch from './UserSearch'
|
||||
import {compose} from 'react-apollo'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import BannerTopBar from './BannerTopBar'
|
||||
|
||||
// Materia UI
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
width: '100%',
|
||||
flexGrow: 1,
|
||||
padding: 10,
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%'
|
||||
}
|
||||
})
|
||||
|
||||
class Search extends Component {
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BannerTopBar
|
||||
size='medium'
|
||||
title='Users Search'
|
||||
text='Find anyone involved in Advanced Algos here.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/users.jpg'
|
||||
/>
|
||||
<div className='container'>
|
||||
<Paper className={classes.root}>
|
||||
<UserSearch />
|
||||
</Paper>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withStyles(styles)
|
||||
)(Search)
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
import UserTabs from './UserTabs'
|
||||
import BannerTopBar from './BannerTopBar'
|
||||
|
||||
const User = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<BannerTopBar
|
||||
size='medium'
|
||||
title='Your Profile'
|
||||
text='Manage your profile info, pictures and more.'
|
||||
backgroundUrl='https://superalgos.org/img/photos/users.jpg'
|
||||
/>
|
||||
<UserTabs />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export default User
|
||||
@@ -0,0 +1,185 @@
|
||||
import React, { Component } from 'react';
|
||||
import {graphql, compose} from 'react-apollo';
|
||||
import {getUsersQuery} 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 Paper from '@material-ui/core/Paper';
|
||||
|
||||
// Full screen dialog imports (still Material UI)
|
||||
|
||||
import PropTypes from 'prop-types';
|
||||
import Dialog from '@material-ui/core/Dialog';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import Slide from '@material-ui/core/Slide';
|
||||
|
||||
// components
|
||||
import UserProfile from './UserProfile';
|
||||
|
||||
// Images
|
||||
import UserDefaultPicture from '../img/user-default-pic.jpg'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
paddingTop:30,
|
||||
paddingBottom:30,
|
||||
flexGrow: 1
|
||||
},
|
||||
card: {
|
||||
maxWidth: 345,
|
||||
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'
|
||||
},
|
||||
});
|
||||
|
||||
function Transition(props) {
|
||||
return <Slide direction="up" {...props} />;
|
||||
}
|
||||
|
||||
class UserList extends Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
selected: null,
|
||||
open: false
|
||||
}
|
||||
}
|
||||
|
||||
handleClickOpen = () => {
|
||||
this.setState({ open: true });
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ open: false });
|
||||
};
|
||||
|
||||
displayUsers(){
|
||||
|
||||
let data = this.props.getUsersQuery;
|
||||
const { classes } = this.props;
|
||||
|
||||
if(data.loading){
|
||||
return ( <div> Loading Users... </div>);
|
||||
} else {
|
||||
return data.users_Users.map(user => {
|
||||
|
||||
return (
|
||||
|
||||
<Grid key={user.id} item>
|
||||
<Card className={classes.card} onClick={ (e) => {
|
||||
this.setState({ selected: user.id});
|
||||
}
|
||||
}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
image={UserDefaultPicture}
|
||||
title="User Profile"
|
||||
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="h2">
|
||||
{user.alias}
|
||||
</Typography>
|
||||
<Typography className={classes.typography} gutterBottom>
|
||||
{user.bio}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<Grid container justify="center" spacing={8}>
|
||||
<Grid item>
|
||||
<Button onClick={this.handleClickOpen}>User Profile</Button>
|
||||
<Dialog
|
||||
fullScreen
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
TransitionComponent={Transition}
|
||||
>
|
||||
<AppBar className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton color="inherit" onClick={this.handleClose} aria-label="Close">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography variant="h6" color="inherit" className={classes.flex}>
|
||||
User Profile
|
||||
</Typography>
|
||||
<Button color="inherit" onClick={this.handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<Paper className={classes.root}>
|
||||
<UserProfile userId={this.state.selected}/>
|
||||
</Paper>
|
||||
</Dialog>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button disabled color="primary">
|
||||
Extended Profile
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid container justify="center" spacing={40} className={classes.root}>
|
||||
{this.displayUsers()}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserList.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default compose(
|
||||
graphql(getUsersQuery, {name: "getUsersQuery"}),
|
||||
withStyles(styles)
|
||||
) (UserList); // This technique binds more than one query to a single component.
|
||||
@@ -0,0 +1,155 @@
|
||||
import React, { Component } from 'react'
|
||||
import {graphql, compose} from 'react-apollo'
|
||||
import {getUserProfileQuery} from '../queries/queries'
|
||||
|
||||
// Materia UI
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Grid from '@material-ui/core/Grid'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardActionArea from '@material-ui/core/CardActionArea'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
|
||||
// Images
|
||||
import UserDefaultPicture from '../img/user-default-pic.jpg'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
padding: 60,
|
||||
margin: 2
|
||||
},
|
||||
card: {
|
||||
maxWidth: 500,
|
||||
minWidth: 500,
|
||||
paddingTop: '30'
|
||||
},
|
||||
media: {
|
||||
height: 0,
|
||||
paddingTop: '56.25%', // 16:9,
|
||||
marginTop: '30',
|
||||
minWidth: '80%'
|
||||
},
|
||||
grid: {
|
||||
paddingTop: '30',
|
||||
marginTop: '30'
|
||||
},
|
||||
typography: {
|
||||
width: '100%',
|
||||
marginTop: 20,
|
||||
marginBottom: 20
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
class UserProfile extends Component {
|
||||
|
||||
displayNames () {
|
||||
const user = this.props.data.users_User
|
||||
const { classes } = this.props
|
||||
|
||||
if (user.firstName !== null && user.lastName !== null) {
|
||||
return (
|
||||
<Typography className={classes.typography} gutterBottom variant='h5' component='h2'>
|
||||
{ user.firstName + ' ' + user.lastName}
|
||||
</Typography>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
displayRole () {
|
||||
const user = this.props.data.users_User
|
||||
const { classes } = this.props
|
||||
|
||||
if (user.role.name !== null && user.role.name !== 'Not Defined') {
|
||||
return (
|
||||
<div>
|
||||
<Divider />
|
||||
<Typography className={classes.typography} align='left' gutterBottom variant='subtitle1' component='h2'>
|
||||
{ user.role.name }
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
displayBio () {
|
||||
const user = this.props.data.users_User
|
||||
const { classes } = this.props
|
||||
|
||||
if (user.bio !== null && user.bio !== '') {
|
||||
return (
|
||||
<div>
|
||||
<Divider />
|
||||
<Typography className={classes.typography} component='p'>
|
||||
{ user.bio }
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
displayUserProfile () {
|
||||
const user = this.props.data.users_User
|
||||
const { classes } = this.props
|
||||
|
||||
if (user) {
|
||||
return (
|
||||
<Grid key={user.id} item>
|
||||
|
||||
<Card className={classes.card} onClick={(e) => {
|
||||
this.setState({ selected: user.id})
|
||||
}
|
||||
}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
image={UserDefaultPicture}
|
||||
title='User Profile'
|
||||
|
||||
/>
|
||||
<CardContent>
|
||||
|
||||
{this.displayNames()}
|
||||
|
||||
<Typography className={classes.typography} gutterBottom variant='h6' component='h3'>
|
||||
{user.alias}
|
||||
</Typography>
|
||||
|
||||
{this.displayRole()}
|
||||
{this.displayBio()}
|
||||
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Grid>
|
||||
)
|
||||
} else {
|
||||
return (<div>No User selected...</div>)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Grid container justify='center' spacing={24}>
|
||||
{this.displayUserProfile()}
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(getUserProfileQuery, { // What follows is the way to pass a parameter to a query.
|
||||
options: (props) => {
|
||||
return {
|
||||
variables: {
|
||||
name: 'getUserProfileQuery',
|
||||
id: props.userId
|
||||
}
|
||||
}
|
||||
}}),
|
||||
withStyles(styles)
|
||||
)(UserProfile) // This technique binds more than one query to a single component.
|
||||
@@ -0,0 +1,275 @@
|
||||
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 UserDefaultPicture from '../img/user-default-pic.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: {
|
||||
width: '80%',
|
||||
marginLeft: '10%',
|
||||
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 (<span>{this.props.selectText}</span>)
|
||||
} else {
|
||||
return (<span />)
|
||||
}
|
||||
}
|
||||
|
||||
selectButton (user) {
|
||||
if (this.props.selectButton === true) {
|
||||
return (
|
||||
<Button id={user.id} ref={user.id} color='primary' onClick={() => {
|
||||
this.props.onSelect(user) // This sends the user to the parent component
|
||||
}}>
|
||||
Select
|
||||
</Button>
|
||||
)
|
||||
} else {
|
||||
return (<div />)
|
||||
}
|
||||
}
|
||||
|
||||
displayUsers () {
|
||||
let data = this.props.getUsersBySearchFields
|
||||
const { classes } = this.props
|
||||
|
||||
if (data.loading) {
|
||||
return (<div> Loading Users... </div>)
|
||||
} else {
|
||||
if (data.users_UsersSearch === undefined) {
|
||||
return (<div> No Users to Display </div>)
|
||||
} else {
|
||||
return data.users_UsersSearch.map(user => {
|
||||
return (
|
||||
|
||||
<Grid key={user.id} item>
|
||||
<Card className={classes.card} onClick={(e) => {
|
||||
this.setState({ selected: user.id})
|
||||
}
|
||||
}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
image={UserDefaultPicture}
|
||||
title='User Profile'
|
||||
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography gutterBottom component='h2'>
|
||||
{user.alias}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<Grid container justify='center' spacing={8}>
|
||||
<Grid item>
|
||||
{this.selectButton(user)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.dispatchEvent(new Event('load')) // This is a workaround to solve the problem that the slider does not show up
|
||||
}
|
||||
|
||||
render () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
|
||||
<div>
|
||||
<form onSubmit={this.submitForm.bind(this)}>
|
||||
|
||||
<Typography className={classes.formTypography} variant='body1' gutterBottom align='left'>
|
||||
Use any of these fileds to search for users. {this.selectText()}
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
error={this.state.aliasError}
|
||||
id='alias'
|
||||
type='text'
|
||||
value={this.state.alias}
|
||||
label='Alias'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
error={this.state.firstNameError}
|
||||
id='firstName'
|
||||
type='text'
|
||||
value={this.state.firstName}
|
||||
label='First Name'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
error={this.state.middleNameError}
|
||||
id='middleName'
|
||||
type='text'
|
||||
value={this.state.middleName}
|
||||
label='Middle Name'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
error={this.state.lastNameError}
|
||||
id='lastName'
|
||||
type='text'
|
||||
value={this.state.lastName}
|
||||
label='Last Name'
|
||||
className={classes.inputField}
|
||||
onChange={this.handleTextField.bind(this)}
|
||||
/>
|
||||
|
||||
<Grid container justify='center' >
|
||||
<Grid item>
|
||||
<Button variant='contained' color='secondary' className={classes.button} onClick={this.submitForm.bind(this)}>Search</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
|
||||
<Grid container className={classes.grid} justify='center' spacing={24}>
|
||||
{this.displayUsers()}
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(getUsersBySearchFields, {name: 'getUsersBySearchFields'}),
|
||||
withStyles(styles)
|
||||
)(UserSearch) // This technique binds more than one query to a single component.
|
||||
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
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 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 ProfileSheet from './ProfileSheet';
|
||||
import ProfileImages from './ProfileImages';
|
||||
import YourReferrer from './YourReferrer';
|
||||
import YourDescendents from './YourDescendents';
|
||||
|
||||
function TabContainer(props) {
|
||||
return (
|
||||
<Typography component="div" style={{ padding: 8 * 3 }}>
|
||||
{props.children}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
TabContainer.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
},
|
||||
});
|
||||
|
||||
class UserTabs extends React.Component {
|
||||
state = {
|
||||
value: 0
|
||||
};
|
||||
|
||||
handleChange = (event, value) => {
|
||||
this.setState({ value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const { value } = this.state;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<AppBar position="static" color="default">
|
||||
<Tabs
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
scrollable
|
||||
scrollButtons="off"
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
fullWidth
|
||||
>
|
||||
<Tab label="Profile Sheet" icon={<ProfileSheetIcon />} />
|
||||
<Tab disabled label="Profile Images" icon={<ProfileImagesIcon />} />
|
||||
<Tab label="Your Referrer" icon={<YourReferrerIcon />} />
|
||||
<Tab label="Your Descendents" icon={<DescendentsIcon />} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
{value === 0 && <TabContainer><ProfileSheet/></TabContainer>}
|
||||
{value === 1 && <TabContainer><ProfileImages/></TabContainer>}
|
||||
{value === 2 && <TabContainer><YourReferrer/></TabContainer>}
|
||||
{value === 3 && <TabContainer><YourDescendents/></TabContainer>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
UserTabs.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(UserTabs);
|
||||
@@ -0,0 +1,96 @@
|
||||
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: '100%',
|
||||
flexGrow: 1,
|
||||
padding: 10,
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%'
|
||||
},
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addForm () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
|
||||
<Typography className={classes.typography} variant='h5' gutterBottom>
|
||||
Your Decendents
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
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. In order to be able to see your descendents, the users who you referred
|
||||
must first point to you as your referrer.
|
||||
</Typography>
|
||||
|
||||
<DescendentsTree userId={this.state.id} />
|
||||
<Grid container className={classes.grid} justify='center' spacing={24} />
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className='container'>
|
||||
{this.addForm()}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withStyles(styles)
|
||||
)(YourDescendents) // This technique binds more than one query to a single component.
|
||||
@@ -0,0 +1,154 @@
|
||||
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: '100%',
|
||||
flexGrow: 1,
|
||||
padding: 10,
|
||||
marginTop: '5%',
|
||||
marginBottom: '10%'
|
||||
},
|
||||
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: {
|
||||
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 (
|
||||
<div>
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
This is the user who introduced you to the project.
|
||||
</Typography>
|
||||
|
||||
<UserProfile userId={this.state.referrerId} />
|
||||
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
This information can not be changed.
|
||||
</Typography>
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<Typography className={classes.typography} variant='body1' gutterBottom align='left'>
|
||||
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.
|
||||
</Typography>
|
||||
|
||||
<UserSearch selectButton onSelect={this.userSelected.bind(this)} selectText="After that press the 'Select' button on the desired user to set it as your referrer. Bear in mind that this action can not be undone." />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addForm () {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<Paper className={classes.root}>
|
||||
<Typography className={classes.typography} variant='h5' gutterBottom>
|
||||
Your Referrer
|
||||
</Typography>
|
||||
{this.renderMode()}
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className='container'>
|
||||
{this.addForm()}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default compose(
|
||||
graphql(updateReferrerMutation, {name: 'updateReferrerMutation'}),
|
||||
withStyles(styles)
|
||||
)(YourReferrer) // This technique binds more than one query to a single component.
|
||||
|
After Width: | Height: | Size: 801 B |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 472 B |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 66 KiB |
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import { Route, Switch } from 'react-router-dom'
|
||||
|
||||
import { MuiPickersUtilsProvider } from 'material-ui-pickers'
|
||||
import LuxonUtils from 'material-ui-pickers/utils/luxon-utils'
|
||||
|
||||
import Home from './components/Home'
|
||||
import User from './components/User'
|
||||
import Browse from './components/Browse'
|
||||
import Search from './components/Search'
|
||||
|
||||
const App = () => (
|
||||
<MuiPickersUtilsProvider utils={LuxonUtils}>
|
||||
<Switch>
|
||||
<Route exact path='/users/' component={Home} />
|
||||
<Route path='/users/user' component={User} />
|
||||
<Route path='/users/browse' component={Browse} />
|
||||
<Route path='/users/search' component={Search} />
|
||||
</Switch>
|
||||
</MuiPickersUtilsProvider>
|
||||
)
|
||||
|
||||
export default App
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
const getUsersQuery = gql`
|
||||
{
|
||||
users_Users {
|
||||
id
|
||||
alias
|
||||
firstName
|
||||
middleName
|
||||
lastName
|
||||
bio
|
||||
role {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const getUserProfileQuery = gql`
|
||||
query($id: ID){
|
||||
users_User (id: $id){
|
||||
id
|
||||
alias
|
||||
firstName
|
||||
middleName
|
||||
lastName
|
||||
bio
|
||||
avatarHandle
|
||||
role {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const getUserByAuthIdQuery = gql`
|
||||
query($authId: String){
|
||||
users_UserByAuthId (authId: $authId){
|
||||
id
|
||||
referrerId
|
||||
alias
|
||||
firstName
|
||||
middleName
|
||||
lastName
|
||||
bio
|
||||
email
|
||||
emailVerified
|
||||
isDeveloper
|
||||
isDataAnalyst
|
||||
isTrader
|
||||
avatarHandle
|
||||
avatarChangeDate
|
||||
role {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const getUsersBySearchFields = gql`
|
||||
query(
|
||||
$alias: String,
|
||||
$firstName: String,
|
||||
$middleName: String,
|
||||
$lastName: String
|
||||
){
|
||||
users_UsersSearch (alias: $alias, firstName: $firstName, middleName: $middleName, lastName: $lastName){
|
||||
id
|
||||
alias
|
||||
firstName
|
||||
middleName
|
||||
lastName
|
||||
}
|
||||
}
|
||||
`
|
||||
const getDescendentsQuery = gql`
|
||||
query(
|
||||
$id: String
|
||||
){
|
||||
users_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`
|
||||
{
|
||||
users_Roles {
|
||||
id
|
||||
name
|
||||
}
|
||||
}
|
||||
`
|
||||
const updateUserMutation = gql`
|
||||
mutation(
|
||||
$firstName:String,
|
||||
$middleName:String,
|
||||
$lastName:String,
|
||||
$bio:String,
|
||||
$isDeveloper:Int,
|
||||
$isTrader:Int,
|
||||
$isDataAnalyst:Int,
|
||||
$roleId:String!
|
||||
)
|
||||
{
|
||||
users_UpdateUser (
|
||||
firstName:$firstName,
|
||||
middleName: $middleName,
|
||||
lastName: $lastName,
|
||||
bio: $bio,
|
||||
isDeveloper: $isDeveloper,
|
||||
isTrader: $isTrader,
|
||||
isDataAnalyst: $isDataAnalyst,
|
||||
roleId: $roleId
|
||||
)
|
||||
{
|
||||
id
|
||||
alias
|
||||
}
|
||||
}
|
||||
`
|
||||
const updateReferrerMutation = gql`
|
||||
mutation(
|
||||
$referrerId:String!
|
||||
)
|
||||
{
|
||||
users_UpdateUserReferrer (
|
||||
referrerId:$referrerId
|
||||
)
|
||||
{
|
||||
id
|
||||
referrerId
|
||||
}
|
||||
}
|
||||
`
|
||||
export {
|
||||
getUsersQuery,
|
||||
getUserByAuthIdQuery,
|
||||
getRolesQuery,
|
||||
getUserProfileQuery,
|
||||
getUsersBySearchFields,
|
||||
getDescendentsQuery,
|
||||
updateUserMutation,
|
||||
updateReferrerMutation
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default theme
|
||||
@@ -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
|
||||
}
|
||||