Merge pull request #2 from AdvancedAlgos/feature/client-refactor-react

Feature/client refactor react
This commit is contained in:
Barry Low authored and GitHub committed 2018-09-17 08:32:23 -07:00
commit e275df28d7
61 files changed
+4770 -1030

No files matched your search

+1 -1
View File
@@ -4,7 +4,7 @@ projects:
extensions:
endpoints:
dev1:
url: http://0.0.0.0:4000/graphql
url: http://localhost:4001/graphql
database:
schemaPath: src/generated/prisma.graphql
+1 -1
View File
@@ -1,6 +1,6 @@
FROM mhart/alpine-node:8
ENV PORT=4000
ENV PORT=4001
RUN mkdir -p /var/www/app && \
mkdir -p /var/www/app/node_modules
+3 -1
View File
@@ -30,6 +30,7 @@ type Team {
slug: String! @unique
profile: TeamProfile
members: [TeamMembers!]! @relation (name: "TeamMembers", onDelete: SET_NULL)
owner: String!
wallet: Wallet
fb: [FinancialBeings!]! @relation (name: "TeamFinancialBeing", onDelete: SET_NULL)
status: [TeamStatus!]!
@@ -41,6 +42,7 @@ type TeamProfile {
motto: String
avatar: String
updatedAt: DateTime!
createdAt: DateTime!
}
type TeamStatus {
@@ -59,7 +61,7 @@ type TeamMembers {
type Member {
id: ID! @unique
nickname: String
alias: String
team: TeamMembers @relation (name: "MemberOnTeam", onDelete: SET_NULL)
authId: String! @unique
visible: Boolean! @default(value: true)
+19
View File
@@ -0,0 +1,19 @@
version: '3'
services:
api:
image: aateamapi-dev:v1
build:
context: .
dockerfile: ./Dockerfile
env_file: ./.env
container_name: aateamapi
tty: true
stdin_open: true
volumes:
- ./:/var/www/app
- /var/www/app/node_modules
working_dir: /var/www/app
restart: always
command: node -r dotenv/config src/index.js
ports:
- "4001:4001"
-19
View File
@@ -27,24 +27,5 @@ services:
POSTGRES_PASSWORD: prisma
volumes:
- postgres:/var/lib/postgresql/data
node:
image: aateamapi-dev:v1
build:
context: .
dockerfile: ./Dockerfile
depends_on:
- prisma
env_file: ./.env
container_name: aateamapi
tty: true
stdin_open: true
volumes:
- ./:/var/www/app
- /var/www/app/node_modules
working_dir: /var/www/app
restart: always
command: node -r dotenv/config src/index.js
ports:
- "4000:4000"
volumes:
postgres:
+43 -56
View File
@@ -1492,9 +1492,9 @@
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"config-chain": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz",
"integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=",
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.12.tgz",
"integrity": "sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA==",
"dev": true,
"requires": {
"ini": "^1.3.4",
@@ -4084,6 +4084,11 @@
"iterall": "^1.2.1"
}
},
"graphql-tag": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.9.2.tgz",
"integrity": "sha512-qnNmof9pAqj/LUzs3lStP0Gw1qhdVCUS7Ab7+SUB6KD5aX1uqxWQRwMnOGTkhKuLvLNIs1TvNz+iS9kUGl1MhA=="
},
"graphql-tools": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-3.1.1.tgz",
@@ -6371,14 +6376,14 @@
"dev": true
},
"prisma": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-1.15.3.tgz",
"integrity": "sha512-95s0Kk8PtptrVhp4ajXPd7A7QrbWdCEyZ/nnvcz0Sz9nzufYYZfBjxgibUyoaNs/XL0KXtiibQIdZhpWIJgOhw==",
"version": "1.16.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-1.16.0.tgz",
"integrity": "sha512-8jZYCrkd1xjy+JJbQi81f8PBOLe8LLWwSu0fK0cqekm3FeafBd8xk5b0RWcIsHyVd6ypKCQt3tnZEMVzmbKrdQ==",
"dev": true,
"requires": {
"fs-extra": "^5.0.0",
"prisma-cli-core": "1.0.75",
"prisma-cli-engine": "1.0.78",
"prisma-cli-core": "1.0.76",
"prisma-cli-engine": "1.0.79",
"semver": "^5.4.1",
"source-map-support": "^0.4.18"
},
@@ -6497,9 +6502,9 @@
}
},
"prisma-cli-core": {
"version": "1.0.75",
"resolved": "https://registry.npmjs.org/prisma-cli-core/-/prisma-cli-core-1.0.75.tgz",
"integrity": "sha512-DiH686w2u9aiz4RoNX3T0XgGzeai8IrXX/Qsocam+lKtBB63aupqCxbj6kp16sNoiXhDPzI2Zx/Hwm0GG9sUhw==",
"version": "1.0.76",
"resolved": "https://registry.npmjs.org/prisma-cli-core/-/prisma-cli-core-1.0.76.tgz",
"integrity": "sha512-HtHn/RM2GlJi23KoIXHoboP97aC54SYDqr7B4uT/80JP6A6nQp5HLt4sJgNfKapaHFZVzXYM7M3LEfGq3tgQbw==",
"dev": true,
"requires": {
"adm-zip": "^0.4.7",
@@ -6533,9 +6538,9 @@
"opn": "^5.1.0",
"pause": "^0.1.0",
"portfinder": "^1.0.13",
"prisma-db-introspection": "0.0.45",
"prisma-db-introspection": "0.0.46",
"prisma-json-schema": "0.0.4",
"prisma-yml": "1.0.90",
"prisma-yml": "1.0.96",
"scuid": "^1.0.2",
"semver": "^5.4.1",
"sillyname": "^0.1.0",
@@ -6599,12 +6604,12 @@
}
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
"dev": true,
"requires": {
"ms": "2.0.0"
"ms": "^2.1.1"
}
},
"dotenv": {
@@ -6766,12 +6771,6 @@
"regex-cache": "^0.4.2"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"prisma-json-schema": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/prisma-json-schema/-/prisma-json-schema-0.0.4.tgz",
@@ -6779,9 +6778,9 @@
"dev": true
},
"prisma-yml": {
"version": "1.0.90",
"resolved": "https://registry.npmjs.org/prisma-yml/-/prisma-yml-1.0.90.tgz",
"integrity": "sha512-i5qMN/p1YvLV931YgfJtUAaHO7nOL07oW/EqQ1AixBGRi9nB21TpZGy9b50TwunaMt8LfVYB5ER82338qrJA5g==",
"version": "1.0.96",
"resolved": "https://registry.npmjs.org/prisma-yml/-/prisma-yml-1.0.96.tgz",
"integrity": "sha512-w3XGzJHUq5pwBoFy/amkGOcTDj4XoHh8UFUUUZcHo5fSyoF8AWeRTu1yWINaPVGxbcmpwKNetntAW4ijiq7M5g==",
"dev": true,
"requires": {
"ajv": "5",
@@ -6828,9 +6827,9 @@
}
},
"prisma-cli-engine": {
"version": "1.0.78",
"resolved": "https://registry.npmjs.org/prisma-cli-engine/-/prisma-cli-engine-1.0.78.tgz",
"integrity": "sha512-chtmecxBgXPQUzvDS0U/R+pHZ/nzV1ksO+OiKr98kT7dBR9QJQbRG0v/j+zoIW4vdR9eyIaXy5i+dW8HU8XnDg==",
"version": "1.0.79",
"resolved": "https://registry.npmjs.org/prisma-cli-engine/-/prisma-cli-engine-1.0.79.tgz",
"integrity": "sha512-CdEIJs4ld1IiQQt2CFFPmeDypN4Ct4sTgmFODVjJix790mpEtxMagzCcA+mhfnRL65P6xiHn76Gdxbz60GyYBA==",
"dev": true,
"requires": {
"@heroku/linewrap": "^1.0.0",
@@ -6873,7 +6872,7 @@
"memfs": "^2.5.3",
"opn": "^5.1.0",
"prisma-json-schema": "0.0.4",
"prisma-yml": "1.0.90",
"prisma-yml": "1.0.96",
"raven": "2.3.0",
"replaceall": "^0.1.6",
"rwlockfile": "^1.4.8",
@@ -6890,12 +6889,12 @@
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
"dev": true,
"requires": {
"ms": "2.0.0"
"ms": "^2.1.1"
}
},
"dotenv": {
@@ -6931,12 +6930,6 @@
"through": "^2.3.6"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"prisma-json-schema": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/prisma-json-schema/-/prisma-json-schema-0.0.4.tgz",
@@ -6944,9 +6937,9 @@
"dev": true
},
"prisma-yml": {
"version": "1.0.90",
"resolved": "https://registry.npmjs.org/prisma-yml/-/prisma-yml-1.0.90.tgz",
"integrity": "sha512-i5qMN/p1YvLV931YgfJtUAaHO7nOL07oW/EqQ1AixBGRi9nB21TpZGy9b50TwunaMt8LfVYB5ER82338qrJA5g==",
"version": "1.0.96",
"resolved": "https://registry.npmjs.org/prisma-yml/-/prisma-yml-1.0.96.tgz",
"integrity": "sha512-w3XGzJHUq5pwBoFy/amkGOcTDj4XoHh8UFUUUZcHo5fSyoF8AWeRTu1yWINaPVGxbcmpwKNetntAW4ijiq7M5g==",
"dev": true,
"requires": {
"ajv": "5",
@@ -7002,9 +6995,9 @@
}
},
"prisma-db-introspection": {
"version": "0.0.45",
"resolved": "https://registry.npmjs.org/prisma-db-introspection/-/prisma-db-introspection-0.0.45.tgz",
"integrity": "sha512-tOdv4mBFZLVds1Wst92l2iarmzlxwFo/EFHs4kW1P0rR2My7BoxRgV0HSZy9xvo9gbU3P8e5JEZofMNrS+5v+g==",
"version": "0.0.46",
"resolved": "https://registry.npmjs.org/prisma-db-introspection/-/prisma-db-introspection-0.0.46.tgz",
"integrity": "sha512-/b7evS9b5xKS7UrcOERkCmV+IWC6OAgFtInFsvdwn4MeLEaVbkOfzRakdOo+t2gb+N7Yy8Eey9eKYgB5jiJAbA==",
"dev": true,
"requires": {
"ajv": "5",
@@ -7029,12 +7022,12 @@
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz",
"integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==",
"dev": true,
"requires": {
"ms": "2.0.0"
"ms": "^2.1.1"
}
},
"dotenv": {
@@ -7043,12 +7036,6 @@
"integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0=",
"dev": true
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
},
"prisma-json-schema": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/prisma-json-schema/-/prisma-json-schema-0.0.1.tgz",
+2 -1
View File
@@ -11,6 +11,7 @@
"dotenv": "^6.0.0",
"express-jwt": "^5.3.1",
"graphql-import": "^0.6.0",
"graphql-tag": "^2.9.2",
"graphql-tools": "3.1.1",
"graphql-yoga": "^1.16.1",
"jsonwebtoken": "^8.3.0",
@@ -22,6 +23,6 @@
"devDependencies": {
"graphql-cli": "^2.16.5",
"npm-run-all": "4.1.3",
"prisma": "^1.14.0"
"prisma": "^1.16.0"
}
}
+44 -18
View File
@@ -1,18 +1,35 @@
const _get = require('lodash.get')
const validateAndParseIdToken = require('./helpers/validateAndParseIdToken')
const createMember = require('./index')
const { validateIdToken } = require('./validateIdToken')
const memberLocationOnContext = 'request.member'
const memberLocationOnContext = 'request.user'
const bearerAccessToken = 'request.headers.authorization'
const ctxMember = ctx => _get(ctx, memberLocationOnContext)
const ctxToken = ctx => _get(ctx, bearerAccessToken)
const createMember = async (ctx, idToken, info) => {
console.log('createMember', idToken)
const member = await ctx.db.mutation.upsertMember({
where: {
authId: idToken.sub,
},
create: {
authId: idToken.sub,
alias: idToken.nickname
},
update: {
authId: idToken.sub,
alias: idToken.nickname
}
}, info)
return member
}
const isLoggedIn = async ctx => {
let member = ctxMember(ctx, memberLocationOnContext)
let token = ctxToken(ctx, bearerAccessToken)
let memberToken
console.log('isLoggedIn: ', ctx, member, token)
if (!member && token) {
let scheme, credentials
const tokenParts = token.split(' ')
@@ -24,21 +41,28 @@ const isLoggedIn = async ctx => {
if (/^Bearer$/i.test(scheme)) {
token = credentials
//verify token
console.log('isLoggedIn2 : ', token)
try {
memberToken = await validateAndParseIdToken(token)
const auth0id = memberToken.sub
memberToken = await validateIdToken(token)
const authId = memberToken.sub
console.log('isLoggedIn3 : ', await authId, ctx.db.query.member)
exists = await ctx.db.query.member({ where: { authId: authId } })
console.log('isLoggedIn4 : ', await exists, createMember )
if (!member && exists === null) {
member = await createMember(ctx, memberToken).then(res => {
console.log('isLoggedIn5 : ', res)
return res
})
member = await ctx.db.query.member({ where: { auth0id } })
if (!member) {
member = await createMember(ctx, memberToken)
return member
}
return member
} catch (err) {
memberToken = false
}
}
}
console.log('isLoggedIn5 : ', await member)
if (!member) {
throw new Error(`Not logged in`)
}
@@ -47,8 +71,8 @@ const isLoggedIn = async ctx => {
const isRequestingMemberAlsoOwner = ({ ctx, memberId, type, typeId }) =>
ctx.db.exists[type]({ id: typeId, owner: { auth0id: memberId } })
const isRequestingMember = ({ ctx, memberId }) => ctx.db.exists.Member({ auth0id: memberId })
ctx.db.exists[type]({ slug: typeId, owner: memberId })
const isRequestingMember = ({ ctx, memberId }) => ctx.db.exists.Member({ authId: memberId })
const directiveResolvers = {
isAuthenticated: async (next, source, args, ctx) => {
@@ -65,12 +89,14 @@ const directiveResolvers = {
throw new Error(`Unauthorized, incorrect role`)
},
isOwner: async (next, source, { type }, ctx) => {
const { id: typeId } =
console.log('directive isOwner: ', source, type, ctx)
const { slug: typeId } =
source && source.id
? source
: ctx.request.body.variables ? ctx.request.body.variables : { id: null }
const { auth0id: memberId } = await isLoggedIn(ctx)
console.log('directive isOwner 0: ', type, typeId, memberId)
const user = await isLoggedIn(ctx)
console.log('directive isOwner 0: ', typeId, user.sub)
const memberId = user.sub
const isOwner =
type === `Member`
? memberId === typeId
@@ -82,14 +108,14 @@ const directiveResolvers = {
throw new Error(`Unauthorized, must be owner`)
},
isOwnerOrHasRole: async (next, source, { roles, type }, ctx, ...p) => {
const { auth0id: memberId } = await isLoggedIn(ctx)
const { authId: memberId } = await isLoggedIn(ctx)
console.log('directive isOwnerOrHasRole 1: ', memberId, roles, type)
if(memberId === undefined && role=== undefined) throw new Error(`Not logged in`)
if (roles.includes(role)) {
return next()
}
const { auth0id: typeId } = ctx.request.body.variables
const { authId: typeId } = ctx.request.body.variables
const isOwner = await isRequestingMemberAlsoOwner({
ctx,
memberId,
+2 -2
View File
@@ -1,9 +1,9 @@
const getMember = async (req, res, next, db) => {
if (!req.user) return next()
const member = await db.query.member({
where: { auth0id: req.user.sub }
where: { authId: req.user.sub }
})
req.user = { token: req.user, ...member }
req.user = { user: member }
next()
}
+1 -1
View File
@@ -21,4 +21,4 @@ const checkJwt = jwt({
algorithms: [`RS256`]
})
module.exports = { checkJwt }
module.exports = checkJwt
+2 -2
View File
@@ -7,7 +7,7 @@ const jwks = jwksClient({
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
})
const validateParseIdToken = idToken =>
const validateIdToken = idToken =>
new Promise((resolve, reject) => {
const { header, payload } = jwt.decode(idToken, { complete: true })
if (!header || !header.kid || !payload) reject(new Error('Invalid Token'))
@@ -25,4 +25,4 @@ const validateParseIdToken = idToken =>
})
})
module.exports = validateParseIdToken
module.exports = { validateIdToken }
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,33 @@
const gql = require('graphql-tag')
const TEAMS_CONNECTIONS_FRAGMENT = gql`{
edges {
node {
id
name
slug
owner
status {
status
reason
createdAt
}
createdAt
profile {
avatar
description
motto
updatedAt
}
members {
role
member {
alias
authId
}
}
}
}
}`
module.exports = TEAMS_CONNECTIONS_FRAGMENT
+29
View File
@@ -0,0 +1,29 @@
const gql = require('graphql-tag')
const TEAMS_FRAGMENT = gql`{
id
name
slug
owner
status {
status
reason
createdAt
}
createdAt
profile {
avatar
description
motto
updatedAt
}
members {
role
member {
alias
authId
}
}
}`
module.exports = TEAMS_FRAGMENT
+81 -110
View File
@@ -1,3 +1,4 @@
require('dotenv').config();
const { GraphQLServer } = require('graphql-yoga')
const {
Prisma,
@@ -6,51 +7,48 @@ const {
} = require('prisma-binding')
const { makeExecutableSchema } = require('graphql-tools')
const { importSchema } = require('graphql-import')
const { checkJwt } = require('./auth/middleware/jwt')
const { getMember } = require('./auth/middleware/getMember')
const validateParseIdToken = require('./auth/helpers/validate-parse-id-token')
const { directiveResolvers } = require('./auth-directives')
const ctxMember = ctx => ctx.request.member
const checkJwt = require('./auth/middleware/jwt')
const { getMember } = require('./auth/middleware/getMember')
const { validateIdToken } = require('./auth/validateIdToken')
const { directiveResolvers } = require('./auth/authDirectives')
const TEAMS_FRAGMENT = require('./graphql/TeamsFragment')
const TEAMS_CONNECTIONS_FRAGMENT = require('./graphql/TeamsConnectionsFragment')
const createMember = async function (ctx, info, idToken) {
console.log('createMember', idToken)
const member = await ctx.db.mutation.upsertMember({
where: {
authId: idToken.sub,
},
create: {
authId: idToken.sub,
alias: idToken.nickname
},
update: {
authId: idToken.sub,
alias: idToken.nickname
}
}, info)
return member
}
const ctxMember = ctx => ctx.request.user
const resolvers = {
Query: {
async member(parent, { auth0id }, ctx, info) {
return ctx.db.query.member({ where: { auth0id } }, info)
async member(parent, arg, ctx, info) {
console.log('member: ', parent, arg)
return ctx.db.query.member({ where: { authId: arg.authId } }, info)
},
async currentMember(parent, args, ctx, info) {
console.log('currentMember: ', ctx.request.res.req.user)
const authId = ctx.request.res.req.user.sub
return ctx.db.query.member({ where: { authId } }, info)
},
teams(parent, args, ctx, info) {
return ctx.db.query.teamsConnection({},
`{
edges {
node {
id
name
slug
profile {
avatar
description
motto
}
owner {
nickname
visible
profile {
avatar
}
}
members {
role
member {
nickname
visible
profile {
avatar
}
}
}
}
}
}`)
return ctx.db.query.teamsConnection({}, TEAMS_CONNECTIONS_FRAGMENT)
},
async teamById(parent, { id }, ctx, info) {
return ctx.db.query.team({ where: { id } }, info)
@@ -58,65 +56,33 @@ const resolvers = {
async teamByName(parent, { name }, ctx, info) {
return ctx.db.query.team({ where: { name: name } }, `{ name }`)
},
async teamByOwner(parent, { ownerId }, ctx, info) {
return ctx.db.query.teamsConnection({where: { owner: { auth0id: ownerId }}},
`{
edges {
node {
id
name
slug
profile {
avatar
description
motto
}
owner {
auth0id
nickname
visible
profile {
avatar
}
}
members {
role
member {
nickname
visible
profile {
avatar
}
}
}
}
}
}`)
async teamsByOwner(parent, { ownerId }, ctx, info) {
console.log('teamsByOwner: ', ctx.user)
return ctx.db.query.teams({where: { owner: ownerId }, orderBy:'updatedAt_DESC'}, TEAMS_FRAGMENT)
},
async owner(parent, args, ctx, info) {
console.log('resolver.query.owner ctx: ', ctxMember(ctx))
return ctx.db.query.member({ where: { id: ctxMember(ctx).auth0id } }, info)
return ctx.db.query.member({ where: { id: ctxMember(ctx).authId } }, info)
.catch((res) => {
console.log('createTeam error: ', res)
const errors = res.graphQLErrors.map((error) => {
return error.message
})
})
},
members: forwardTo('db')
}
},
Mutation: {
async authenticate(parent, { idToken }, ctx, info) {
let memberToken = null
try {
memberToken = await validateAndParseIdToken(idToken)
memberToken = await validateIdToken(idToken)
console.log('authenticate.memberToken: ', await memberToken)
} catch (err) {
console.log('authenticat.validateAndParseIdToken err: ', err)
console.log('authenticat.validateIdToken err: ', err)
throw new Error(err.message)
}
const auth0id = memberToken.sub
let member = await ctx.db.query.member({ where: { auth0id } }, info)
const authId = memberToken.sub
let member = await ctx.db.query.member({ where: { authId } }, info)
if (!member) {
try {
@@ -124,28 +90,25 @@ const resolvers = {
} catch (error) {
throw new Error(error.message)
}
}
}
return member
},
async createTeam(parent, { name, slug, owner }, ctx, info) {
return ctx.db.mutation.createTeam({
data: {
name: name,
slug: slug,
owner: {connect:{auth0id: owner}},
members: {create: {role: 'OWNER', member: {connect: {auth0id: owner}}}}
}
}, info)
.catch((res) => {
console.log('createTeam error: ', res)
const errors = res.graphQLErrors.map((error) => {
return error.message
})
return ctx.db.mutation.createTeam({ data: { name: name, slug: slug, owner: owner, members: { create: { member:{ connect:{ authId: owner} }, role: 'OWNER' } }, profile:{create:{avatar:"a"} } } }, TEAMS_FRAGMENT)
.catch((err) => {
console.log('createTeam error: ', err)
return err
})
},
async deleteTeam(parent, { id }, ctx, info) {
return ctx.db.mutation.deleteTeam({ where: { id } }, info)
async updateTeamProfile(parent, { slug, owner, description, motto, avatar }, ctx, info) {
return ctx.db.mutation.updateTeam({data:{profile: {update: {description: description, motto: motto, avatar: avatar}}}, where:{slug: slug}}, TEAMS_FRAGMENT)
.catch((err) => {
console.log('createTeam error: ', err)
return err
})
},
async deleteTeam(parent, { slug }, ctx, info) {
return ctx.db.mutation.deleteTeam({ where: { slug } }, info)
.catch((res) => {
console.log('deleteTeam error: ', res)
const errors = res.graphQLErrors.map((error) => {
@@ -174,27 +137,34 @@ const schema = makeExecutableSchema({
const server = new GraphQLServer({
schema,
context: req => ({
...req,
db
})
token: req.headers,
user: req.user,
db,
...req
}),
})
server.express.post(
server.express.use(
server.options.endpoint,
checkJwt,
(err, req, res, next) => {
if (err) return res.status(401).send(err.message)
next()
function (err, req, res, next) {
console.log('checkJwt: ', err, req)
if (err) {
return res.status(201).send(err.message)
} else {
next()
}
}
)
server.express.post(server.options.endpoint, (req, res, done) =>
getMember(req, res, done, db)
)
server.express.post(server.options.endpoint, (req, res, done) => getUser(req, res, done, db))
const whitelist = [
'http://localhost:1337',
'http://0.0.0.0:5000'
'http://localhost:3000',
'http://localhost:3001',
'http://localhost:3002',
'http://localhost:4000',
'http://localhost:4002'
]
const corsOptionsDelegate = (req, callback) => {
var corsOptions
@@ -207,12 +177,13 @@ const corsOptionsDelegate = (req, callback) => {
}
const options = {
port: 4001,
cors: corsOptionsDelegate,
endpoint: '/graphql',
subscriptions: '/graphql',
playground: '/playground'
}
server.start(options, () => console.log(`Server is running on http://localhost:4000${server.options.endpoint}`))
server.start(options, () => console.log(`Server is running on http://localhost:4001${server.options.endpoint}`))
module.exports= { createPrismaMember: createPrismaMember }
module.exports= { createMember }
+49
View File
@@ -0,0 +1,49 @@
# import Query.TeamConnection, Team from "./generated/prisma.graphql"
directive @isAuthenticated on QUERY | FIELD | MUTATION
directive @hasRole(roles: [String]) on QUERY | FIELD | MUTATION
directive @isOwner(type: String) on QUERY | MUTATION
directive @isOwnerOrHasRole(type: String, roles: [String]) on QUERY | MUTATION
type Query {
teams: TeamConnection
teamDetails: [Team!]! @isAuthenticated
teamByName(name: String!): Team
teamById(teamId: String!): Team
teamsByOwner(ownerId: String!): [Team!]! @isAuthenticated
owner: Member @isAuthenticated
currentMember: Member @isAuthenticated
member(authId: String!): Member @isAuthenticated
}
type Mutation {
authenticate(idToken: String!): Member
createTeam(name: String!, slug: String!, owner: String!): Team @isAuthenticated
updateTeamProfile(slug: String!, owner: String!, description: String, motto: String, avatar: String): Team @isAuthenticated
deleteTeam(slug: String!): Team @isAuthenticated @isOwner(type: "Team")
}
input MemberProfileInput {
authId: String!
alias: String!
}
fragment MemberInfo on Member {
id
authId
alias
isActive
profile {
...MemberProfileInfo
}
}
fragment MemberProfileInfo on MemberProfile {
firstName
lastName
email
avatar
role {
name
}
}
-35
View File
@@ -1,35 +0,0 @@
# import Query.TeamConnection, Team from "./generated/prisma.graphql"
directive @isAuthenticated on QUERY | FIELD | MUTATION
directive @hasRole(roles: [String]) on QUERY | FIELD | MUTATION
directive @isOwner(type: String) on QUERY | MUTATION
directive @isOwnerOrHasRole(type: String, roles: [String]) on QUERY | MUTATION
type Query {
teams: TeamConnection
teamDetails: [Team!]! @isAuthenticated
teamByName(name: String!): Team
teamById(teamId: String!): Team
teamByOwner(ownerId: String!): TeamConnection
owner: Member @isAuthenticated
member(auth0id: String!): Member @isAuthenticated
}
type Mutation {
authenticate(idToken: String!): Member
createTeam(name: String!, slug: String, owner: String): Team @isAuthenticated
deleteTeam(id: ID!): Team @isAuthenticated @isOwner(type: "Team")
updateProfile(data: MemberProfileInput): Member @isAuthenticated
}
input MemberProfileInput {
auth0id: String!
nickname: String!
anonymous: Boolean
visible: Boolean
avatar: String
email: String
repo: String
bio: String
status: String
}
+10 -1
View File
@@ -20,7 +20,9 @@ import {
Footer,
Callback,
Home,
About,
Dashboard,
Teams,
theme,
globalStyles
} from './views'
@@ -148,7 +150,14 @@ class App extends Component {
<NavBar auth={auth} />
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/dashboard' component={Dashboard} />
<Route exact path='/about' component={About} />
<Route exact path='/teams' component={Teams} />
<Route exact path='/teams/:slug' component={Teams} />
<Route
exact
path='/(dashboard|manage-teams|team-members|settings)/'
render={props => <Dashboard {...props} auth={auth} />}
/>
<Route
path='/callback'
render={props => {
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 120 24"><defs><style>.cls-1,.cls-3{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{stroke:#2a96ff;stroke-miterlimit:10;stroke-width:0.5px;}.cls-4{fill:#fff;}.cls-5{fill:#e3493c;}</style><clipPath id="clip-path"><rect class="cls-1" x="272.54" y="79.59" width="600" height="600"/></clipPath></defs><title>aa-logo-dark</title><g id="Layer_1" data-name="Layer 1"><g class="cls-2"><circle class="cls-3" cx="338.14" cy="263.28" r="277.97"/></g></g><g id="Layer_4" data-name="Layer 4"><path class="cls-4" d="M27.67,15.74l-.19-1.58H26.29l-.2,1.58H24.51L25.7,7.79h2.39l1.19,8Zm-.75-6.39h-.08l-.4,3.41h.89Z"/><path class="cls-4" d="M32.15,7.79h1.93a2.39,2.39,0,0,1,1.46.35,1.68,1.68,0,0,1,.54,1.11,21,21,0,0,1,.1,2.52,20.93,20.93,0,0,1-.1,2.51,1.68,1.68,0,0,1-.54,1.11,2.39,2.39,0,0,1-1.46.35H32.15Zm1.93,6.51a.46.46,0,0,0,.37-.12,1.19,1.19,0,0,0,.13-.6c0-.32,0-.93,0-1.81s0-1.5,0-1.82a1.19,1.19,0,0,0-.13-.6.46.46,0,0,0-.37-.12h-.36V14.3Z"/><path class="cls-4" d="M39.94,15.74,38.87,7.79h1.59l.66,6.31h.12l.61-6.31h1.57l-1.07,8Z"/><path class="cls-4" d="M48.8,15.74l-.19-1.58H47.42l-.2,1.58H45.64l1.19-7.95h2.39l1.19,8Zm-.75-6.39H48l-.4,3.41h.89Z"/><path class="cls-4" d="M56.06,15.74l-1.23-4h0v4h-1.5V7.79h1.47l1.13,3.61h.06V7.79h1.51v8Z"/><path class="cls-4" d="M63.14,15.77a3.11,3.11,0,0,1-.59.06,2.06,2.06,0,0,1-1.36-.37,1.94,1.94,0,0,1-.58-1.19,17.1,17.1,0,0,1-.13-2.5,14.22,14.22,0,0,1,.15-2.5,1.94,1.94,0,0,1,.58-1.2,2,2,0,0,1,1.34-.37,4.21,4.21,0,0,1,.62,0,1.86,1.86,0,0,1,.45.12V9.2a5.77,5.77,0,0,0-.78-.06.77.77,0,0,0-.55.15,1.22,1.22,0,0,0-.18.64c0,.33,0,.94,0,1.84s0,1.5,0,1.83a1.22,1.22,0,0,0,.18.64.77.77,0,0,0,.55.15,4.74,4.74,0,0,0,.8-.06v1.29A1.56,1.56,0,0,1,63.14,15.77Z"/><path class="cls-4" d="M66.59,15.74V7.79h3.19V9.23h-1.6V11h1.41v1.45H68.18V14.3h1.6v1.44Z"/><path class="cls-4" d="M72.79,7.79h1.92a2.4,2.4,0,0,1,1.47.35,1.67,1.67,0,0,1,.53,1.11,20.46,20.46,0,0,1,.11,2.52,20.38,20.38,0,0,1-.11,2.51,1.67,1.67,0,0,1-.53,1.11,2.4,2.4,0,0,1-1.47.35H72.79Zm1.92,6.51a.48.48,0,0,0,.38-.12,1.19,1.19,0,0,0,.13-.6c0-.32,0-.93,0-1.81s0-1.5,0-1.82a1.19,1.19,0,0,0-.13-.6.48.48,0,0,0-.38-.12h-.35V14.3Z"/><path class="cls-5" d="M85.6,15.74l-.91-2.31H80.93L80,15.74h-.76l3.11-7.95h.89l3.12,8ZM82.84,8.66h-.06l-1.61,4.08h3.27Z"/><path class="cls-5" d="M88.44,15.74V7.79h.74V15H93v.72Z"/><path class="cls-5" d="M97.86,12.24v-.68h2.7v4c-.37.06-.8.12-1.26.16s-.87.06-1.22.06a5.68,5.68,0,0,1-2.14-.3A1.71,1.71,0,0,1,95,14.4a9.76,9.76,0,0,1-.26-2.63A9.85,9.85,0,0,1,95,9.13,1.71,1.71,0,0,1,96,8a5.91,5.91,0,0,1,2.13-.29c.38,0,.81,0,1.27.06s.86.1,1.18.16v.66a13.61,13.61,0,0,0-2-.19,8.76,8.76,0,0,0-2,.16,1.21,1.21,0,0,0-.79.85,8.45,8.45,0,0,0-.23,2.37A10.48,10.48,0,0,0,95.66,14a1.27,1.27,0,0,0,.7.91,4.41,4.41,0,0,0,1.72.24l.9,0c.35,0,.64,0,.87-.09V12.24Z"/><path class="cls-5" d="M103.84,15.54a1.48,1.48,0,0,1-.83-1.08,13.91,13.91,0,0,1-.17-2.69,14,14,0,0,1,.17-2.7A1.48,1.48,0,0,1,103.84,8a6,6,0,0,1,2.21-.29,5.94,5.94,0,0,1,2.2.29,1.46,1.46,0,0,1,.83,1.08,13.75,13.75,0,0,1,.18,2.7,13.7,13.7,0,0,1-.18,2.69,1.46,1.46,0,0,1-.83,1.08,5.94,5.94,0,0,1-2.2.29A6,6,0,0,1,103.84,15.54Zm3.93-.57a1,1,0,0,0,.61-.81,13.62,13.62,0,0,0,.14-2.39,13.68,13.68,0,0,0-.14-2.4,1,1,0,0,0-.61-.81,8.84,8.84,0,0,0-3.45,0,1,1,0,0,0-.6.81,12.47,12.47,0,0,0-.15,2.4,12.42,12.42,0,0,0,.15,2.39,1,1,0,0,0,.6.81,8.84,8.84,0,0,0,3.45,0Z"/><path class="cls-5" d="M112.94,15.79c-.5,0-.94-.06-1.31-.11V15l1.33.1c.48,0,.88.05,1.2.05a8.75,8.75,0,0,0,1-.05,1.78,1.78,0,0,0,.65-.22.68.68,0,0,0,.35-.47,3.13,3.13,0,0,0,.09-.83,4.54,4.54,0,0,0-.06-.9.57.57,0,0,0-.31-.37,3.72,3.72,0,0,0-.88-.18l-2-.22a2.08,2.08,0,0,1-1.06-.32,1.18,1.18,0,0,1-.44-.64,5,5,0,0,1-.09-1.07,2.57,2.57,0,0,1,.3-1.38,1.56,1.56,0,0,1,.9-.63,6.12,6.12,0,0,1,1.59-.16c.45,0,.9,0,1.35,0s.81.08,1.07.13v.66c-.31,0-.68-.07-1.11-.1s-.83,0-1.19,0a6.52,6.52,0,0,0-1.1.07,1.88,1.88,0,0,0-.66.22.73.73,0,0,0-.33.44,2.52,2.52,0,0,0-.08.75,2.94,2.94,0,0,0,.07.77.61.61,0,0,0,.29.36,2.05,2.05,0,0,0,.69.16l2,.21a4.86,4.86,0,0,1,1.17.24,1,1,0,0,1,.55.63,4.24,4.24,0,0,1,.13,1.27,3,3,0,0,1-.31,1.54,1.36,1.36,0,0,1-.85.64,5.78,5.78,0,0,1-1.47.14C113.93,15.83,113.45,15.82,112.94,15.79Z"/><path class="cls-5" d="M11.36,13.89a7.43,7.43,0,0,1,2.52.4c.82.58,2,.89,2.5.3s0-1.61-.79-2.38a7.64,7.64,0,0,1-.93-2.35c.11-1-.23-2.23-1-2.35s-1.47,1-1.65,1.88a7.38,7.38,0,0,1-1.61,2c-.9.42-1.77,1.3-1.49,2S10.48,14.16,11.36,13.89Z"/><path class="cls-5" d="M14.75,3.42C16,3.47,16.49,6,16.29,7.91a26.16,26.16,0,0,0,1.64,4.69,1.88,1.88,0,0,0,.79.79,10.55,10.55,0,0,1-.84-5c.79-2.19.44-5.15-.75-5.84a2.71,2.71,0,0,0-3.68,1.2A1.8,1.8,0,0,1,14.75,3.42Z"/><path class="cls-5" d="M5,14C4.4,13,6.3,11.26,8.09,10.45,8.66,10.2,11,7.2,11.33,6.7a2,2,0,0,0,.29-1.09A10.62,10.62,0,0,1,7.68,8.86C5.38,9.27,3,11.05,3,12.42A2.71,2.71,0,0,0,5.87,15,1.88,1.88,0,0,1,5,14Z"/><path class="cls-5" d="M19.12,17.42c-.66,1-3.07.23-4.67-.92a25.88,25.88,0,0,0-4.87-.92,1.79,1.79,0,0,0-1.08.29,10.37,10.37,0,0,1,4.Line truncated

After

Width:  |  Height:  |  Size: 5.0 KiB

+24 -15
View File
@@ -102,9 +102,13 @@ class Auth {
this.signinOrCreateAccount({ ...data })
this.cb(data)
console.log(authResult.idTokenPayload)
setItem('user', JSON.stringify(authResult.idTokenPayload))
const user = {
authId: authResult.idTokenPayload.sub,
alias: authResult.idTokenPayload.nickname
}
setItem('user', JSON.stringify(user))
if (window.location.href.includes(`callback`)) {
window.location.href = '/dashboard'
// window.location.href = '/dashboard'
}
return true
}
@@ -126,11 +130,13 @@ class Auth {
})
console.log('signinOrCreateAccount auth: ', await data)
setItem('user', JSON.stringify(data.data.authenticate))
const user = {
authId: data.data.authenticate.authId,
alias: data.data.authenticate.alias
}
setItem('user', JSON.stringify(user))
if (window.location.href.includes(`callback`)) {
window.location.href = '/dashboard'
} else {
window.location.reload()
}
return data
} catch (err) {
@@ -157,10 +163,16 @@ class Auth {
async isAuthenticated () {
// check session and run Auth0 SS0
const getUser = await getItem('user')
const getExpires = await getItem('expires_at')
let user = JSON.parse(getUser)
if (validObject(user, 'exp') && new Date().getTime() < user.exp * 1000) {
console.log('handleAuth.user exp: ', user, window.location.href)
if (new Date().getTime() < getExpires * 1000) {
console.log(
'handleAuth.user exp: ',
user,
getExpires,
window.location.href
)
return user
}
@@ -168,7 +180,11 @@ class Auth {
const checkSSO = await this.checkSession()
.then(result => {
console.log('handleAuth.checksessions: ', result)
setItem('user', result.idTokenPayload.sub)
const user = {
authId: result.idTokenPayload.sub,
alias: result.idTokenPayload.nickname
}
setItem('user', JSON.stringify(user))
// user confirmed, log into client
this.setSession(result)
return result.idTokenPayload
@@ -180,13 +196,6 @@ class Auth {
}
return err
})
if (
(window.location.href.includes(`manage`) ||
window.location.href.includes(`profile`)) &&
!user
) {
window.location.href = '/'
}
if (/manage|profile|create|dashboard/.test(window.location.href) && !user) {
this.login()
@@ -1,14 +1,14 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const CreateTeamMutation = gql`
const CREATE_TEAM = gql`
mutation CreateTeamMutation($name: String!, $slug: String!, $owner: String!) {
createTeam(name: $name, slug: $slug, owner: $owner) {
id
name
slug
owner
members{
members {
role
member {
authId
@@ -18,3 +18,5 @@ export const CreateTeamMutation = gql`
}
}
`
export default CREATE_TEAM
@@ -1,10 +1,12 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const DeleteTeamMutation = gql`
mutation deleteTeam($id: ID!) {
deleteTeam(id: $id) {
export const DELETE_TEAM = gql`
mutation deleteTeam($slug: String!) {
deleteTeam(slug: $slug) {
id
}
}
`
export default DELETE_TEAM
+12 -14
View File
@@ -1,7 +1,7 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const GetAllTeamsQuery = gql`
export const GET_ALL_TEAMS_QUERY = gql`
query teamsQuery {
teams {
edges {
@@ -9,26 +9,24 @@ export const GetAllTeamsQuery = gql`
id
name
slug
owner
status {
status
reason
createdAt
}
createdAt
profile {
avatar
description
motto
}
owner {
nickname
visible
profile {
avatar
}
updatedAt
}
members {
role
member {
nickname
visible
profile {
avatar
}
alias
authId
}
}
}
@@ -37,4 +35,4 @@ export const GetAllTeamsQuery = gql`
}
`
export default GetAllTeamsQuery
export default GET_ALL_TEAMS_QUERY
-40
View File
@@ -1,40 +0,0 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const GetTeamQuery = gql`
query teamDetailsQuery($auth0id: String!) {
teamByOwner(ownerId: $auth0id) {
edges {
node {
id
name
slug
profile {
avatar
description
motto
}
owner {
nickname
visible
profile {
avatar
}
}
members {
role
member {
nickname
visible
profile {
avatar
}
}
}
}
}
}
}
`
export default GetTeamQuery
@@ -0,0 +1,34 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const GET_TEAMS_BY_OWNER = gql`
query teamsByOwnerQuery($authId: String!) {
teamsByOwner(ownerId: $authId) {
id
name
slug
owner
status {
status
reason
createdAt
}
createdAt
profile {
avatar
description
motto
updatedAt
}
members {
role
member {
alias
authId
}
}
}
}
`
export default GET_TEAMS_BY_OWNER
+32
View File
@@ -0,0 +1,32 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const TEAMS_FRAGMENT = gql`
fragment TeamsResponse on teams {
id
name
slug
owner
status {
status
reason
createdAt
}
createdAt
profile {
avatar
description
motto
updatedAt
}
members {
role
member {
alias
authId
}
}
}
`
export default TEAMS_FRAGMENT
@@ -0,0 +1,34 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
const UPDATE_TEAM_PROFILE = gql`
mutation UpdateTeamProfileMutation($slug: String!, $owner: String!, $description: String, $motto: String) {
updateTeamProfile(slug: $slug, owner: $owner, description: $description, motto: $motto) {
id
name
slug
owner
status {
status
reason
createdAt
}
createdAt
profile {
avatar
description
motto
updatedAt
}
members {
role
member {
alias
authId
}
}
}
}
`
export default UPDATE_TEAM_PROFILE
+13
View File
@@ -0,0 +1,13 @@
const ErrorResponse = {
'unique constraint': 'Sorry, already taken! Please create a unique '
}
export const checkGraphQLError = error => {
console.log('checkGraphQLError ', ErrorResponse)
if (error.search(/unique constraint/)) {
const field = error.substring(error.indexOf('=') + 1)
console.log('unique constraint', field.replace(/ +/g, ''))
}
}
export default checkGraphQLError
+4
View File
@@ -19,6 +19,10 @@ export function isEmpty (obj) {
return true
}
export function isString (value) {
return typeof value === 'string' || value instanceof String
}
export function validObject (object, ...keys) {
return keys.reduce((a, b) => (a || {})[b], object) !== undefined
}
+61
View File
@@ -0,0 +1,61 @@
import React from 'react'
import PropTypes from 'prop-types'
import CssBaseline from '@material-ui/core/CssBaseline'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
const styles = theme => ({
'@global': {
body: {
backgroundColor: theme.palette.common.white
}
},
layout: {
width: 'auto',
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
[theme.breakpoints.up(900 + theme.spacing.unit * 3 * 2)]: {
width: 900,
marginLeft: 'auto',
marginRight: 'auto'
}
},
heroContent: {
maxWidth: 600,
margin: '0 auto',
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
}
})
const About = ({ classes }) => (
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<div className={classes.heroContent}>
<Typography
variant='display3'
align='center'
color='textPrimary'
gutterBottom
>
Collaborate. Innovate. Evolve.
</Typography>
<Typography
variant='title'
align='center'
color='textSecondary'
component='h3'
>
Welcome to the Advanced Algos Teams Module
</Typography>
</div>
</main>
</React.Fragment>
)
About.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(styles)(About)
+43
View File
@@ -0,0 +1,43 @@
import React from 'react'
import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
const styles = theme => ({
card: {
display: 'flex',
width: '100%',
margin: '1.5em',
padding: '3em'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
}
})
export const MessageCard = ({ classes, message, children }) => (
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardContent className={classes.cardContent}>
<Typography variant='headline' align='center' gutterBottom>{message}</Typography>
{children}
</CardContent>
</div>
</Card>
)
MessageCard.propTypes = {
children: PropTypes.node,
classes: PropTypes.object.isRequired,
message: PropTypes.string
}
export default withStyles(styles)(MessageCard)
+3
View File
@@ -0,0 +1,3 @@
import MessageCard from './MessageCard'
export { MessageCard }
+28
View File
@@ -0,0 +1,28 @@
import React from 'react'
import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import ManageTeamsList from './components/ManageTeamsList'
const styles = theme => ({
tableContainer: {
height: 320
}
})
const ManageTeams = ({ classes }) => (
<div>
<Typography variant='display1' gutterBottom>
Manage Your Teams
</Typography>
<ManageTeamsList />
</div>
)
ManageTeams.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(styles)(ManageTeams)
+63
View File
@@ -0,0 +1,63 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Grid from '@material-ui/core/Grid'
import DashTeam from './components/DashTeam'
import DashEditor from './components/DashEditor'
const styles = theme => ({
root: {
display: 'flex'
},
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 160,
height: 160,
justifyContent: 'flex-start'
},
buttonRight: {
justifyContent: 'flex-end'
},
aawebMedia: {
width: '100%',
height: 320
},
dashLink: {
float: 'right',
color: '#111',
fontSize: 16,
textDecoration: 'none',
paddingRight: '1em'
}
})
export class Overview extends Component {
render () {
const { classes } = this.props
return (
<Grid container spacing={24}>
<DashTeam classes={classes} />
<DashEditor classes={classes} />
</Grid>
)
}
}
Overview.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(styles)(Overview)
+30
View File
@@ -0,0 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import { MessageCard } from '../common/'
const styles = theme => ({
tableContainer: {
height: 320
}
})
const TeamMembers = ({ classes }) => (
<div>
<Typography variant='display1' gutterBottom>
Members
</Typography>
<div className={classes.tableContainer}>
<MessageCard message='Coming soon. Invite and manage team members' />
</div>
</div>
)
TeamMembers.propTypes = {
classes: PropTypes.object.isRequired
}
export default withStyles(styles)(TeamMembers)
@@ -1,135 +1,100 @@
import React, { Component } from 'react'
import React from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
import Button from '@material-ui/core/Button'
import AddIcon from '@material-ui/icons/Add'
import TextField from '@material-ui/core/TextField'
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 { Mutation } from 'react-apollo'
import Typography from '@material-ui/core/Typography'
import { getItem } from '../../../utils/local-storage'
import { checkGraphQLError } from '../../../utils/graphql-errors'
export class CreateTeamDialog extends Component {
constructor (props) {
super(props)
import CREATE_TEAM from '../../../graphql/teams/CreateTeamMutation'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handlePost = this.handlePost.bind(this)
this.slugify = this.slugify.bind(this)
this.handleChange = this.handleChange.bind(this)
const CreateTeam = ({ authId }) => {
let input
console.log('CreateTeam', authId)
this.state = {
open: false,
name: ''
}
}
handleClickOpen () {
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false })
}
handleChange (e) {
this.setState({ name: e.target.value })
}
render () {
console.log(this.props)
return (
<div>
<Button
variant='extendedFab'
aria-label='AddTeam'
className={this.props.classes.button}
onClick={this.handleClickOpen}
>
<AddIcon className={this.props.classes.extendedIcon} />
<Typography variant='subheading'>
Create A Team
</Typography>
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<DialogTitle id='form-dialog-title'>Subscribe</DialogTitle>
<DialogContent>
<Typography variant='subheading'>
Team Creation
</Typography>
<DialogContentText>
Name your team!
</DialogContentText>
<TextField
autoFocus
margin='dense'
id='teamname'
label='Team Name'
type='text'
fullWidth
value={this.state.name}
onChange={this.handleChange}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button onClick={this.handlePost} color='primary'>
Create Team
</Button>
</DialogActions>
</Dialog>
</div>
)
}
slugify (string) {
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
const p = new RegExp(a.split('').join('|'), 'g')
return string.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w-]+/g, '') // Remove all non-word characters
.replace(/--+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
async handlePost (e) {
e.preventDefault()
const { name } = this.state
const slug = this.slugify(name)
const currentUser = await getItem('user')
let authId = JSON.parse(currentUser)
authId = authId.sub
console.log('createPage cu:', JSON.parse(currentUser))
await this.props.createTeamMutation({
variables: {
name,
slug,
owner: authId
}
})
this.setState({ open: false })
}
return (
<Mutation
mutation={CREATE_TEAM}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER,
variables: { authId }
}
]}
>
{(createTeam, { loading, error, data }) => {
let errors
let loader
if (loading) {
loader = <Typography variant='caption'>Submitting team...</Typography>
}
if (error) {
errors = error.graphQLErrors.map(({ message }, i) => {
const displayMessage = checkGraphQLError(message)
console.log('createTeam error:', displayMessage)
return (
<Typography key={i} variant='caption'>
{message}
</Typography>
)
})
}
return (
<div>
<form
onSubmit={e => {
handleSubmit(e, createTeam, input)
}}
>
<input
ref={node => {
input = node
}}
/>
<button type='submit'>Create Team</button>
</form>
{loader}
{errors}
</div>
)
}}
</Mutation>
)
}
CreateTeamDialog.propTypes = {
classes: PropTypes.object.isRequired,
createTeamMutation: PropTypes.function
CreateTeam.propTypes = {
authId: PropTypes.any
}
export default withRouter(CreateTeamDialog)
const handleSubmit = async (e, createTeam, input) => {
e.preventDefault()
const currentUser = await getItem('user')
let authId = JSON.parse(currentUser)
authId = authId.authId
console.log('createTeam submit: ', authId)
const name = input.value
const slug = slugify(name)
await createTeam({ variables: { name, slug, owner: authId } })
input.value = ''
}
const slugify = string => {
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
const p = new RegExp(a.split('').join('|'), 'g')
return string
.toString()
.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w-]+/g, '') // Remove all non-word characters
.replace(/--+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
export default CreateTeam
@@ -0,0 +1,196 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Mutation } from 'react-apollo'
import { withStyles } from '@material-ui/core/styles'
import Button from '@material-ui/core/Button'
import AddIcon from '@material-ui/icons/Add'
import TextField from '@material-ui/core/TextField'
import FormControl from '@material-ui/core/FormControl'
import FormHelperText from '@material-ui/core/FormHelperText'
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 Typography from '@material-ui/core/Typography'
import { getItem } from '../../../utils/local-storage'
import { isEmpty } from '../../../utils/js-helpers'
import CREATE_TEAM from '../../../graphql/teams/CreateTeamMutation'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
// import { checkGraphQLError } from '../../../utils/graphql-errors'
const styles = theme => ({
dialogStyle: {
padding: '3em'
},
buttonRight: {
// position: 'absolute',
// right: '3em'
marginTop: '1em',
marginRight: 'auto',
marginLeft: 'auto',
display: 'block',
lineHeight: '1em'
}
})
export class CreateTeamDialog extends Component {
constructor (props) {
super(props)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.validate = this.validate.bind(this)
this.slugify = this.slugify.bind(this)
this.state = {
open: false,
name: '',
errors: {
name: ''
}
}
}
render () {
const { classes, authId } = this.props
return (
<Mutation
mutation={CREATE_TEAM}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER,
variables: { authId }
}
]}
>
{(createTeam, { loading, error, data }) => {
let errors
let loader = null
if (loading) {
loader = <Typography variant='subheading'>Submitting team...</Typography>
}
if (error) {
errors = error.graphQLErrors.map(({ message }, i) => {
// const displayMessage = checkGraphQLError(message)
return (
<Typography key={i} variant='caption'>
{message}
</Typography>
)
})
}
return (
<div>
<Button variant='fab' color='primary' aria-label='Add' className={classes.buttonRight} onClick={this.handleClickOpen}>
<AddIcon />
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<DialogTitle id='form-dialog-title'>Create a Team</DialogTitle>
<DialogContent>
<DialogContentText>Team Name:</DialogContentText>
<FormControl required error={e => (this.state.errors.name !== '' || error)}>
<TextField
autoFocus
margin='dense'
id='teamname'
label='Team Name'
type='text'
fullWidth
value={this.state.name}
onChange={this.handleChange}
error={this.state.errors.name !== '' || error}
/>
{this.state.errors.name !== '' && (<FormHelperText>{this.state.errors.name}</FormHelperText>)}
{error && (<FormHelperText>{errors}</FormHelperText>)}
{loader}
</FormControl>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button onClick={e => {
this.handleSubmit(e, createTeam, this.state.name)
}} color='primary'>
Create Team
</Button>
</DialogActions>
</Dialog>
</div>
)
}}
</Mutation>
)
}
handleClickOpen () {
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false })
}
handleChange (e) {
const errors = this.validate(e.target.value)
if (isEmpty(errors)) {
this.setState({ errors: { name: '' } })
this.setState({ name: e.target.value })
} else {
this.setState({ errors: errors })
this.setState({ name: e.target.value })
}
}
async handleSubmit (e, createTeam, name) {
e.preventDefault()
const currentUser = await getItem('user')
let authId = JSON.parse(currentUser)
authId = authId.authId
console.log('createTeam submit: ', authId, name)
const slug = this.slugify(name)
await createTeam({ variables: { name, slug, owner: authId } })
this.setState({ name: '' })
}
validate (data) {
const errors = {}
if (data === '') errors.name = 'Please enter a team name'
return errors
}
slugify (string) {
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
const p = new RegExp(a.split('').join('|'), 'g')
return string
.toString()
.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w-]+/g, '') // Remove all non-word characters
.replace(/--+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
}
CreateTeamDialog.propTypes = {
classes: PropTypes.object.isRequired,
authId: PropTypes.string
}
export default withStyles(styles)(CreateTeamDialog)
@@ -0,0 +1,131 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withRouter } from 'react-router-dom'
import Button from '@material-ui/core/Button'
import AddIcon from '@material-ui/icons/Add'
import TextField from '@material-ui/core/TextField'
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 Typography from '@material-ui/core/Typography'
import { getItem } from '../../../utils/local-storage'
export class CreateTeamDialog extends Component {
constructor (props) {
super(props)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handlePost = this.handlePost.bind(this)
this.slugify = this.slugify.bind(this)
this.handleChange = this.handleChange.bind(this)
this.state = {
open: false,
name: ''
}
}
handleClickOpen () {
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false })
}
handleChange (e) {
this.setState({ name: e.target.value })
}
render () {
console.log(this.props)
return (
<div>
<Button
variant='extendedFab'
aria-label='AddTeam'
className={this.props.classes.button}
onClick={this.handleClickOpen}
>
<AddIcon className={this.props.classes.extendedIcon} />
<Typography variant='subheading'>Create A Team</Typography>
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<DialogTitle id='form-dialog-title'>Subscribe</DialogTitle>
<DialogContent>
<Typography variant='subheading'>Team Creation</Typography>
<DialogContentText>Name your team!</DialogContentText>
<TextField
autoFocus
margin='dense'
id='teamname'
label='Team Name'
type='text'
fullWidth
value={this.state.name}
onChange={this.handleChange}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button onClick={this.handlePost} color='primary'>
Create Team
</Button>
</DialogActions>
</Dialog>
</div>
)
}
slugify (string) {
const a = 'àáäâãåèéëêìíïîòóöôùúüûñçßÿœæŕśńṕẃǵǹḿǘẍźḧ·/_,:;'
const b = 'aaaaaaeeeeiiiioooouuuuncsyoarsnpwgnmuxzh------'
const p = new RegExp(a.split('').join('|'), 'g')
return string
.toString()
.toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(p, c => b.charAt(a.indexOf(c))) // Replace special characters
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w-]+/g, '') // Remove all non-word characters
.replace(/--+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, '') // Trim - from end of text
}
async handlePost (e) {
e.preventDefault()
const { name } = this.state
const slug = this.slugify(name)
const currentUser = await getItem('user')
let authId = JSON.parse(currentUser)
authId = authId.sub
console.log('createPage cu:', JSON.parse(currentUser))
await this.props.createTeamMutation({
variables: {
name,
slug,
owner: authId
}
})
this.setState({ open: false })
}
}
CreateTeamDialog.propTypes = {
classes: PropTypes.object.isRequired,
createTeamMutation: PropTypes.function
}
export default withRouter(CreateTeamDialog)
@@ -0,0 +1,50 @@
import React from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Grid from '@material-ui/core/Grid'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CreateTeam from './CreateTeam'
const styles = theme => ({
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 160,
height: 160,
justifyContent: 'flex-start'
}
})
const CreateTeamWrapper = ({ classes, authId }) => (
<Grid container spacing={24}>
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardContent className={classes.createCardContent}>
<CreateTeam classes={classes} authId={authId} />
</CardContent>
</div>
</Card>
</Grid>
</Grid>
)
CreateTeamWrapper.propTypes = {
classes: PropTypes.object.isRequired,
authId: PropTypes.string
}
export default withStyles(styles)(CreateTeamWrapper)
@@ -0,0 +1,42 @@
import React from 'react'
import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import Grid from '@material-ui/core/Grid'
import Card from '@material-ui/core/Card'
import CardMedia from '@material-ui/core/CardMedia'
import CardActions from '@material-ui/core/CardActions'
import Button from '@material-ui/core/Button'
import aaweb from '../../../assets/AlgonetWebPlatform.jpg'
export const DashEditor = ({ classes }) => (
<Grid item md={6}>
<Typography variant='display1' gutterBottom>
Develop
</Typography>
<Grid container spacing={24}>
<Grid item md={12}>
<Card>
<CardMedia
className={classes.aawebMedia}
image={aaweb}
title='Image title'
/>
<CardActions>
<Typography variant='title' color='inherit' aligh='right'>
<Button>Start Developing ></Button>
</Typography>
</CardActions>
</Card>
</Grid>
</Grid>
</Grid>
)
DashEditor.propTypes = {
classes: PropTypes.object.isRequired
}
export default DashEditor
@@ -0,0 +1,124 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Query } from 'react-apollo'
import { withStateHandlers, lifecycle, compose } from 'recompose'
import { Link } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
import Grid from '@material-ui/core/Grid'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import DashTeamItem from './DashTeamItem'
import CreateTeamDialog from './CreateTeamDialog'
import { MessageCard } from '../../common/'
import { isDefined, isString } from '../../../utils/js-helpers'
import { getItem } from '../../../utils/local-storage'
export const DashTeam = ({ classes, user = null }) => {
let owner
let loader
let authId = null
console.log('DashTeam: ', user)
if (user !== null && isString(user)) {
owner = JSON.parse(user)
if (isDefined(owner.authId)) authId = owner.authId
} else {
loader = <Typography variant='caption'>Loading...</Typography>
}
console.log('DashTeam 2: ', owner, authId)
if (authId === undefined || authId === null) {
return (
<Grid item md={6}>
<Typography variant='display1' gutterBottom>
Teams
</Typography>
<Typography variant='caption'>Loading...</Typography>
</Grid>
)
}
return (
<Grid item md={6} style={{ position: 'relative' }}>
<Typography variant='display1' gutterBottom>
Teams <Link to='/manage-teams' className={classes.dashLink}>Manage Teams</Link>
</Typography>
<Query query={GET_TEAMS_BY_OWNER} variables={{ authId }}>
{({ loading, error, data }) => {
console.log('GET_TEAMS_BY_OWNER: ', loading, error, data)
let errors
let queryLoader
if (error) {
errors = error.graphQLErrors.map(({ message }, i) => {
return (
<Typography key={i} variant='caption'>
{message}
</Typography>
)
})
}
if (!loading && !error) {
if (data.teamsByOwner.length > 0) {
return (
<Grid container spacing={24}>
{!loading &&
data.teamsByOwner.map(team => (
<DashTeamItem key={team.id} team={team} />
))}
{queryLoader}
{errors}
{loader}
</Grid>
)
} else {
return (
<Grid container spacing={40}>
<Grid item xs={10}>
<MessageCard message='No teams yet...'>
<CreateTeamDialog authId={authId} />
</MessageCard>
</Grid>
</Grid>
)
}
} else {
return (
<Grid container spacing={24}>
<Typography variant='subheading' gutterBottom>
Loading...
</Typography>
{queryLoader}
{errors}
</Grid>
)
}
}}
</Query>
</Grid>
)
}
DashTeam.propTypes = {
user: PropTypes.any,
classes: PropTypes.object.isRequired
}
const getUserOnMount = lifecycle({
componentDidMount () {
getItem('user').then(user => {
this.setState({ user })
}) // Set user to state
}
})
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
user: ({ user }) => () => ({ user })
})
const DashTeamAuthId = compose(
mapStateToProps,
getUserOnMount
)(DashTeam)
export default DashTeamAuthId
@@ -0,0 +1,70 @@
import React from 'react'
import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import Grid from '@material-ui/core/Grid'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardMedia from '@material-ui/core/CardMedia'
import CardActions from '@material-ui/core/CardActions'
import Button from '@material-ui/core/Button'
const styles = theme => ({
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 160,
height: 160,
justifyContent: 'flex-start'
},
buttonRight: {
justifyContent: 'flex-end'
}
})
export const DashTeamItem = ({ classes, team }) => (
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography variant='headline'>{team.name}</Typography>
<Typography variant='subheading' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='subheading' paragraph>
Members:6
</Typography>
</CardContent>
<CardActions>
<Button size='small' color='primary' className={classes.buttonRight}>
Details
</Button>
</CardActions>
</div>
</Card>
</Grid>
)
DashTeamItem.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired
}
export default withStyles(styles)(DashTeamItem)
@@ -13,9 +13,11 @@ import SentimentSatisfiedAltIcon from '@material-ui/icons/SentimentSatisfiedAlt'
import AndroidIcon from '@material-ui/icons/Android'
const DashboardLink = props => <Link to='/dashboard' {...props} />
const TeamsLink = props => <Link to='/manage-team' {...props} />
const MembersLink = props => <Link to='/members' {...props} />
const TeamsLink = props => <Link to='/manage-teams' {...props} />
const MembersLink = props => <Link to='/team-members' {...props} />
const SettingsLink = props => <Link to='/settings' {...props} />
const DocumentationLink = props => <a href='https://advancedalgos.net/documentation-quick-start.shtml' target='_blank' {...props} />
const SupportLink = props => <a href='https://t.me/advancedalgoscommunity' target='_blank' {...props} />
export const MainDrawerItems = (
<div>
@@ -35,7 +37,7 @@ export const MainDrawerItems = (
<ListItemIcon>
<SupervisedUserCircleIcon />
</ListItemIcon>
<ListItemText primary='Members' />
<ListItemText primary='Team Members' />
</ListItem>
<ListItem button component={TeamsLink}>
<ListItemIcon>
@@ -54,13 +56,13 @@ export const MainDrawerItems = (
export const SecondaryDrawerItems = (
<div>
<ListItem button>
<ListItem button component={DocumentationLink}>
<ListItemIcon>
<LibraryBooksIcon />
</ListItemIcon>
<ListItemText primary='Documentation' />
</ListItem>
<ListItem button>
<ListItem button component={SupportLink}>
<ListItemIcon>
<SentimentSatisfiedAltIcon />
</ListItemIcon>
@@ -0,0 +1,143 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import { Mutation } from 'react-apollo'
import Button from '@material-ui/core/Button'
import DeleteIcon from '@material-ui/icons/Delete'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle'
import Typography from '@material-ui/core/Typography'
import DELETE_TEAM from '../../../graphql/teams/DeleteTeamMutation'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import { checkGraphQLError } from '../../../utils/graphql-errors'
const styles = theme => ({
dialogContainer: {
display: 'block',
margin: '3em',
minWidth: 400
},
buttonRight: {
justifyContent: 'flex-end'
}
})
export class ManageTeamDelete extends Component {
constructor (props) {
super(props)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.state = {
open: false,
motto: '',
description: ''
}
}
handleClickOpen () {
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false })
}
handleChange (e) {
this.setState({ name: e.target.value })
}
render () {
console.log(this.props, this.props.slug)
const { classes, authId } = this.props
return (
<Mutation
mutation={DELETE_TEAM}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER,
variables: { authId }
}
]}
update={(cache, { data: { deleteTeam } }) => {
const data = cache.readQuery({ query: GET_TEAMS_BY_OWNER })
console.log('Mutation cache update: ', deleteTeam, data)
data.getTeamsByOwner.push(deleteTeam)
cache.writeQuery({ query: GET_TEAMS_BY_OWNER, data })
}}
>
{(deleteTeam, { loading, error, data }) => {
let errors
let loader
if (loading) {
loader = <Typography variant='caption'>Submitting team...</Typography>
}
if (error) {
errors = error.graphQLErrors.map(({ message }, i) => {
const displayMessage = checkGraphQLError(message)
console.log('createTeam error:', displayMessage)
return (
<Typography key={i} variant='caption'>
{message}
</Typography>
)
})
}
return (
<div>
<Button size='small' color='primary' className={classes.buttonRight} onClick={this.handleClickOpen}>
<DeleteIcon /> Delete
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={classes.dialogContainer}>
<DialogTitle id='form-dialog-title'>Delete Team Team</DialogTitle>
<DialogContent>
<Typography variant='subheading' color='primary'>DANGER - Deleting your team cannot be undone</Typography>
<Typography variant='subheading'>Are you sure you want to delete this team?</Typography>
{loader}
{errors}
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button onClick={e => {
this.handleSubmit(e, deleteTeam, this.props.slug, authId)
}} color='primary'>
Delete Team
</Button>
</DialogActions>
</div>
</Dialog>
</div>
)
}}
</Mutation>
)
}
async handleSubmit (e, deleteTeam, slug, authId) {
e.preventDefault()
await deleteTeam({ variables: { slug, owner: authId } })
}
}
ManageTeamDelete.propTypes = {
classes: PropTypes.object.isRequired,
slug: PropTypes.string.isRequired,
authId: PropTypes.string.isRequired
}
export default withStyles(styles)(ManageTeamDelete)
@@ -0,0 +1,108 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { withStyles } from '@material-ui/core/styles'
import Button from '@material-ui/core/Button'
import DetailsIcon from '@material-ui/icons/Details'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import DialogTitle from '@material-ui/core/DialogTitle'
import Typography from '@material-ui/core/Typography'
const styles = theme => ({
dialogContainer: {
display: 'block',
margin: '3em',
minWidth: 400
},
buttonRight: {
justifyContent: 'flex-end'
}
})
export class ManageTeamDetails extends Component {
constructor (props) {
super(props)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.state = {
open: false
}
}
render () {
console.log(this.props.team)
const { name, members, profile, createdAt } = this.props.team
return (
<div>
<Button size='small' color='primary' className={this.props.classes.buttonRight} onClick={this.handleClickOpen}>
<DetailsIcon /> Details
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={this.props.classes.dialogContainer}>
<DialogTitle id='form-dialog-title'>{name} Team Details</DialogTitle>
<DialogContent>
<Typography
variant='display3'
align='center'
color='textPrimary'
gutterBottom
>
{name}
</Typography>
<Typography variant='subheading' color='textSecondary'>
{createdAt}
</Typography>
<Typography variant='subheading' color='textSecondary'>
Motto: {profile.motto}
</Typography>
<Typography variant='subheading' color='textSecondary'>
Description: {profile.description}
</Typography>
<Typography variant='subheading' paragraph gutterBottom>
Members: {members.length}
</Typography>
<Typography variant='subheading' color='primary'>
Team Admin:
{
members.map(member => {
if (member.role === 'OWNER' || member.role === 'ADMIN') {
return member.member.alias
}
})
}
</Typography>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Close
</Button>
</DialogActions>
</div>
</Dialog>
</div>
)
}
handleClickOpen () {
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false })
}
}
ManageTeamDetails.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.any
}
export default withStyles(styles)(ManageTeamDetails)
@@ -0,0 +1,185 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Mutation } from 'react-apollo'
import { withStyles } from '@material-ui/core/styles'
import Button from '@material-ui/core/Button'
import EditIcon from '@material-ui/icons/Edit'
import TextField from '@material-ui/core/TextField'
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 Typography from '@material-ui/core/Typography'
import { getItem } from '../../../utils/local-storage'
import UPDATE_TEAM_PROFILE from '../../../graphql/teams/UpdateTeamProfileMutation'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import { checkGraphQLError } from '../../../utils/graphql-errors'
const styles = theme => ({
dialogContainer: {
display: 'block',
margin: '3em',
minWidth: 400
},
buttonRight: {
justifyContent: 'flex-end'
}
})
export class ManageTeamEdit extends Component {
constructor (props) {
super(props)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
console.log('ManageTeamEdit', props.team)
const motto = props.team.profile.motto || ''
const description = props.team.profile.description || ''
this.state = {
open: false,
motto: motto,
description: description
}
}
render () {
console.log(this.props, this.props.slug)
const { classes, team, authId } = this.props
return (
<Mutation
mutation={UPDATE_TEAM_PROFILE}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER,
variables: { authId }
}
]}
>
{(updateTeamProfile, { loading, error, data }) => {
let errors
let loader
if (loading) {
loader = <Typography variant='caption'>Submitting team...</Typography>
}
if (error) {
errors = error.graphQLErrors.map(({ message }, i) => {
const displayMessage = checkGraphQLError(message)
console.log('updateTeamProfile error:', displayMessage)
return (
<Typography key={i} variant='caption'>
{message}
</Typography>
)
})
}
return (
<div>
<Button size='small' color='primary' className={classes.buttonRight} onClick={this.handleClickOpen}>
<EditIcon /> Edit
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={classes.dialogContainer}>
<DialogTitle id='form-dialog-title'>Edit Team Details</DialogTitle>
<DialogContent>
<TextField
autoFocus
margin='dense'
id='name'
label='Team Name'
type='text'
fullWidth
disabled
value={team.name}
/>
<TextField
autoFocus
margin='dense'
id='motto'
label='Team Motto'
type='text'
fullWidth
value={this.state.motto}
onChange={this.handleChange}
/>
<DialogContentText>Team description:</DialogContentText>
<TextField
margin='dense'
id='description'
label='Team Description'
type='text'
rows={4}
multiline
fullWidth
value={this.state.description}
onChange={this.handleChange}
/>
{loader}
{errors}
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button onClick={e => {
this.handleSubmit(e, updateTeamProfile, this.props.slug)
}} color='primary'>
Update Team
</Button>
</DialogActions>
</div>
</Dialog>
</div>
)
}}
</Mutation>
)
}
handleClickOpen () {
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false })
}
handleChange (e) {
switch (e.target.id) {
case 'motto':
this.setState({ motto: e.target.value })
break
case 'description':
this.setState({ description: e.target.value })
break
default:
}
}
async handleSubmit (e, updateTeamProfile, slug) {
e.preventDefault()
const currentUser = await getItem('user')
let authId = JSON.parse(currentUser)
authId = authId.authId
await updateTeamProfile({ variables: { slug, owner: authId, description: this.state.description, motto: this.state.motto } })
}
}
ManageTeamEdit.propTypes = {
classes: PropTypes.object.isRequired,
slug: PropTypes.string.isRequired,
authId: PropTypes.string,
team: PropTypes.object
}
export default withStyles(styles)(ManageTeamEdit)
@@ -0,0 +1,48 @@
import React from 'react'
import PropTypes from 'prop-types'
import Grid from '@material-ui/core/Grid'
import Card from '@material-ui/core/Card'
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 ManageTeamDetails from './ManageTeamDetails'
import ManageTeamDelete from './ManageTeamDelete'
import ManageTeamEdit from './ManageTeamEdit'
export const ManageTeamsItem = ({ classes, team, authId }) => (
<Grid item xs={12}>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant='headline' component='h2'>
{team.name}
</Typography>
<Typography variant='caption' color='textSecondary'>
{team.createdAt} | Members: {team.members.length}
</Typography>
</CardContent>
<CardActions>
<ManageTeamDetails team={team} />
<ManageTeamEdit slug={team.slug} authId={authId} team={team} />
<ManageTeamDelete slug={team.slug} authId={authId} />
</CardActions>
</div>
</Card>
</Grid>
)
ManageTeamsItem.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired,
authId: PropTypes.string.isRequired
}
export default ManageTeamsItem
@@ -0,0 +1,151 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Query } from 'react-apollo'
import { withStateHandlers, lifecycle, compose } from 'recompose'
import Grid from '@material-ui/core/Grid'
import { withStyles } from '@material-ui/core/styles'
import { isDefined, isString } from '../../../utils/js-helpers'
import { getItem } from '../../../utils/local-storage'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import ManageTeamsItem from './ManageTeamsItem'
import CreateTeamDialog from './CreateTeamDialog'
import { MessageCard } from '../../common/'
const styles = theme => ({
heroContent: {
maxWidth: 600,
margin: '0 auto',
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
},
cardGrid: {
padding: `${theme.spacing.unit * 8}px 0`
},
buttonRight: {
justifyContent: 'flex-end'
},
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 100,
height: 100,
maxWidth: 100,
justifyContent: 'flex-start'
}
})
export const ManageTeamsList = ({ classes, user = null }) => {
let owner
let authId = null
console.log('ManageTeamsList: ', user)
if (user !== null && isString(user)) {
owner = JSON.parse(user)
if (isDefined(owner.authId)) authId = owner.authId
}
console.log('ManageTeamsList 2: ', owner, authId)
if (authId === undefined || authId === null) {
return (
<Grid container spacing={40}>
<Grid>
<MessageCard message='Loading...' />
</Grid>
</Grid>
)
} else {
return (
<Query query={GET_TEAMS_BY_OWNER} fetchPolicy='network-only' variables={{ authId }}>
{({ loading, error, data }) => {
console.log('GET_TEAMS_BY_OWNER: ', loading, error, data)
let errors = null
if (error) {
errors = error.graphQLErrors.map(({ message }, i) => {
return (
<MessageCard message={message} />
)
})
}
if (!loading && !error) {
if (data.teamsByOwner.length > 0) {
return (
<React.Fragment>
<Grid container spacing={40}>
{!loading &&
data.teamsByOwner.map(team => (
<ManageTeamsItem
key={team.id}
team={team}
classes={classes}
authId={authId}
/>
))}
<MessageCard message='Create another team'>
<CreateTeamDialog authId={authId} />
</MessageCard>
{errors}
</Grid>
</React.Fragment>
)
} else {
return (
<Grid container spacing={40}>
<Grid item xs={10}>
<MessageCard message='You don&rsquo;t have any teams. Create one!'>
<CreateTeamDialog authId={authId} />
</MessageCard>
</Grid>
</Grid>
)
}
} else {
return (
<Grid container spacing={40}>
<Grid item xs={12}>
<MessageCard message='Loading...' />
if (errors !== null) (<MessageCard message={errors} />)
</Grid>
</Grid>
)
}
}}
</Query>
)
}
}
ManageTeamsList.propTypes = {
user: PropTypes.any,
classes: PropTypes.object.isRequired
}
const getUserOnMount = lifecycle({
componentDidMount () {
getItem('user').then(user => {
this.setState({ user })
}) // Set user to state
}
})
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
user: ({ user }) => () => ({ user })
})
const ManageTeamsListAuthId = compose(
mapStateToProps,
getUserOnMount
)(ManageTeamsList)
export default withStyles(styles)(ManageTeamsListAuthId)
+25 -152
View File
@@ -1,25 +1,19 @@
import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'react-apollo'
import { Route, Switch } from 'react-router-dom'
import { lifecycle } from 'recompose'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import Drawer from '@material-ui/core/Drawer'
import List from '@material-ui/core/List'
import Divider from '@material-ui/core/Divider'
import Grid from '@material-ui/core/Grid'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardMedia from '@material-ui/core/CardMedia'
import CardActions from '@material-ui/core/CardActions'
import Button from '@material-ui/core/Button'
import { MainDrawerItems, SecondaryDrawerItems } from './components/DrawerLinks'
import { CreateTeamDialog } from './components/CreateTeam'
import { CreateTeamMutation } from '../../graphql/teams/CreateTeamMutation'
import aaweb from '../../assets/AlgonetWebPlatform.jpg'
import Overview from './Overview'
import ManageTeams from './ManageTeams'
import TeamMembers from './TeamMembers'
import Settings from './Settings'
const drawerWidth = 240
@@ -49,40 +43,10 @@ const styles = theme => ({
overflow: 'auto',
width: `calc(90% - ${drawerWidth}px)`
},
appBarSpacer: theme.mixins.toolbar,
tableContainer: {
height: 320
},
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 160,
height: 160,
justifyContent: 'flex-start'
},
aawebMedia: {
width: '100%',
height: 320
},
buttonRight: {
justifyContent: 'flex-end'
},
extendedIcon: {
marginRight: theme.spacing.unit
}
appBarSpacer: theme.mixins.toolbar
})
const Dashboard = ({ classes, createTeamMutation, ...props }) => (
const Dashboard = ({ classes }) => (
<div className={classes.root}>
<Drawer
variant='permanent'
@@ -97,120 +61,29 @@ const Dashboard = ({ classes, createTeamMutation, ...props }) => (
</Drawer>
<main className={classes.content}>
<div className={classes.appBarSpacer} />
<Grid container spacing={24} className={classes.cardGrid}>
<Grid item md={6}>
<Typography variant='display1' gutterBottom>
Teams
</Typography>
<Grid container spacing={24} className={classes.cardGrid}>
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardContent className={classes.createCardContent}>
<CreateTeamDialog classes={classes} createTeamMutation={createTeamMutation} />
</CardContent>
</div>
</Card>
</Grid>
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography variant='headline'>Goodester</Typography>
<Typography variant='subheading' color='textSecondary'>
09.15.2018
</Typography>
<Typography variant='subheading' paragraph>
Members:6
</Typography>
<Typography variant='subheading' color='primary'>
Now recruiting!
</Typography>
</CardContent>
<CardActions>
<Button
size='small'
color='primary'
className={classes.buttonRight}
>
Details
</Button>
</CardActions>
</div>
</Card>
</Grid>
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography variant='headline'>Goodester</Typography>
<Typography variant='subheading' color='textSecondary'>
09.15.2018
</Typography>
<Typography variant='subheading' paragraph>
Members:6
</Typography>
<Typography variant='subheading' color='primary'>
Now recruiting!
</Typography>
</CardContent>
<CardActions>
<Button
size='small'
color='primary'
className={classes.buttonRight}
>
Details
</Button>
</CardActions>
</div>
</Card>
</Grid>
</Grid>
</Grid>
<Grid item md={6}>
<Typography variant='display1' gutterBottom>
Develop
</Typography>
<Grid container spacing={24} className={classes.cardGrid}>
<Grid item md={12}>
<Card className={classes.mainFeaturedPost}>
<CardMedia
className={classes.aawebMedia}
image={aaweb}
title='Image title'
/>
<CardContent>
<Typography variant='title' color='inherit' aligh='right'>
<Button>Start Developing ></Button>
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</Grid>
</Grid>
<Switch>
<Route exact path='/dashboard' component={Overview} />
<Route exact path='/manage-teams' component={ManageTeams} />
<Route exact path='/team-members' component={TeamMembers} />
<Route exact path='/settings' component={Settings} />
</Switch>
</main>
</div>
)
Dashboard.propTypes = {
classes: PropTypes.object.isRequired,
createTeamMutation: PropTypes.function
classes: PropTypes.object.isRequired
}
const CreateTeamDialogWithMutation = graphql(CreateTeamMutation, {
name: 'createTeamMutation' // name of the injected prop: this.props.createTeamMutation...
const DashboardCheckSession = lifecycle({
async componentDidMount () {
try {
const authenticated = await this.props.auth.isAuthenticated()
console.log('DashboardCheckSession res: ', authenticated)
} catch (err) {
console.log('DashboardCheckSession err: ', err)
}
}
})(Dashboard)
export default withStyles(styles)(CreateTeamDialogWithMutation)
export default withStyles(styles)(DashboardCheckSession)
-111
View File
@@ -1,111 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'react-apollo'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import Drawer from '@material-ui/core/Drawer'
import List from '@material-ui/core/List'
import Divider from '@material-ui/core/Divider'
import { MainDrawerItems, SecondaryDrawerItems } from './components/DrawerLinks'
import { TeamTable } from './components/TeamTable'
import { CreateTeamMutation } from '../../graphql/teams/CreateTeamMutation'
const drawerWidth = 240
const styles = theme => ({
root: {
display: 'flex'
},
'@global': {
body: {
backgroundColor: theme.palette.common.white
}
},
appBar: {
zIndex: theme.zIndex.drawer + 1
},
drawerPaper: {
position: 'relative',
width: drawerWidth,
whiteSpace: 'nowrap',
zIndex: 1000
},
content: {
flexGrow: 1,
backgroundColor: theme.palette.background.default,
padding: theme.spacing.unit * 3,
height: '100vh',
overflow: 'auto',
width: `calc(90% - ${drawerWidth}px)`
},
appBarSpacer: theme.mixins.toolbar,
tableContainer: {
height: 320
},
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 160,
height: 160,
justifyContent: 'flex-start'
},
aawebMedia: {
width: '100%',
height: 320
},
buttonRight: {
justifyContent: 'flex-end'
},
extendedIcon: {
marginRight: theme.spacing.unit
}
})
const Dashboard = ({ classes, createTeamMutation, ...props }) => (
<div className={classes.root}>
<Drawer
variant='permanent'
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.appBarSpacer} />
<List>{MainDrawerItems}</List>
<Divider />
<List>{SecondaryDrawerItems}</List>
</Drawer>
<main className={classes.content}>
<div className={classes.appBarSpacer} />
<Typography variant='display1' gutterBottom>
Members
</Typography>
<div className={classes.tableContainer}>
<TeamTable />
</div>
</main>
</div>
)
Dashboard.propTypes = {
classes: PropTypes.object.isRequired,
createTeamMutation: PropTypes.function
}
const CreateTeamDialogWithMutation = graphql(CreateTeamMutation, {
name: 'createTeamMutation' // name of the injected prop: this.props.createTeamMutation...
})(Dashboard)
export default withStyles(styles)(CreateTeamDialogWithMutation)
+12 -192
View File
@@ -1,210 +1,30 @@
import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'react-apollo'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import Drawer from '@material-ui/core/Drawer'
import List from '@material-ui/core/List'
import Divider from '@material-ui/core/Divider'
import Grid from '@material-ui/core/Grid'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardMedia from '@material-ui/core/CardMedia'
import CardActions from '@material-ui/core/CardActions'
import Button from '@material-ui/core/Button'
import { MainDrawerItems, SecondaryDrawerItems } from './components/DrawerLinks'
import { TeamTable } from './components/TeamTable'
import { CreateTeamDialog } from './components/CreateTeam'
import { CreateTeamMutation } from '../../graphql/teams/CreateTeamMutation'
const drawerWidth = 240
import { MessageCard } from '../common/'
const styles = theme => ({
root: {
display: 'flex'
},
'@global': {
body: {
backgroundColor: theme.palette.common.white
}
},
appBar: {
zIndex: theme.zIndex.drawer + 1
},
drawerPaper: {
position: 'relative',
width: drawerWidth,
whiteSpace: 'nowrap',
zIndex: 1000
},
content: {
flexGrow: 1,
backgroundColor: theme.palette.background.default,
padding: theme.spacing.unit * 3,
height: '100vh',
overflow: 'auto',
width: `calc(90% - ${drawerWidth}px)`
},
appBarSpacer: theme.mixins.toolbar,
tableContainer: {
height: 320
},
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 160,
height: 160,
justifyContent: 'flex-start'
},
aawebMedia: {
width: '100%',
height: 320
},
buttonRight: {
justifyContent: 'flex-end'
},
extendedIcon: {
marginRight: theme.spacing.unit
}
})
const Dashboard = ({ classes, createTeamMutation, ...props }) => (
<div className={classes.root}>
<Drawer
variant='permanent'
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.appBarSpacer} />
<List>{MainDrawerItems}</List>
<Divider />
<List>{SecondaryDrawerItems}</List>
</Drawer>
<main className={classes.content}>
<div className={classes.appBarSpacer} />
<Grid container spacing={24} className={classes.cardGrid}>
<Grid item md={6}>
<Typography variant='display1' gutterBottom>
Settings
</Typography>
<Grid container spacing={24} className={classes.cardGrid}>
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardContent className={classes.createCardContent}>
<CreateTeamDialog classes={classes} createTeamMutation={createTeamMutation} />
</CardContent>
</div>
</Card>
</Grid>
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography variant='headline'>Goodester</Typography>
<Typography variant='subheading' color='textSecondary'>
09.15.2018
</Typography>
<Typography variant='subheading' paragraph>
Members:6
</Typography>
<Typography variant='subheading' color='primary'>
Now recruiting!
</Typography>
</CardContent>
<CardActions>
<Button
size='small'
color='primary'
className={classes.buttonRight}
>
Details
</Button>
</CardActions>
</div>
</Card>
</Grid>
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography variant='headline'>Goodester</Typography>
<Typography variant='subheading' color='textSecondary'>
09.15.2018
</Typography>
<Typography variant='subheading' paragraph>
Members:6
</Typography>
<Typography variant='subheading' color='primary'>
Now recruiting!
</Typography>
</CardContent>
<CardActions>
<Button
size='small'
color='primary'
className={classes.buttonRight}
>
Details
</Button>
</CardActions>
</div>
</Card>
</Grid>
</Grid>
</Grid>
<Grid item md={6}>
<Typography variant='display1' gutterBottom>
Notifications
</Typography>
<Grid container spacing={24} className={classes.cardGrid}>
<Grid item md={12}>
<Card className={classes.mainFeaturedPost}>
<CardContent>
<Typography variant='title' color='inherit' aligh='right'>
<Button>Start Developing ></Button>
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</Grid>
</Grid>
</main>
export const Settings = ({ classes }) => (
<div>
<Typography variant='display1' gutterBottom>
Settings
</Typography>
<div className={classes.tableContainer}>
<MessageCard message='Coming soon. Management settings.' />
</div>
</div>
)
Dashboard.propTypes = {
classes: PropTypes.object.isRequired,
createTeamMutation: PropTypes.function
Settings.propTypes = {
classes: PropTypes.object.isRequired
}
const CreateTeamDialogWithMutation = graphql(CreateTeamMutation, {
name: 'createTeamMutation' // name of the injected prop: this.props.createTeamMutation...
})(Dashboard)
export default withStyles(styles)(CreateTeamDialogWithMutation)
export default withStyles(styles)(Settings)
-111
View File
@@ -1,111 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import { graphql } from 'react-apollo'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import Drawer from '@material-ui/core/Drawer'
import List from '@material-ui/core/List'
import Divider from '@material-ui/core/Divider'
import { MainDrawerItems, SecondaryDrawerItems } from './components/DrawerLinks'
import { TeamTable } from './components/TeamTable'
import { CreateTeamMutation } from '../../graphql/teams/CreateTeamMutation'
const drawerWidth = 240
const styles = theme => ({
root: {
display: 'flex'
},
'@global': {
body: {
backgroundColor: theme.palette.common.white
}
},
appBar: {
zIndex: theme.zIndex.drawer + 1
},
drawerPaper: {
position: 'relative',
width: drawerWidth,
whiteSpace: 'nowrap',
zIndex: 1000
},
content: {
flexGrow: 1,
backgroundColor: theme.palette.background.default,
padding: theme.spacing.unit * 3,
height: '100vh',
overflow: 'auto',
width: `calc(90% - ${drawerWidth}px)`
},
appBarSpacer: theme.mixins.toolbar,
tableContainer: {
height: 320
},
card: {
display: 'flex',
width: '100%'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
},
cardMedia: {
flex: 1,
width: 160,
height: 160,
justifyContent: 'flex-start'
},
aawebMedia: {
width: '100%',
height: 320
},
buttonRight: {
justifyContent: 'flex-end'
},
extendedIcon: {
marginRight: theme.spacing.unit
}
})
const Dashboard = ({ classes, createTeamMutation, ...props }) => (
<div className={classes.root}>
<Drawer
variant='permanent'
classes={{
paper: classes.drawerPaper
}}
>
<div className={classes.appBarSpacer} />
<List>{MainDrawerItems}</List>
<Divider />
<List>{SecondaryDrawerItems}</List>
</Drawer>
<main className={classes.content}>
<div className={classes.appBarSpacer} />
<Typography variant='display1' gutterBottom>
Teams
</Typography>
<div className={classes.tableContainer}>
<TeamTable />
</div>
</main>
</div>
)
Dashboard.propTypes = {
classes: PropTypes.object.isRequired,
createTeamMutation: PropTypes.function
}
const CreateTeamDialogWithMutation = graphql(CreateTeamMutation, {
name: 'createTeamMutation' // name of the injected prop: this.props.createTeamMutation...
})(Dashboard)
export default withStyles(styles)(CreateTeamDialogWithMutation)
+9 -7
View File
@@ -55,26 +55,28 @@ const Footer = ({ classes }) => {
Home
</Typography>
</NavLink>
<NavLink to='/' className={classNames(classes.footerLink)}>
<NavLink to='/teams' className={classNames(classes.footerLink)}>
<Typography variant='subheading' color='textSecondary'>
Teams
</Typography>
</NavLink>
<NavLink to='/' className={classNames(classes.footerLink)}>
<NavLink to='/about' className={classNames(classes.footerLink)}>
<Typography variant='subheading' color='textSecondary'>
About
</Typography>
</NavLink>
</Grid>
<Grid item xs>
<Typography variant='title' color='textPrimary' gutterBottom>
Support
</Typography>
<NavLink to='/' className={classNames(classes.footerLink)}>
<a href='https://t.me/advancedalgoscommunity' target='_blank>' className={classNames(classes.footerLink)}>
<Typography variant='title' color='textPrimary' gutterBottom>
Support
</Typography>
</a>
<a href='https://advancedalgos.net/documentation-quick-start.shtml' target='_blank>' className={classNames(classes.footerLink)}>
<Typography variant='subheading' color='textSecondary'>
Documentation
</Typography>
</NavLink>
</a>
<NavLink to='/' className={classNames(classes.footerLink)}>
<Typography variant='subheading' color='textSecondary'>
Telegram
+3 -1
View File
@@ -2,8 +2,10 @@ 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, Dashboard, theme, globalStyles }
export { NavBar, Footer, Callback, Home, About, Dashboard, Teams, theme, globalStyles }
+15 -3
View File
@@ -20,6 +20,8 @@ import { getItem } from '../../utils/local-storage'
import { LoggedIn } from './LoggedIn'
import { LoggedOut } from './LoggedOut'
import AALogo from '../../assets/logos/advanced-algos/aa-logo-dark.svg'
const AboutLink = props => <Link to='/about' {...props} />
const TeamsLink = props => <Link to='/teams' {...props} />
const DashboardLink = props => <Link to='/dashboard' {...props} />
@@ -34,6 +36,9 @@ const styles = theme => ({
root: {
flexGrow: 1
},
colorDefault: {
color: '#000'
},
flex: {
flexGrow: 1
},
@@ -46,6 +51,12 @@ const styles = theme => ({
},
appBar: {
position: 'relative'
},
img: {
margin: 20,
display: 'block',
maxWidth: 240,
maxHeight: 48
}
})
@@ -71,8 +82,9 @@ class NavBar extends Component {
return (
<div className={classes.root}>
<AppBar position='static' className={classes.appBar}>
<AppBar position='static' classes={{ root: classes.appBar, colorDefault: classes.colorDefault }}>
<Toolbar>
<img className={classes.img} src={AALogo} alt='Advanced Algos' />
<Typography
variant='title'
color='inherit'
@@ -89,8 +101,8 @@ class NavBar extends Component {
>
<HomeIcon />
</IconButton>
<Button component={TeamsLink}>Teams</Button>
<Button component={AboutLink}>About</Button>
<Button component={TeamsLink} color='inherit'>Teams</Button>
<Button component={AboutLink} color='inherit'>About</Button>
{this.state.user !== undefined && this.state.user !== null ? (
<React.Fragment>
@@ -0,0 +1,89 @@
import React from 'react'
import PropTypes from 'prop-types'
import CssBaseline from '@material-ui/core/CssBaseline'
import Typography from '@material-ui/core/Typography'
import Paper from '@material-ui/core/Paper'
import { withStyles } from '@material-ui/core/styles'
import { Link } from 'react-router-dom'
const styles = theme => ({
root: {
flexGrow: 1
},
'@global': {
body: {
backgroundColor: theme.palette.common.white
}
},
layout: {
width: 'auto',
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
marginBottom: theme.spacing.unit * 6,
[theme.breakpoints.up(900 + theme.spacing.unit * 3 * 2)]: {
width: 900,
marginLeft: 'auto',
marginRight: 'auto'
}
},
paper: {
marginTop: theme.spacing.unit * 3,
marginBottom: theme.spacing.unit * 3,
padding: theme.spacing.unit * 2,
[theme.breakpoints.up(600 + theme.spacing.unit * 3 * 2)]: {
marginTop: theme.spacing.unit * 6,
marginBottom: theme.spacing.unit * 6,
padding: theme.spacing.unit * 3
}
},
heroContent: {
maxWidth: 600,
margin: '0 auto',
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
}
})
const TeamsDetails = ({ classes, team }) => (
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<Paper className={classes.paper}>
<div className={classes.heroContent}>
<Link to='/teams'>&larr; Back to all teams</Link>
<Typography
variant='display3'
align='center'
color='textPrimary'
gutterBottom
>
{team.name}
</Typography>
<Typography variant='subheading' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='subheading' paragraph gutterBottom>
Members: {team.members.length}
</Typography>
<Typography variant='subheading' color='primary'>
Team Admin:
{
team.members.map(member => {
if (member.role === 'OWNER' || member.role === 'ADMIN') {
return member.member.alias
}
})
}
</Typography>
</div>
</Paper>
</main>
</React.Fragment>
)
TeamsDetails.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired
}
export default withStyles(styles)(TeamsDetails)
@@ -0,0 +1,55 @@
import React from 'react'
import PropTypes from 'prop-types'
import Grid from '@material-ui/core/Grid'
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 { Link } from 'react-router-dom'
export const TeamsItem = ({ classes, team }) => (
<Grid item sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant='headline' component='h2'>
{team.name}
</Typography>
<Typography variant='caption' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='caption' paragraph gutterBottom>
Members: 1
</Typography>
<Typography variant='subheading' color='primary'>
Now recruiting!
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link to={`/teams/${team.slug}`} >
<Button size='small' color='primary' className={classes.buttonRight}>
Details
</Button>
</Link>
</CardActions>
</Card>
</Grid>
)
TeamsItem.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired
}
export default TeamsItem
@@ -0,0 +1,128 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Query } from 'react-apollo'
import Typography from '@material-ui/core/Typography'
import Grid from '@material-ui/core/Grid'
import { withStyles } from '@material-ui/core/styles'
import GET_ALL_TEAMS_QUERY from '../../../graphql/teams/GetAllTeamsQuery'
import { isEmpty } from '../../../utils/js-helpers'
import TeamsItem from './TeamsItem'
import TeamsDetails from './TeamsDetails'
const styles = theme => ({
heroContent: {
maxWidth: 600,
margin: '0 auto',
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
},
cardGrid: {
padding: `${theme.spacing.unit * 8}px 0`
},
card: {
height: '100%',
display: 'flex',
flexDirection: 'column'
},
cardMedia: {
paddingTop: '56.25%' // 16:9
},
cardContent: {
flexGrow: 1
},
buttonRight: {
justifyContent: 'flex-end'
}
})
export const TeamsList = ({ classes, match }) => (
<Query query={GET_ALL_TEAMS_QUERY} fetchPolicy='cache-and-network' >
{({ loading, error, data }) => {
console.log('GET_ALL_TEAMS: ', loading, error, data, match)
let slug = null
if (!isEmpty(match.params) && match.params.slug) slug = match.params.slug
let errors
let queryLoader
if (error) {
errors = error.graphQLErrors.map(({ message }, i) => {
return (
<Typography key={i} variant='caption'>
{message}
</Typography>
)
})
}
if (!loading && !error) {
if (data.teams.edges.length > 0) {
if (slug !== null) {
return (
<Grid container spacing={40}>
{!loading &&
data.teams.edges.map(team => {
if (team.node.slug === slug) {
return (
<TeamsDetails
key={team.node.id}
team={team.node}
/>
)
}
})}
{queryLoader}
{errors}
</Grid>
)
} else {
return (
<React.Fragment>
<div className={classes.heroContent}>
<Typography
variant='display3'
align='center'
color='textPrimary'
gutterBottom
>
Teams
</Typography>
</div>
<Grid container spacing={40}>
{!loading &&
data.teams.edges.map(team => (
<TeamsItem
key={team.node.id}
team={team.node}
classes={classes}
/>
))}
{queryLoader}
{errors}
</Grid>
</React.Fragment>
)
}
}
} else {
return (
<Grid container spacing={40}>
<Typography variant='subheading' gutterBottom>
Loading...
</Typography>
{queryLoader}
{errors}
</Grid>
)
}
}}
</Query>
)
TeamsList.propTypes = {
classes: PropTypes.object.isRequired,
match: PropTypes.object
}
export default withStyles(styles)(TeamsList)
+45
View File
@@ -0,0 +1,45 @@
import React from 'react'
import PropTypes from 'prop-types'
import CssBaseline from '@material-ui/core/CssBaseline'
import { withStyles } from '@material-ui/core/styles'
import TeamsList from './components/TeamsList'
const styles = theme => ({
root: {
flexGrow: 1
},
'@global': {
body: {
backgroundColor: theme.palette.common.white
}
},
layout: {
width: 'auto',
marginLeft: theme.spacing.unit * 3,
marginRight: theme.spacing.unit * 3,
marginBottom: theme.spacing.unit * 6,
[theme.breakpoints.up(900 + theme.spacing.unit * 3 * 2)]: {
width: 900,
marginLeft: 'auto',
marginRight: 'auto'
}
}
})
const Teams = ({ classes, match }) => (
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<TeamsList match={match} />
</main>
</React.Fragment>
)
Teams.propTypes = {
classes: PropTypes.object.isRequired,
match: PropTypes.object
}
export default withStyles(styles)(Teams)
+2 -2
View File
@@ -1,10 +1,10 @@
import { createMuiTheme } from '@material-ui/core/styles'
import indigo from '@material-ui/core/colors/indigo'
import red from '@material-ui/core/colors/red'
// import red from '@material-ui/core/colors/red'
export const theme = createMuiTheme({
palette: {
primary: red,
primary: { main: '#303036' },
secondary: indigo
},
typography: {
+5
View File
@@ -0,0 +1,5 @@
# Code Styling and Formatting
```
npm install --save-dev eslint-config-standard eslint-config-standard-react eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node eslint-plugin-react
```