Merge pull request #3 from AdvancedAlgos/feature/member-mangement

Feature/member mangement
This commit is contained in:
Barry Low authored and GitHub committed 2018-10-06 03:02:04 -07:00
commit c98455d5b8
51 files changed
+4096 -282

No files matched your search

+1
View File
@@ -1,4 +1,5 @@
/.env
/.env.prod
/database/.env
/database/docker-compose.prisma.prod.yml
/docker-compose.prisma.prod.yml
+20 -5
View File
@@ -7,8 +7,9 @@ enum Role {
enum Status {
ACTIVE
INACTIVE
APPLY
INVITE
APPLIED
INVITED
REMOVED
}
enum FBType {
@@ -53,24 +54,35 @@ type TeamStatus {
}
type TeamMembers {
id: ID! @unique
member: Member @relation (name: "MemberOnTeam", onDelete: SET_NULL)
team: Team @relation (name: "TeamMembers", onDelete: SET_NULL)
role: Role @default(value: MEMBER)
status: [MemberStatus!]!
status: [TeamMemberStatus!]!
email: String @unique
createdAt: DateTime!
updatedAt: DateTime!
}
type TeamMemberStatus {
id: ID! @unique
status: Status @Default(value: INACTIVE)
reason: String
createdAt: DateTime!
}
type Member {
id: ID! @unique
alias: String
team: TeamMembers @relation (name: "MemberOnTeam", onDelete: SET_NULL)
teams: [TeamMembers!]! @relation (name: "MemberOnTeam", onDelete: SET_NULL)
authId: String! @unique
visible: Boolean! @default(value: true)
status: [MemberStatus!]!
createdAt: DateTime!
}
type MemberStatus {
status: Status @Default(value: inactive)
status: Status @Default(value: INACTIVE)
reason: String
createdAt: DateTime!
}
@@ -84,6 +96,9 @@ type FinancialBeings {
id: ID! @unique
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
team: Team @relation (name: "TeamFinancialBeing", onDelete: SET_NULL)
status: [FBStatus!]!
updatedAt: DateTime!
+2 -2
View File
@@ -1,8 +1,8 @@
version: '3'
services:
api:
image: advancedalgosteamsmodule/teammodule-api:v1
env_file: ./.env
image: advancedalgosteamsmodule/teammodule-api:v2
env_file: ./.env.prod
container_name: aateamapi
tty: true
stdin_open: true
+1358 -71
View File
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -8,6 +8,7 @@
},
"dependencies": {
"@azure/storage-blob": "^10.0.0-preview",
"axios": "^0.18.0",
"azure-storage": "^2.10.1",
"cors": "^2.8.4",
"debug": "^4.0.1",
@@ -27,6 +28,6 @@
"devDependencies": {
"graphql-cli": "^2.16.5",
"npm-run-all": "4.1.3",
"prisma": "^1.16.4"
"prisma": "^1.17.1"
}
}
+8 -8
View File
@@ -29,7 +29,7 @@ const isLoggedIn = async ctx => {
let member = ctxMember(ctx, memberLocationOnContext)
let token = ctxToken(ctx, bearerAccessToken)
let memberToken
console.log('isLoggedIn: ', ctx, member, token)
// console.log('isLoggedIn: ', ctx, member, token)
if (!member && token) {
let scheme, credentials
const tokenParts = token.split(' ')
@@ -41,16 +41,16 @@ const isLoggedIn = async ctx => {
if (/^Bearer$/i.test(scheme)) {
token = credentials
//verify token
console.log('isLoggedIn2 : ', token)
// console.log('isLoggedIn2 : ', token)
try {
memberToken = await validateIdToken(token)
const authId = memberToken.sub
console.log('isLoggedIn3 : ', await authId, ctx.db.query.member)
// console.log('isLoggedIn3 : ', await authId, ctx.db.query.member)
exists = await ctx.db.query.member({ where: { authId: authId } })
console.log('isLoggedIn4 : ', await exists, createMember )
// console.log('isLoggedIn4 : ', await exists, createMember )
if (!member && exists === null) {
member = await createMember(ctx, memberToken).then(res => {
console.log('isLoggedIn5 : ', res)
// console.log('isLoggedIn5 : ', res)
return res
})
@@ -62,7 +62,7 @@ const isLoggedIn = async ctx => {
}
}
}
console.log('isLoggedIn5 : ', await member)
// console.log('isLoggedIn5 : ', await member)
if (!member) {
throw new Error(`Not logged in`)
}
@@ -95,13 +95,13 @@ const directiveResolvers = {
? source
: ctx.request.body.variables ? ctx.request.body.variables : { id: null }
const user = await isLoggedIn(ctx)
console.log('directive isOwner 0: ', typeId, user.sub)
// console.log('directive isOwner 0: ', typeId, user.sub)
const memberId = user.sub
const isOwner =
type === `Member`
? memberId === typeId
: await isRequestingMemberAlsoOwner({ ctx, memberId, type, typeId })
console.log('directive isOwner 1: ', source, isOwner, type, typeId, memberId)
// console.log('directive isOwner 1: ', source, isOwner, type, typeId, memberId)
if (isOwner) {
return next()
}
+152
View File
@@ -0,0 +1,152 @@
var jwt = require('jsonwebtoken')
var axios = require('axios')
const sendTeamMemberInvite = function(email, team) {
const dev = process.env.NODE_ENV === 'development' ? true : false
console.log('sendTeamMemberInvite', email, team)
const API_KEY = process.env.SG_APIKEY
const token = jwt.sign({ email: email, team: team.slug }, API_KEY, { expiresIn: '7d' })
let origin = 'https://teams.advancedalgos.net'
const params = '/activate-team-membership/'
if (dev){
origin = 'http://localhost:3001'
}
const headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin' : origin,
'Access-Control-Allow-Credentials': 'true'
};
const data = JSON.stringify({
"personalizations": [
{
"to": [
{
"email": email
}
],
dynamic_template_data: {
"aateaminvitelink": origin + params + token,
"aateamname": team.name,
"subject": "INVITATION: Join team " + team.name
},
"subject": "INVITATION: Join team " + team.name
}
],
"from": {
"email": "noreply@advancedalgos.net",
"name": "Advanced Algos Teams"
},
"reply_to": {
"email": "feedback@advancedalgos.net",
"name": "Advanced Algos Teams"
},
"template_id": process.env.SG_INVITE_EMAILID
})
let sendVerify = axios({
method: 'post',
url: 'https://api.sendgrid.com/v3/mail/send',
data: data,
headers:{
'content-type': 'application/json',
'authorization': 'Bearer ' + API_KEY
}
})
.then(function (response) {
if (response.status >= 200 && response.status < 300) {
return 'Success'
} else {
throw response.data.errors[0].message
}
})
.catch(function (error) {
return `${error.status}-${error.response.data.errors[0].message}`
})
return sendVerify
}
const sendTeamCreateConfirmation = function(email, teamName, botName) {
const dev = process.env.NODE_ENV === 'development' ? true : false
console.log('sendTeamMemberInvite', email, teamName, botName)
const API_KEY = process.env.SG_APIKEY
let origin = 'https://teams.advancedalgos.net'
if (dev){
origin = 'http://localhost:3001'
}
const headers = {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin' : origin,
'Access-Control-Allow-Credentials': 'true'
};
const data = JSON.stringify({
"personalizations": [
{
"to": [
{
"email": email
}
],
dynamic_template_data: {
"aadeveloplink": "https://develop.advancedalgos.net/index.html",
"aateamname": teamName,
"aabotname": botName,
"subject": "Team " + teamName + " has been created!"
},
"subject": "Team " + teamName + " has been created!"
}
],
"from": {
"email": "noreply@advancedalgos.net",
"name": "Advanced Algos Teams"
},
"reply_to": {
"email": "feedback@advancedalgos.net",
"name": "Advanced Algos Teams"
},
"template_id": process.env.SG_TEAMCREATE_EMAILID
})
let sendTeamCreation = axios({
method: 'post',
url: 'https://api.sendgrid.com/v3/mail/send',
data: data,
headers:{
'content-type': 'application/json',
'authorization': 'Bearer ' + API_KEY
}
})
.then(function (response) {
if (response.status >= 200 && response.status < 300) {
return 'Success'
} else {
throw response.data.errors[0].message
}
})
.catch(function (error) {
return `${error.status}-${error.response.data.errors[0].message}`
})
return sendTeamCreation
}
const verifyInviteToken = async function(token) {
const API_KEY = process.env.SG_APIKEY
let verifiedToken
try {
verifiedToken = await jwt.verify(token, API_KEY, {maxAge: '7d'})
} catch(err) {
if (err.name === "TokenExpiredError"){
throw new Error ("Error: Token Expired. Please request new invitation or apply to the team again")
} else {
throw new Error (`Error: Token error - ${err.message}`)
}
}
console.log('verifyInviteToken', verifiedToken)
return verifiedToken
}
module.exports = { sendTeamMemberInvite, sendTeamCreateConfirmation, verifyInviteToken }
+637 -60
View File
@@ -1,5 +1,5 @@
# source: http://localhost:4466
# timestamp: Thu Sep 20 2018 16:52:44 GMT-0700 (Pacific Daylight Time)
# timestamp: Fri Oct 05 2018 13:16:40 GMT-0700 (Pacific Daylight Time)
type AggregateFBStatus {
count: Int!
@@ -25,6 +25,10 @@ type AggregateTeamMembers {
count: Int!
}
type AggregateTeamMemberStatus {
count: Int!
}
type AggregateTeamProfile {
count: Int!
}
@@ -245,6 +249,9 @@ type FinancialBeings implements Node {
id: ID!
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
team(where: TeamWhereInput): Team
status(where: FBStatusWhereInput, orderBy: FBStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [FBStatus!]
updatedAt: DateTime!
@@ -264,6 +271,9 @@ type FinancialBeingsConnection {
input FinancialBeingsCreateInput {
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
team: TeamCreateOneWithoutFbInput
status: FBStatusCreateManyInput
}
@@ -276,6 +286,9 @@ input FinancialBeingsCreateManyWithoutTeamInput {
input FinancialBeingsCreateWithoutTeamInput {
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
status: FBStatusCreateManyInput
}
@@ -295,6 +308,12 @@ enum FinancialBeingsOrderByInput {
type_DESC
kind_ASC
kind_DESC
name_ASC
name_DESC
slug_ASC
slug_DESC
avatar_ASC
avatar_DESC
updatedAt_ASC
updatedAt_DESC
createdAt_ASC
@@ -305,6 +324,9 @@ type FinancialBeingsPreviousValues {
id: ID!
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
updatedAt: DateTime!
createdAt: DateTime!
}
@@ -351,6 +373,9 @@ input FinancialBeingsSubscriptionWhereInput {
input FinancialBeingsUpdateInput {
type: FBType
kind: FBKind
name: String
slug: String
avatar: String
team: TeamUpdateOneWithoutFbInput
status: FBStatusUpdateManyInput
}
@@ -367,6 +392,9 @@ input FinancialBeingsUpdateManyWithoutTeamInput {
input FinancialBeingsUpdateWithoutTeamDataInput {
type: FBType
kind: FBKind
name: String
slug: String
avatar: String
status: FBStatusUpdateManyInput
}
@@ -450,6 +478,126 @@ input FinancialBeingsWhereInput {
"""All values that are not contained in given list."""
kind_not_in: [FBKind!]
name: String
"""All values that are not equal to given value."""
name_not: String
"""All values that are contained in given list."""
name_in: [String!]
"""All values that are not contained in given list."""
name_not_in: [String!]
"""All values less than the given value."""
name_lt: String
"""All values less than or equal the given value."""
name_lte: String
"""All values greater than the given value."""
name_gt: String
"""All values greater than or equal the given value."""
name_gte: String
"""All values containing the given string."""
name_contains: String
"""All values not containing the given string."""
name_not_contains: String
"""All values starting with the given string."""
name_starts_with: String
"""All values not starting with the given string."""
name_not_starts_with: String
"""All values ending with the given string."""
name_ends_with: String
"""All values not ending with the given string."""
name_not_ends_with: String
slug: String
"""All values that are not equal to given value."""
slug_not: String
"""All values that are contained in given list."""
slug_in: [String!]
"""All values that are not contained in given list."""
slug_not_in: [String!]
"""All values less than the given value."""
slug_lt: String
"""All values less than or equal the given value."""
slug_lte: String
"""All values greater than the given value."""
slug_gt: String
"""All values greater than or equal the given value."""
slug_gte: String
"""All values containing the given string."""
slug_contains: String
"""All values not containing the given string."""
slug_not_contains: String
"""All values starting with the given string."""
slug_starts_with: String
"""All values not starting with the given string."""
slug_not_starts_with: String
"""All values ending with the given string."""
slug_ends_with: String
"""All values not ending with the given string."""
slug_not_ends_with: String
avatar: String
"""All values that are not equal to given value."""
avatar_not: String
"""All values that are contained in given list."""
avatar_in: [String!]
"""All values that are not contained in given list."""
avatar_not_in: [String!]
"""All values less than the given value."""
avatar_lt: String
"""All values less than or equal the given value."""
avatar_lte: String
"""All values greater than the given value."""
avatar_gt: String
"""All values greater than or equal the given value."""
avatar_gte: String
"""All values containing the given string."""
avatar_contains: String
"""All values not containing the given string."""
avatar_not_contains: String
"""All values starting with the given string."""
avatar_starts_with: String
"""All values not starting with the given string."""
avatar_not_starts_with: String
"""All values ending with the given string."""
avatar_ends_with: String
"""All values not ending with the given string."""
avatar_not_ends_with: String
updatedAt: DateTime
"""All values that are not equal to given value."""
@@ -513,9 +661,10 @@ scalar Long
type Member implements Node {
id: ID!
alias: String
team(where: TeamMembersWhereInput): TeamMembers
teams(where: TeamMembersWhereInput, orderBy: TeamMembersOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TeamMembers!]
authId: String!
visible: Boolean!
status(where: MemberStatusWhereInput, orderBy: MemberStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [MemberStatus!]
createdAt: DateTime!
}
@@ -533,18 +682,20 @@ input MemberCreateInput {
alias: String
authId: String!
visible: Boolean
team: TeamMembersCreateOneWithoutMemberInput
teams: TeamMembersCreateManyWithoutMemberInput
status: MemberStatusCreateManyInput
}
input MemberCreateOneWithoutTeamInput {
create: MemberCreateWithoutTeamInput
input MemberCreateOneWithoutTeamsInput {
create: MemberCreateWithoutTeamsInput
connect: MemberWhereUniqueInput
}
input MemberCreateWithoutTeamInput {
input MemberCreateWithoutTeamsInput {
alias: String
authId: String!
visible: Boolean
status: MemberStatusCreateManyInput
}
"""An edge in a connection."""
@@ -806,27 +957,29 @@ input MemberUpdateInput {
alias: String
authId: String
visible: Boolean
team: TeamMembersUpdateOneWithoutMemberInput
teams: TeamMembersUpdateManyWithoutMemberInput
status: MemberStatusUpdateManyInput
}
input MemberUpdateOneWithoutTeamInput {
create: MemberCreateWithoutTeamInput
input MemberUpdateOneWithoutTeamsInput {
create: MemberCreateWithoutTeamsInput
connect: MemberWhereUniqueInput
disconnect: Boolean
delete: Boolean
update: MemberUpdateWithoutTeamDataInput
upsert: MemberUpsertWithoutTeamInput
update: MemberUpdateWithoutTeamsDataInput
upsert: MemberUpsertWithoutTeamsInput
}
input MemberUpdateWithoutTeamDataInput {
input MemberUpdateWithoutTeamsDataInput {
alias: String
authId: String
visible: Boolean
status: MemberStatusUpdateManyInput
}
input MemberUpsertWithoutTeamInput {
update: MemberUpdateWithoutTeamDataInput!
create: MemberCreateWithoutTeamInput!
input MemberUpsertWithoutTeamsInput {
update: MemberUpdateWithoutTeamsDataInput!
create: MemberCreateWithoutTeamsInput!
}
input MemberWhereInput {
@@ -984,7 +1137,12 @@ input MemberWhereInput {
"""All values greater than or equal the given value."""
createdAt_gte: DateTime
team: TeamMembersWhereInput
teams_every: TeamMembersWhereInput
teams_some: TeamMembersWhereInput
teams_none: TeamMembersWhereInput
status_every: MemberStatusWhereInput
status_some: MemberStatusWhereInput
status_none: MemberStatusWhereInput
}
input MemberWhereUniqueInput {
@@ -999,36 +1157,45 @@ type Mutation {
createFinancialBeings(data: FinancialBeingsCreateInput!): FinancialBeings!
createTeamProfile(data: TeamProfileCreateInput!): TeamProfile!
createWallet(data: WalletCreateInput!): Wallet!
createFBStatus(data: FBStatusCreateInput!): FBStatus!
createTeamStatus(data: TeamStatusCreateInput!): TeamStatus!
createMemberStatus(data: MemberStatusCreateInput!): MemberStatus!
createFBStatus(data: FBStatusCreateInput!): FBStatus!
createTeamMemberStatus(data: TeamMemberStatusCreateInput!): TeamMemberStatus!
createTeamStatus(data: TeamStatusCreateInput!): TeamStatus!
updateTeam(data: TeamUpdateInput!, where: TeamWhereUniqueInput!): Team
updateTeamMembers(data: TeamMembersUpdateInput!, where: TeamMembersWhereUniqueInput!): TeamMembers
updateMember(data: MemberUpdateInput!, where: MemberWhereUniqueInput!): Member
updateFinancialBeings(data: FinancialBeingsUpdateInput!, where: FinancialBeingsWhereUniqueInput!): FinancialBeings
updateTeamMemberStatus(data: TeamMemberStatusUpdateInput!, where: TeamMemberStatusWhereUniqueInput!): TeamMemberStatus
deleteTeam(where: TeamWhereUniqueInput!): Team
deleteTeamMembers(where: TeamMembersWhereUniqueInput!): TeamMembers
deleteMember(where: MemberWhereUniqueInput!): Member
deleteFinancialBeings(where: FinancialBeingsWhereUniqueInput!): FinancialBeings
deleteTeamMemberStatus(where: TeamMemberStatusWhereUniqueInput!): TeamMemberStatus
upsertTeam(where: TeamWhereUniqueInput!, create: TeamCreateInput!, update: TeamUpdateInput!): Team!
upsertTeamMembers(where: TeamMembersWhereUniqueInput!, create: TeamMembersCreateInput!, update: TeamMembersUpdateInput!): TeamMembers!
upsertMember(where: MemberWhereUniqueInput!, create: MemberCreateInput!, update: MemberUpdateInput!): Member!
upsertFinancialBeings(where: FinancialBeingsWhereUniqueInput!, create: FinancialBeingsCreateInput!, update: FinancialBeingsUpdateInput!): FinancialBeings!
upsertTeamMemberStatus(where: TeamMemberStatusWhereUniqueInput!, create: TeamMemberStatusCreateInput!, update: TeamMemberStatusUpdateInput!): TeamMemberStatus!
updateManyTeams(data: TeamUpdateInput!, where: TeamWhereInput): BatchPayload!
updateManyTeamMemberses(data: TeamMembersUpdateInput!, where: TeamMembersWhereInput): BatchPayload!
updateManyMembers(data: MemberUpdateInput!, where: MemberWhereInput): BatchPayload!
updateManyFinancialBeingses(data: FinancialBeingsUpdateInput!, where: FinancialBeingsWhereInput): BatchPayload!
updateManyTeamProfiles(data: TeamProfileUpdateInput!, where: TeamProfileWhereInput): BatchPayload!
updateManyWallets(data: WalletUpdateInput!, where: WalletWhereInput): BatchPayload!
updateManyFBStatuses(data: FBStatusUpdateInput!, where: FBStatusWhereInput): BatchPayload!
updateManyTeamStatuses(data: TeamStatusUpdateInput!, where: TeamStatusWhereInput): BatchPayload!
updateManyMemberStatuses(data: MemberStatusUpdateInput!, where: MemberStatusWhereInput): BatchPayload!
updateManyFBStatuses(data: FBStatusUpdateInput!, where: FBStatusWhereInput): BatchPayload!
updateManyTeamMemberStatuses(data: TeamMemberStatusUpdateInput!, where: TeamMemberStatusWhereInput): BatchPayload!
updateManyTeamStatuses(data: TeamStatusUpdateInput!, where: TeamStatusWhereInput): BatchPayload!
deleteManyTeams(where: TeamWhereInput): BatchPayload!
deleteManyTeamMemberses(where: TeamMembersWhereInput): BatchPayload!
deleteManyMembers(where: MemberWhereInput): BatchPayload!
deleteManyFinancialBeingses(where: FinancialBeingsWhereInput): BatchPayload!
deleteManyTeamProfiles(where: TeamProfileWhereInput): BatchPayload!
deleteManyWallets(where: WalletWhereInput): BatchPayload!
deleteManyFBStatuses(where: FBStatusWhereInput): BatchPayload!
deleteManyTeamStatuses(where: TeamStatusWhereInput): BatchPayload!
deleteManyMemberStatuses(where: MemberStatusWhereInput): BatchPayload!
deleteManyFBStatuses(where: FBStatusWhereInput): BatchPayload!
deleteManyTeamMemberStatuses(where: TeamMemberStatusWhereInput): BatchPayload!
deleteManyTeamStatuses(where: TeamStatusWhereInput): BatchPayload!
}
enum MutationType {
@@ -1065,21 +1232,25 @@ type Query {
financialBeingses(where: FinancialBeingsWhereInput, orderBy: FinancialBeingsOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [FinancialBeings]!
teamProfiles(where: TeamProfileWhereInput, orderBy: TeamProfileOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TeamProfile]!
wallets(where: WalletWhereInput, orderBy: WalletOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [Wallet]!
fBStatuses(where: FBStatusWhereInput, orderBy: FBStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [FBStatus]!
teamStatuses(where: TeamStatusWhereInput, orderBy: TeamStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TeamStatus]!
memberStatuses(where: MemberStatusWhereInput, orderBy: MemberStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [MemberStatus]!
fBStatuses(where: FBStatusWhereInput, orderBy: FBStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [FBStatus]!
teamMemberStatuses(where: TeamMemberStatusWhereInput, orderBy: TeamMemberStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TeamMemberStatus]!
teamStatuses(where: TeamStatusWhereInput, orderBy: TeamStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TeamStatus]!
team(where: TeamWhereUniqueInput!): Team
teamMembers(where: TeamMembersWhereUniqueInput!): TeamMembers
member(where: MemberWhereUniqueInput!): Member
financialBeings(where: FinancialBeingsWhereUniqueInput!): FinancialBeings
teamMemberStatus(where: TeamMemberStatusWhereUniqueInput!): TeamMemberStatus
teamsConnection(where: TeamWhereInput, orderBy: TeamOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TeamConnection!
teamMembersesConnection(where: TeamMembersWhereInput, orderBy: TeamMembersOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TeamMembersConnection!
membersConnection(where: MemberWhereInput, orderBy: MemberOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): MemberConnection!
financialBeingsesConnection(where: FinancialBeingsWhereInput, orderBy: FinancialBeingsOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): FinancialBeingsConnection!
teamProfilesConnection(where: TeamProfileWhereInput, orderBy: TeamProfileOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TeamProfileConnection!
walletsConnection(where: WalletWhereInput, orderBy: WalletOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): WalletConnection!
fBStatusesConnection(where: FBStatusWhereInput, orderBy: FBStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): FBStatusConnection!
teamStatusesConnection(where: TeamStatusWhereInput, orderBy: TeamStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TeamStatusConnection!
memberStatusesConnection(where: MemberStatusWhereInput, orderBy: MemberStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): MemberStatusConnection!
fBStatusesConnection(where: FBStatusWhereInput, orderBy: FBStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): FBStatusConnection!
teamMemberStatusesConnection(where: TeamMemberStatusWhereInput, orderBy: TeamMemberStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TeamMemberStatusConnection!
teamStatusesConnection(where: TeamStatusWhereInput, orderBy: TeamStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): TeamStatusConnection!
"""Fetches an object given its ID"""
node(
@@ -1097,8 +1268,9 @@ enum Role {
enum Status {
ACTIVE
INACTIVE
APPLY
INVITE
APPLIED
INVITED
REMOVED
}
type Subscription {
@@ -1108,9 +1280,10 @@ type Subscription {
financialBeings(where: FinancialBeingsSubscriptionWhereInput): FinancialBeingsSubscriptionPayload
teamProfile(where: TeamProfileSubscriptionWhereInput): TeamProfileSubscriptionPayload
wallet(where: WalletSubscriptionWhereInput): WalletSubscriptionPayload
fBStatus(where: FBStatusSubscriptionWhereInput): FBStatusSubscriptionPayload
teamStatus(where: TeamStatusSubscriptionWhereInput): TeamStatusSubscriptionPayload
memberStatus(where: MemberStatusSubscriptionWhereInput): MemberStatusSubscriptionPayload
fBStatus(where: FBStatusSubscriptionWhereInput): FBStatusSubscriptionPayload
teamMemberStatus(where: TeamMemberStatusSubscriptionWhereInput): TeamMemberStatusSubscriptionPayload
teamStatus(where: TeamStatusSubscriptionWhereInput): TeamStatusSubscriptionPayload
}
type Team implements Node {
@@ -1186,11 +1359,14 @@ type TeamEdge {
cursor: String!
}
type TeamMembers {
type TeamMembers implements Node {
id: ID!
member(where: MemberWhereInput): Member
team(where: TeamWhereInput): Team
role: Role
status(where: MemberStatusWhereInput, orderBy: MemberStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [MemberStatus!]
status(where: TeamMemberStatusWhereInput, orderBy: TeamMemberStatusOrderByInput, skip: Int, after: String, before: String, first: Int, last: Int): [TeamMemberStatus!]
email: String
createdAt: DateTime!
updatedAt: DateTime!
}
@@ -1206,29 +1382,34 @@ type TeamMembersConnection {
input TeamMembersCreateInput {
role: Role
member: MemberCreateOneWithoutTeamInput
email: String
member: MemberCreateOneWithoutTeamsInput
team: TeamCreateOneWithoutMembersInput
status: MemberStatusCreateManyInput
status: TeamMemberStatusCreateManyInput
}
input TeamMembersCreateManyWithoutMemberInput {
create: [TeamMembersCreateWithoutMemberInput!]
connect: [TeamMembersWhereUniqueInput!]
}
input TeamMembersCreateManyWithoutTeamInput {
create: [TeamMembersCreateWithoutTeamInput!]
}
input TeamMembersCreateOneWithoutMemberInput {
create: TeamMembersCreateWithoutMemberInput
connect: [TeamMembersWhereUniqueInput!]
}
input TeamMembersCreateWithoutMemberInput {
role: Role
email: String
team: TeamCreateOneWithoutMembersInput
status: MemberStatusCreateManyInput
status: TeamMemberStatusCreateManyInput
}
input TeamMembersCreateWithoutTeamInput {
role: Role
member: MemberCreateOneWithoutTeamInput
status: MemberStatusCreateManyInput
email: String
member: MemberCreateOneWithoutTeamsInput
status: TeamMemberStatusCreateManyInput
}
"""An edge in a connection."""
@@ -1241,18 +1422,23 @@ type TeamMembersEdge {
}
enum TeamMembersOrderByInput {
role_ASC
role_DESC
updatedAt_ASC
updatedAt_DESC
id_ASC
id_DESC
role_ASC
role_DESC
email_ASC
email_DESC
createdAt_ASC
createdAt_DESC
updatedAt_ASC
updatedAt_DESC
}
type TeamMembersPreviousValues {
id: ID!
role: Role
email: String
createdAt: DateTime!
updatedAt: DateTime!
}
@@ -1295,36 +1481,320 @@ input TeamMembersSubscriptionWhereInput {
node: TeamMembersWhereInput
}
type TeamMemberStatus implements Node {
id: ID!
status: Status
reason: String
createdAt: DateTime!
}
"""A connection to a list of items."""
type TeamMemberStatusConnection {
"""Information to aid in pagination."""
pageInfo: PageInfo!
"""A list of edges."""
edges: [TeamMemberStatusEdge]!
aggregate: AggregateTeamMemberStatus!
}
input TeamMemberStatusCreateInput {
status: Status
reason: String
}
input TeamMemberStatusCreateManyInput {
create: [TeamMemberStatusCreateInput!]
connect: [TeamMemberStatusWhereUniqueInput!]
}
"""An edge in a connection."""
type TeamMemberStatusEdge {
"""The item at the end of the edge."""
node: TeamMemberStatus!
"""A cursor for use in pagination."""
cursor: String!
}
enum TeamMemberStatusOrderByInput {
id_ASC
id_DESC
status_ASC
status_DESC
reason_ASC
reason_DESC
createdAt_ASC
createdAt_DESC
updatedAt_ASC
updatedAt_DESC
}
type TeamMemberStatusPreviousValues {
id: ID!
status: Status
reason: String
createdAt: DateTime!
}
type TeamMemberStatusSubscriptionPayload {
mutation: MutationType!
node: TeamMemberStatus
updatedFields: [String!]
previousValues: TeamMemberStatusPreviousValues
}
input TeamMemberStatusSubscriptionWhereInput {
"""Logical AND on all given filters."""
AND: [TeamMemberStatusSubscriptionWhereInput!]
"""Logical OR on all given filters."""
OR: [TeamMemberStatusSubscriptionWhereInput!]
"""Logical NOT on all given filters combined by AND."""
NOT: [TeamMemberStatusSubscriptionWhereInput!]
"""
The subscription event gets dispatched when it's listed in mutation_in
"""
mutation_in: [MutationType!]
"""
The subscription event gets only dispatched when one of the updated fields names is included in this list
"""
updatedFields_contains: String
"""
The subscription event gets only dispatched when all of the field names included in this list have been updated
"""
updatedFields_contains_every: [String!]
"""
The subscription event gets only dispatched when some of the field names included in this list have been updated
"""
updatedFields_contains_some: [String!]
node: TeamMemberStatusWhereInput
}
input TeamMemberStatusUpdateDataInput {
status: Status
reason: String
}
input TeamMemberStatusUpdateInput {
status: Status
reason: String
}
input TeamMemberStatusUpdateManyInput {
create: [TeamMemberStatusCreateInput!]
connect: [TeamMemberStatusWhereUniqueInput!]
disconnect: [TeamMemberStatusWhereUniqueInput!]
delete: [TeamMemberStatusWhereUniqueInput!]
update: [TeamMemberStatusUpdateWithWhereUniqueNestedInput!]
upsert: [TeamMemberStatusUpsertWithWhereUniqueNestedInput!]
}
input TeamMemberStatusUpdateWithWhereUniqueNestedInput {
where: TeamMemberStatusWhereUniqueInput!
data: TeamMemberStatusUpdateDataInput!
}
input TeamMemberStatusUpsertWithWhereUniqueNestedInput {
where: TeamMemberStatusWhereUniqueInput!
update: TeamMemberStatusUpdateDataInput!
create: TeamMemberStatusCreateInput!
}
input TeamMemberStatusWhereInput {
"""Logical AND on all given filters."""
AND: [TeamMemberStatusWhereInput!]
"""Logical OR on all given filters."""
OR: [TeamMemberStatusWhereInput!]
"""Logical NOT on all given filters combined by AND."""
NOT: [TeamMemberStatusWhereInput!]
id: ID
"""All values that are not equal to given value."""
id_not: ID
"""All values that are contained in given list."""
id_in: [ID!]
"""All values that are not contained in given list."""
id_not_in: [ID!]
"""All values less than the given value."""
id_lt: ID
"""All values less than or equal the given value."""
id_lte: ID
"""All values greater than the given value."""
id_gt: ID
"""All values greater than or equal the given value."""
id_gte: ID
"""All values containing the given string."""
id_contains: ID
"""All values not containing the given string."""
id_not_contains: ID
"""All values starting with the given string."""
id_starts_with: ID
"""All values not starting with the given string."""
id_not_starts_with: ID
"""All values ending with the given string."""
id_ends_with: ID
"""All values not ending with the given string."""
id_not_ends_with: ID
status: Status
"""All values that are not equal to given value."""
status_not: Status
"""All values that are contained in given list."""
status_in: [Status!]
"""All values that are not contained in given list."""
status_not_in: [Status!]
reason: String
"""All values that are not equal to given value."""
reason_not: String
"""All values that are contained in given list."""
reason_in: [String!]
"""All values that are not contained in given list."""
reason_not_in: [String!]
"""All values less than the given value."""
reason_lt: String
"""All values less than or equal the given value."""
reason_lte: String
"""All values greater than the given value."""
reason_gt: String
"""All values greater than or equal the given value."""
reason_gte: String
"""All values containing the given string."""
reason_contains: String
"""All values not containing the given string."""
reason_not_contains: String
"""All values starting with the given string."""
reason_starts_with: String
"""All values not starting with the given string."""
reason_not_starts_with: String
"""All values ending with the given string."""
reason_ends_with: String
"""All values not ending with the given string."""
reason_not_ends_with: String
createdAt: DateTime
"""All values that are not equal to given value."""
createdAt_not: DateTime
"""All values that are contained in given list."""
createdAt_in: [DateTime!]
"""All values that are not contained in given list."""
createdAt_not_in: [DateTime!]
"""All values less than the given value."""
createdAt_lt: DateTime
"""All values less than or equal the given value."""
createdAt_lte: DateTime
"""All values greater than the given value."""
createdAt_gt: DateTime
"""All values greater than or equal the given value."""
createdAt_gte: DateTime
}
input TeamMemberStatusWhereUniqueInput {
id: ID
}
input TeamMembersUpdateInput {
role: Role
member: MemberUpdateOneWithoutTeamInput
email: String
member: MemberUpdateOneWithoutTeamsInput
team: TeamUpdateOneWithoutMembersInput
status: MemberStatusUpdateManyInput
status: TeamMemberStatusUpdateManyInput
}
input TeamMembersUpdateManyWithoutMemberInput {
create: [TeamMembersCreateWithoutMemberInput!]
connect: [TeamMembersWhereUniqueInput!]
disconnect: [TeamMembersWhereUniqueInput!]
delete: [TeamMembersWhereUniqueInput!]
update: [TeamMembersUpdateWithWhereUniqueWithoutMemberInput!]
upsert: [TeamMembersUpsertWithWhereUniqueWithoutMemberInput!]
}
input TeamMembersUpdateManyWithoutTeamInput {
create: [TeamMembersCreateWithoutTeamInput!]
}
input TeamMembersUpdateOneWithoutMemberInput {
create: TeamMembersCreateWithoutMemberInput
disconnect: Boolean
delete: Boolean
update: TeamMembersUpdateWithoutMemberDataInput
upsert: TeamMembersUpsertWithoutMemberInput
connect: [TeamMembersWhereUniqueInput!]
disconnect: [TeamMembersWhereUniqueInput!]
delete: [TeamMembersWhereUniqueInput!]
update: [TeamMembersUpdateWithWhereUniqueWithoutTeamInput!]
upsert: [TeamMembersUpsertWithWhereUniqueWithoutTeamInput!]
}
input TeamMembersUpdateWithoutMemberDataInput {
role: Role
email: String
team: TeamUpdateOneWithoutMembersInput
status: MemberStatusUpdateManyInput
status: TeamMemberStatusUpdateManyInput
}
input TeamMembersUpsertWithoutMemberInput {
input TeamMembersUpdateWithoutTeamDataInput {
role: Role
email: String
member: MemberUpdateOneWithoutTeamsInput
status: TeamMemberStatusUpdateManyInput
}
input TeamMembersUpdateWithWhereUniqueWithoutMemberInput {
where: TeamMembersWhereUniqueInput!
data: TeamMembersUpdateWithoutMemberDataInput!
}
input TeamMembersUpdateWithWhereUniqueWithoutTeamInput {
where: TeamMembersWhereUniqueInput!
data: TeamMembersUpdateWithoutTeamDataInput!
}
input TeamMembersUpsertWithWhereUniqueWithoutMemberInput {
where: TeamMembersWhereUniqueInput!
update: TeamMembersUpdateWithoutMemberDataInput!
create: TeamMembersCreateWithoutMemberInput!
}
input TeamMembersUpsertWithWhereUniqueWithoutTeamInput {
where: TeamMembersWhereUniqueInput!
update: TeamMembersUpdateWithoutTeamDataInput!
create: TeamMembersCreateWithoutTeamInput!
}
input TeamMembersWhereInput {
"""Logical AND on all given filters."""
AND: [TeamMembersWhereInput!]
@@ -1334,6 +1804,46 @@ input TeamMembersWhereInput {
"""Logical NOT on all given filters combined by AND."""
NOT: [TeamMembersWhereInput!]
id: ID
"""All values that are not equal to given value."""
id_not: ID
"""All values that are contained in given list."""
id_in: [ID!]
"""All values that are not contained in given list."""
id_not_in: [ID!]
"""All values less than the given value."""
id_lt: ID
"""All values less than or equal the given value."""
id_lte: ID
"""All values greater than the given value."""
id_gt: ID
"""All values greater than or equal the given value."""
id_gte: ID
"""All values containing the given string."""
id_contains: ID
"""All values not containing the given string."""
id_not_contains: ID
"""All values starting with the given string."""
id_starts_with: ID
"""All values not starting with the given string."""
id_not_starts_with: ID
"""All values ending with the given string."""
id_ends_with: ID
"""All values not ending with the given string."""
id_not_ends_with: ID
role: Role
"""All values that are not equal to given value."""
@@ -1344,6 +1854,68 @@ input TeamMembersWhereInput {
"""All values that are not contained in given list."""
role_not_in: [Role!]
email: String
"""All values that are not equal to given value."""
email_not: String
"""All values that are contained in given list."""
email_in: [String!]
"""All values that are not contained in given list."""
email_not_in: [String!]
"""All values less than the given value."""
email_lt: String
"""All values less than or equal the given value."""
email_lte: String
"""All values greater than the given value."""
email_gt: String
"""All values greater than or equal the given value."""
email_gte: String
"""All values containing the given string."""
email_contains: String
"""All values not containing the given string."""
email_not_contains: String
"""All values starting with the given string."""
email_starts_with: String
"""All values not starting with the given string."""
email_not_starts_with: String
"""All values ending with the given string."""
email_ends_with: String
"""All values not ending with the given string."""
email_not_ends_with: String
createdAt: DateTime
"""All values that are not equal to given value."""
createdAt_not: DateTime
"""All values that are contained in given list."""
createdAt_in: [DateTime!]
"""All values that are not contained in given list."""
createdAt_not_in: [DateTime!]
"""All values less than the given value."""
createdAt_lt: DateTime
"""All values less than or equal the given value."""
createdAt_lte: DateTime
"""All values greater than the given value."""
createdAt_gt: DateTime
"""All values greater than or equal the given value."""
createdAt_gte: DateTime
updatedAt: DateTime
"""All values that are not equal to given value."""
@@ -1368,9 +1940,14 @@ input TeamMembersWhereInput {
updatedAt_gte: DateTime
member: MemberWhereInput
team: TeamWhereInput
status_every: MemberStatusWhereInput
status_some: MemberStatusWhereInput
status_none: MemberStatusWhereInput
status_every: TeamMemberStatusWhereInput
status_some: TeamMemberStatusWhereInput
status_none: TeamMemberStatusWhereInput
}
input TeamMembersWhereUniqueInput {
id: ID
email: String
}
enum TeamOrderByInput {
@@ -22,10 +22,29 @@ const TEAMS_CONNECTIONS_FRAGMENT = gql`{
}
members {
role
email
member {
alias
authId
}
status {
id
status
reason
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
}
+19 -1
View File
@@ -13,17 +13,35 @@ const TEAMS_FRAGMENT = gql`{
createdAt
profile {
avatar
banner
description
motto
updatedAt
}
members {
role
email
member {
alias
authId
}
status {
id
status
reason
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}`
+86 -17
View File
@@ -8,6 +8,7 @@ const {
const { makeExecutableSchema } = require('graphql-tools')
const { importSchema } = require('graphql-import')
const parser = require('fast-xml-parser')
var axios = require('axios')
const checkJwt = require('./auth/middleware/jwt')
const { getMember } = require('./auth/middleware/getMember')
@@ -20,6 +21,8 @@ const azureAccount = process.env.AZURE_STORAGE_ACCOUNT
const azureKey = process.env.AZURE_STORAGE_ACCESS_KEY
const azureStorageUrl = process.env.AZURE_STORAGE_URL
const { sendTeamMemberInvite, sendTeamCreateConfirmation, verifyInviteToken } = require('./email/sendgrid')
const TEAMS_FRAGMENT = require('./graphql/TeamsFragment')
const TEAMS_CONNECTIONS_FRAGMENT = require('./graphql/TeamsConnectionsFragment')
@@ -73,10 +76,18 @@ const resolvers = {
async teamByName(parent, { name }, ctx, info) {
return ctx.db.query.team({ where: { name: name } }, `{ name }`)
},
async teamWithRole(parent, { teamId, role }, ctx, info) {
return ctx.db.query.teamsConnection({where: {AND: [{id: teamId},{members_some:{role: role}}]}, first:1}, info)
},
async teamsByOwner(parent, { ownerId }, ctx, info) {
console.log('teamsByOwner: ', ctx)
return ctx.db.query.teams({where: { owner: ownerId }, orderBy:'updatedAt_DESC'}, TEAMS_FRAGMENT)
},
async teamsByRole(parent, args, ctx, info) {
const authId = ctx.request.user.sub
let teamAdmin = await ctx.db.query.teams({where: {members_some: {OR: [{role: 'ADMIN'}, {role: 'OWNER'}], AND: [{member: {authId: authId}}]}}, orderBy: 'updatedAt_DESC'}, TEAMS_FRAGMENT)
console.log('teamsByRole teamAdmin: ', teamAdmin)
return teamAdmin
},
async owner(parent, args, ctx, info) {
console.log('resolver.query.owner ctx: ', ctxMember(ctx))
return ctx.db.query.member({ where: { id: ctxMember(ctx).authId } }, info)
@@ -110,15 +121,77 @@ const resolvers = {
}
return member
},
async createTeam(parent, { name, slug, owner }, ctx, info) {
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)
async sendMemberInviteSG(parent, { email, teamId }, ctx, info) {
const team = await ctx.db.query.team({ where: { id: teamId } }, TEAMS_FRAGMENT)
const addMember = await ctx.db.mutation.upsertTeamMembers({
where:{email: email},
create:{role:'MEMBER', email:email, team: {connect: {id: teamId}}, status:{create:{status: 'INVITED', reason: `Invited by ${team.members[0].member.alias}`}}},
update:{role: 'MEMBER', email: email, team: {connect: {id: teamId}}, status:{create:{status: 'INVITED', reason: `Invite resent by ${team.members[0].member.alias}`}}}},
`{ id }`)
const sendInvite = await sendTeamMemberInvite(email, team)
return sendInvite
},
async verifyTeamInvite(parent, { token }, ctx, info) {
let verifiedToken = null
let team = null
try {
verifiedToken = await verifyInviteToken(token)
team = {
email: verifiedToken.email,
team: {
slug: verifiedToken.team
}
}
console.log('verifyTeamInvite.token: ', await verifiedToken)
} catch (err) {
console.log('verifyTeamInvite. err: ', err)
throw new Error(err.message)
}
return team
},
async createTeam(parent, { name, slug, botName, botSlug }, ctx, info) {
console.log('createTeam ctx:', ctx.request.user)
const authId = ctx.request.user.sub
const encodedAuthId = encodeURI(authId)
const getUserModuleUser = await axios.get(`https://users-api.advancedalgos.net/graphql?query=%7B%0A%20%20userByAuthId(authId%3A%22${encodedAuthId}%22)%7B%0A%20%20%20%20id%0A%09%09email%0A%20%20%20%20alias%0A%20%20%7D%0A%7D`)
const alias = await getUserModuleUser.data.data.userByAuthId.alias
const email = getUserModuleUser.data.data.userByAuthId.email
const avatar = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-avatar-default.png'
const banner = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-banner-default.png'
/*
const createTeamUrl = encodeURI(`${slug}/${name}/${alias}/${botSlug}`)
const platformUrl = 'https://develop.advancedalgos.net/AABrowserAPI/teamSetup/'
// const platformUrl = 'http://localhost:1337/AABrowserAPI/teamSetup/'
const createPlatformTeam = await axios.get(`${platformUrl}${createTeamUrl}`)
console.log('createPlatformTeam: ', await createPlatformTeam)
// const sessionToken = getUserModuleUser.data.data.userByAuthId.id
update session token on user module
const updateUserSession = await axios.post('https://users-api.advancedalgos.net/graphql', {
query: 'mutation { language }'
})
*/
const createTeam = await ctx.db.mutation.createTeam({ data: {name: name, slug: slug, owner: authId, members: {create: {member: {connect: {authId: authId}}, role: 'OWNER'}}, profile: {create: {avatar: avatar, banner: banner}}, fb: {create: {name: botName, slug: botSlug, kind:'TRADER', avatar: avatar, status: {create: {status: 'ACTIVE', reason: "Cloned on team creation"}}}}, status: {create: {status: 'ACTIVE', reason:"Team created"}}} }, TEAMS_FRAGMENT)
.catch((err) => {
console.log('createTeam error: ', err)
return err
})
sendTeamCreateConfirmation(email, name, botName)
return createTeam
},
async updateTeamProfile(parent, { slug, owner, description, motto, avatar, banner }, ctx, info) {
return ctx.db.mutation.updateTeam({data:{profile: {update: {description: description, motto: motto, avatar: avatar, banner: banner}}}, where:{slug: slug}}, TEAMS_FRAGMENT)
.catch((err) => {
console.log('createTeam error: ', err)
return err
})
},
async updateTeamProfile(parent, { slug, owner, description, motto, avatar, banner }, ctx, info) {
return ctx.db.mutation.updateTeam({data:{profile: {update: {description: description, motto: motto, avatar: avatar, banner: banner}}}, where:{slug: slug}}, TEAMS_FRAGMENT)
async updateFB(parent, { fbId, avatar}, ctx, info) {
return ctx.db.mutation.updateFinancialBeings({data:{avatar:avatar}, where:{id:fbId}}, info)
.catch((err) => {
console.log('createTeam error: ', err)
return err
@@ -187,19 +260,15 @@ let whitelist = [
'http://localhost:3002',
'http://localhost:4000',
'http://localhost:4002',
'http://localhost:1337'
'http://localhost:1337',
'https://teams.advancedalgos.net',
'https://users.advancedalgos.net',
'https://users-api.advancedalgos.net',
'https://keyvault.advancedalgos.net',
'https://keyvault-api.advancedalgos.net',
'https://aawebdevelop.azurewebsites.net',
'https://develop.advancedalgos.net'
]
if(process.env.NODE_ENV === 'production'){
whitelist = [
'https://teams.advancedalgos.net',
'https://users.advancedalgos.net',
'https://users-api.advancedalgos.net',
'https://keyvault.advancedalgos.net',
'https://keyvault-api.advancedalgos.net',
'http://localhost:1337'
]
}
const corsOptionsDelegate = (req, callback) => {
var corsOptions
+9 -2
View File
@@ -10,7 +10,9 @@ type Query {
teamDetails: [Team!]! @isAuthenticated
teamByName(name: String!): Team
teamById(teamId: String!): Team
teamWithRole(teamId: String!, role: String!): Team
teamsByOwner(ownerId: String!): [Team!]! @isAuthenticated
teamsByRole: [Team!]! @isAuthenticated
owner: Member @isAuthenticated
currentMember: Member @isAuthenticated
member(authId: String!): Member @isAuthenticated
@@ -18,10 +20,15 @@ type Query {
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, banner: String): Team @isAuthenticated
verifyTeamInvite(token: String!): TeamMembers
createTeam(name: String!, slug: String!, botName: String!, botSlug: String!): Team @isAuthenticated
updateTeamProfile(slug: String!, description: String, motto: String, avatar: String, banner: String): Team @isAuthenticated
updateFB(fbId: String!, avatar: String): FinancialBeings @isAuthenticated
deleteTeam(slug: String!): Team @isAuthenticated @isOwner(type: "Team")
getAzureSAS(teamSlug: String!):String
sendMemberInviteSG(email: String!, teamId: String!): String
confirmMember(token: String!): String
removeTeamMember(teamId: String!, memberId: String!): String
}
fragment MemberInfo on Member {
+1 -1
View File
@@ -145,7 +145,7 @@ const createSASQueryURL = async (containerName) => {
// console.log(`ContainerCheck marker: `, listContainersResponse)
marker = listContainersResponse.marker;
for (const container of listContainersResponse.containerItems) {
// console.log(`ContainerCheck: ${container.name} | ${containerName} | ${marker}`)
console.log(`ContainerCheck: ${container.name} | ${containerName} | ${marker}`)
if(container.name === containerName){
containerCheck = container.name
}
+1
View File
@@ -56,6 +56,7 @@ typings/
# dotenv environment variables file
.env
.env.prod
# Build folder js files
build/
+25 -8
View File
@@ -6363,8 +6363,7 @@
"decode-uri-component": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU="
},
"decompress-response": {
"version": "3.3.0",
@@ -12204,6 +12203,18 @@
"prepend-http": "^1.0.0",
"query-string": "^4.1.0",
"sort-keys": "^1.0.0"
},
"dependencies": {
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"dev": true,
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
}
}
}
},
"npm-run-all": {
@@ -13899,13 +13910,19 @@
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
},
"query-string": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
"dev": true,
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-6.1.0.tgz",
"integrity": "sha512-pNB/Gr8SA8ff8KpUFM36o/WFAlthgaThka5bV19AD9PNTH20Pwq5Zxodif2YyHwrctp6SkL4GqlOot0qR/wGaw==",
"requires": {
"object-assign": "^4.1.0",
"strict-uri-encode": "^1.0.0"
"decode-uri-component": "^0.2.0",
"strict-uri-encode": "^2.0.0"
},
"dependencies": {
"strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha1-ucczDHBChi9rFC3CdLvMWGbONUY="
}
}
},
"querystring": {
+3 -4
View File
@@ -59,8 +59,8 @@
"script-ext-html-webpack-plugin": "^2.0.1",
"style-loader": "^0.21.0",
"uglifyjs-webpack-plugin": "^1.3.0",
"webpack": "^4.17.1",
"webpack-cli": "^2.1.5",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.1",
"webpack-hot-client": "^4.1.1",
"webpack-merge": "^4.1.4",
"webpack-serve": "^1.0.4",
@@ -81,7 +81,6 @@
"apollo-link-ws": "^1.0.8",
"apollo-utilities": "^1.0.20",
"auth0-lock": "^11.9.0",
"azure-storage": "^2.10.1",
"babel-polyfill": "^6.26.0",
"blob-util": "^2.0.2",
"browser-image-resizer": "^1.2.0",
@@ -91,11 +90,11 @@
"graphql": "^0.13.2",
"graphql-tag": "^2.9.2",
"prop-types": "^15.6.2",
"query-string": "^6.1.0",
"react": "^16.5.0",
"react-apollo": "^2.1.11",
"react-dom": "^16.5.0",
"react-dropzone": "^5.1.0",
"react-image-crop": "^5.0.0",
"react-image-cropper": "^1.3.0",
"react-router-dom": "^4.3.1",
"recompose": "^0.30.0",
+6
View File
@@ -19,6 +19,7 @@ import {
NavBar,
Footer,
Callback,
AcceptTeamInvite,
Home,
About,
Dashboard,
@@ -171,6 +172,11 @@ class App extends Component {
return <Callback {...props} />
}}
/>
<Route
exact
path='/activate-team-membership'
render={props => <AcceptTeamInvite {...props} auth={auth} />}
/>
</Switch>
<Footer />
</div>
+58 -2
View File
@@ -16,7 +16,19 @@ const AUTHENTICATE = gql`
}
}
`
const lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, {
const VERIFY_TEAM_INVITE = gql`
mutation verifyTeamInvite($token: String!) {
verifyTeamInvite(token: $token) {
email
team {
slug
}
}
}
`
export const defaultOptions = {
oidcConformant: true,
autoclose: true,
auth: {
@@ -28,7 +40,27 @@ const lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, {
scope: `openid profile email user_metadata app_metadata picture`
}
}
})
}
export const inviteOptions = {
oidcConformant: true,
autoclose: false,
allowedConnections: ['Username-Password-Authentication'],
allowShowPassword: true,
auth: {
sso: false,
redirectUrl: window.location.origin + '/callback',
responseType: 'token id_token',
audience: `${AUTH_CONFIG.api_audience}`,
params: {
state: '',
scope: `email`
}
},
prefill: { email: '' }
}
let lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, defaultOptions)
class Auth {
constructor (cb, apolloClient) {
@@ -37,6 +69,7 @@ class Auth {
this.apolloClient = apolloClient
this.cb = cb.bind(this)
this.login = this.login.bind(this)
this.loginInvite = this.loginInvite.bind(this)
this.logout = this.logout.bind(this)
this.isAuthenticated = this.isAuthenticated.bind(this)
this.checkSession = this.checkSession.bind(this)
@@ -47,6 +80,29 @@ class Auth {
lock.show()
}
async loginInvite (jwt) {
let email
let team
try {
const data = await client.mutate({
mutation: VERIFY_TEAM_INVITE,
variables: { token: jwt }
})
console.log('loginInvite auth: ', await data.data.verifyTeamInvite)
setItem('invite', JSON.stringify(data.data.verifyTeamInvite))
email = data.data.verifyTeamInvite.email
team = data.data.verifyTeamInvite.team.slug
} catch (err) {
return console.log('loginInvite err: ', err)
}
inviteOptions.prefill.email = email
inviteOptions.auth.params.state = `${email}|${team}`
let lockInvite = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, inviteOptions)
lockInvite.show()
}
handleAuthentication () {
// Add a callback for Lock's `authenticated` event
lock.on('authenticated', this.setSession.bind(this))
@@ -2,8 +2,8 @@
import gql from 'graphql-tag'
const CREATE_TEAM = gql`
mutation CreateTeamMutation($name: String!, $slug: String!, $owner: String!) {
createTeam(name: $name, slug: $slug, owner: $owner) {
mutation CreateTeamMutation($name: String!, $slug: String!, $botName: String!, $botSlug: String!) {
createTeam(name: $name, slug: $slug, botName: $botName, botSlug: $botSlug) {
id
name
slug
@@ -25,10 +25,29 @@ export const GET_ALL_TEAMS_QUERY = gql`
}
members {
role
email
member {
alias
authId
}
status {
id
status
reason
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
}
@@ -23,10 +23,29 @@ export const GET_TEAMS_BY_OWNER = gql`
}
members {
role
email
member {
alias
authId
}
status {
id
status
reason
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
}
@@ -0,0 +1,12 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const REMOVE_TEAM_MEMBER = gql`
mutation removeTeamMember($teamId: String!, $memberId: String!) {
removeTeamMember(teamId: $teamId, memberId: $memberId) {
id
}
}
`
export default REMOVE_TEAM_MEMBER
@@ -0,0 +1,10 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const SEND_MEMBER_INVITE = gql`
mutation sendMemberInvite($email: String!, $teamId: String!) {
sendMemberInviteSG(email: $email, teamId: $teamId)
}
`
export default SEND_MEMBER_INVITE
@@ -0,0 +1,12 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
export const SET_TEAM_MEMBER_ROLE = gql`
mutation setTeamMemberRole($teamId: String!, $memberId: String!, $role: String!) {
setTeamMemberRole(teamId: $teamId, memberId: $memberId, role: $role) {
id
}
}
`
export default SET_TEAM_MEMBER_ROLE
+19
View File
@@ -22,10 +22,29 @@ export const TEAMS_FRAGMENT = gql`
}
members {
role
email
member {
alias
authId
}
status {
id
status
reason
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
`
@@ -0,0 +1,16 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
const UPDATE_FB = gql`
mutation UpdateFBMutation($fbId: String!, $avatar: String) {
updateFB(fbId: $fbId, avatar: $avatar) {
id
name
slug
avatar
kind
}
}
`
export default UPDATE_FB
@@ -4,7 +4,6 @@ import gql from 'graphql-tag'
const UPDATE_TEAM_PROFILE = gql`
mutation UpdateTeamProfileMutation(
$slug: String!,
$owner: String!,
$description: String,
$motto: String,
$avatar: String,
@@ -12,7 +11,6 @@ const UPDATE_TEAM_PROFILE = gql`
) {
updateTeamProfile(
slug: $slug,
owner: $owner,
description: $description,
motto: $motto
avatar: $avatar,
@@ -37,10 +35,17 @@ const UPDATE_TEAM_PROFILE = gql`
}
members {
role
email
member {
alias
authId
}
status {
id
status
reason
createdAt
}
}
}
}
+3 -1
View File
@@ -1,5 +1,7 @@
import React from 'react'
const Callback = () => <div>Authenticating...</div>
import { MessageCard } from '@advancedalgos/web-components'
const Callback = () => <MessageCard message='Authenticating...' />
export default Callback
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import { MessageCard } from '@advancedalgos/web-components'
import ManageFBList from './components/ManageFBList'
const styles = theme => ({
tableContainer: {
@@ -17,9 +17,7 @@ const FinancialBeings = ({ classes }) => (
<Typography variant='display1' gutterBottom>
Financial Beings
</Typography>
<div className={classes.tableContainer}>
<MessageCard message='Coming soon. View your financial beings by team.' />
</div>
<ManageFBList />
</div>
)
+3 -3
View File
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
// import ManageTeamMembersList from './components/ManageTeamMembersList'
import { MessageCard } from '@advancedalgos/web-components'
const styles = theme => ({
@@ -16,10 +17,9 @@ const TeamMembers = ({ classes }) => (
<div>
<Typography variant='display1' gutterBottom>
Team Members
<MessageCard message='Coming soon. Invite and manage team members.' />
</Typography>
<div className={classes.tableContainer}>
<MessageCard message='Coming soon. Invite and manage team members' />
</div>
</div>
)
@@ -21,9 +21,19 @@ 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
},
dialogStyle: {
padding: '3em'
},
textField: {
width: '60%',
marginLeft: '20%',
marginBottom: 10
},
buttonRight: {
// position: 'absolute',
// right: '3em'
@@ -49,9 +59,9 @@ export class CreateTeamDialog extends Component {
this.state = {
open: false,
name: '',
errors: {
name: ''
}
botName: '',
nameError: '',
botNameError: ''
}
}
@@ -60,12 +70,10 @@ export class CreateTeamDialog extends Component {
return (
<Mutation
mutation={CREATE_TEAM}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER,
variables: { authId }
}
]}
refetchQueries={[{
query: GET_TEAMS_BY_OWNER,
variables: { authId: authId }
}]}
>
{(createTeam, { loading, error, data }) => {
let errors
@@ -101,43 +109,69 @@ export class CreateTeamDialog extends Component {
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<DialogTitle id='form-dialog-title'>Create a Team</DialogTitle>
<DialogContent>
<FormControl
required
error={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, authId)
}}
color='primary'
>
Create Team
</Button>
</DialogActions>
<div classes={classes.dialogContainer}>
<DialogTitle id='form-dialog-title'>Create a Team</DialogTitle>
<DialogContent>
<FormControl
required
error={this.state.nameError !== '' || this.state.botNameError !== '' || error !== null}
>
<Typography variant='subheading' align='center'>Create a name for your team</Typography>
<TextField
autoFocus
margin='dense'
id='name'
label='Team Name'
type='text'
fullWidth
className={classes.textField}
value={this.state.name}
onChange={this.handleChange}
error={this.state.nameError !== ''}
/>
{this.state.nameError !== '' && (
<FormHelperText>{this.state.nameError}</FormHelperText>
)}
<Typography variant='subheading' align='center' ><br />
Create a name for your teams bot.
</Typography>
<Typography align='center' >
<em>(Temporary: In the future, creating multiple Financial Beings <br />
will occur in a separate module)</em>
</Typography>
<TextField
autoFocus
margin='dense'
id='botName'
label='Team Bot Name'
type='text'
fullWidth
className={classes.textField}
value={this.state.botName}
onChange={this.handleChange}
error={this.state.botNameError !== ''}
/>
{this.state.botNameError !== '' && (
<FormHelperText>{this.state.botNameError}</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, this.state.botName)
}}
color='primary'
>
Create Team
</Button>
</DialogActions>
</div>
</Dialog>
</div>
)
@@ -155,27 +189,50 @@ export class CreateTeamDialog extends Component {
}
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 })
let id = e.target.id
let value = e.target.value
let error = this.validate({ id, value })
switch (id) {
case 'name':
if (!isEmpty(error)) this.setState({ nameError: error })
this.setState({ name: value })
break
case 'botName':
if (!isEmpty(error)) this.setState({ botNameError: error })
this.setState({ botName: value })
break
default:
}
}
async handleSubmit (e, createTeam, name, authId) {
async handleSubmit (e, createTeam, name, botName) {
e.preventDefault()
console.log('createTeam submit: ', authId, name)
console.log('createTeam submit: ', name, botName)
const slug = this.slugify(name)
await createTeam({ variables: { name, slug, owner: authId } })
const botSlug = this.slugify(name)
await createTeam({ variables: { name, slug, botName, botSlug } })
this.setState({ name: '', open: false })
}
validate (data) {
const errors = {}
if (data === '') errors.name = 'Please enter a team name'
switch (data.id) {
case 'name':
if (data.value === '') {
this.setState({ nameError: 'Please enter a team name' })
} else {
this.setState({ nameError: '' })
}
break
case 'botName':
if (data.value === '') {
this.setState({ botNameError: 'Please enter a bot name' })
} else {
this.setState({ botNameError: '' })
}
break
default:
}
return errors
}
@@ -11,8 +11,6 @@ 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)
@@ -25,7 +23,8 @@ export class CreateTeamDialog extends Component {
this.state = {
open: false,
name: ''
name: '',
botName: ''
}
}
@@ -38,7 +37,16 @@ export class CreateTeamDialog extends Component {
}
handleChange (e) {
this.setState({ name: e.target.value })
let value = e.target.value
switch (e.target.id) {
case 'name':
this.setState({ name: value })
break
case 'botName':
this.setState({ botName: value })
break
default:
}
}
render () {
@@ -73,6 +81,17 @@ export class CreateTeamDialog extends Component {
value={this.state.name}
onChange={this.handleChange}
/>
<DialogContentText>Create a name for you teams bot. (Temporary: In the future, creating multiple Financial Beings will occur in a separate module)</DialogContentText>
<TextField
autoFocus
margin='dense'
id='botname'
label='Team Bot Name'
type='text'
fullWidth
value={this.state.botName}
onChange={this.handleChange}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
@@ -106,20 +125,19 @@ export class CreateTeamDialog extends Component {
async handlePost (e) {
e.preventDefault()
const { name } = this.state
const { name, botName } = 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))
const botSlug = this.slugify(botName)
await this.props.createTeamMutation({
variables: {
name,
slug,
owner: authId
botName,
botSlug
}
})
this.setState({ open: false })
this.setState({ open: false, name: '', botName: '' })
}
}
@@ -18,14 +18,11 @@ 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)
@@ -71,7 +68,6 @@ export const DashTeam = ({ classes, user = null }) => {
))}
{queryLoader}
{errors}
{loader}
</Grid>
)
} else {
@@ -79,7 +75,7 @@ export const DashTeam = ({ classes, user = null }) => {
<Grid container spacing={40}>
<Grid item xs={10}>
<MessageCard message='No teams yet...'>
<CreateTeamDialog authId={authId} />
<CreateTeamDialog />
</MessageCard>
</Grid>
</Grid>
@@ -0,0 +1,207 @@
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 DialogTitle from '@material-ui/core/DialogTitle'
import Typography from '@material-ui/core/Typography'
import { isEmpty } from '../../../utils/js-helpers'
import SEND_MEMBER_INVITE from '../../../graphql/teams/SendMemberInvite'
// 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 InviteMemberDialog 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.state = {
open: false,
email: '',
errors: {
email: ''
}
}
}
render () {
const { classes, teamId } = this.props
if (this.state.open) {
return (
<Mutation mutation={SEND_MEMBER_INVITE} >
{(sendMemberInvite, { loading, error, data }) => {
console.log('sendMemberInvite: ', data)
let errors
let loader = null
if (loading) {
loader = (
<Typography variant='subheading'>Submitting invitation...</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'>Invite a New Team Member</DialogTitle>
<DialogContent>
{(data === undefined || data.sendMemberInviteSG !== 'Success') && (
<FormControl
required
error={this.state.errors.email !== '' || (error !== undefined || error !== null)}
>
<TextField
autoFocus
margin='dense'
id='email'
label='Email Address'
type='text'
fullWidth
value={this.state.email}
onChange={e => this.handleChange(e)}
error={this.state.errors.email !== ''}
/>
{this.state.errors.email !== '' && (
<FormHelperText>{this.state.errors.email}</FormHelperText>
)}
{error && <FormHelperText>{errors}</FormHelperText>}
{loader}
</FormControl>
)}
{data !== undefined && data.sendMemberInviteSG === 'Success' && (
<Typography variant='subheading'>Invitation successfully sent!</Typography>
)}
</DialogContent>
<DialogActions>
{(data === undefined || data.sendMemberInviteSG !== 'Success') && (
<React.Fragment>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button
onClick={e => {
this.handleSubmit(e, sendMemberInvite, this.state.email, teamId)
}}
color='primary'
>
Send Invitation
</Button>
</React.Fragment>
)}
{data !== undefined && data.sendMemberInviteSG === 'Success' && (
<Button onClick={this.handleClose} color='primary'>
Close
</Button>
)}
</DialogActions>
</Dialog>
</div>
)
}}
</Mutation>
)
} else {
return (
<Button
variant='fab'
color='primary'
aria-label='Add'
className={classes.buttonRight}
onClick={this.handleClickOpen}
>
<AddIcon />
</Button>
)
}
}
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: { email: '' } })
this.setState({ email: e.target.value })
} else {
this.setState({ errors: errors })
this.setState({ email: e.target.value })
}
}
async handleSubmit (e, sendMemberInvite, email, teamId) {
e.preventDefault()
console.log('sendMemberInvite submit: ', email, teamId)
await sendMemberInvite({ variables: { email, teamId } })
this.setState({ email: '' })
}
validate (data) {
const errors = {}
if (data === '') errors.email = 'Please enter an email address'
return errors
}
}
InviteMemberDialog.propTypes = {
classes: PropTypes.object.isRequired,
teamId: PropTypes.string
}
export default withStyles(styles)(InviteMemberDialog)
@@ -0,0 +1,223 @@
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 { MessageCard, ImageUpload } from '@advancedalgos/web-components'
import UPDATE_FB from '../../../graphql/teams/UpdateFBMutation'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import { checkGraphQLError } from '../../../utils/graphql-errors'
import GET_AZURE_SAS from '../../../graphql/teams/GetAzureSASMutation'
const styles = theme => ({
dialogContainer: {
display: 'block',
margin: '3em',
minWidth: 400
},
buttonRight: {
justifyContent: 'flex-end'
}
})
export class ManageFBEdit extends Component {
constructor (props) {
super(props)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleAvatar = this.handleAvatar.bind(this)
const avatar = props.fb.avatar || ''
this.authId = props.authId
this.state = {
open: false,
avatar: avatar
}
}
render () {
console.log('ManageFBEdit ', this.props, this.props.slug, this.props.fb)
const { classes, fb, slug } = this.props
return (
<Mutation
mutation={UPDATE_FB}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER,
variables: { authId: this.authId }
}
]}
>
{(updateFB, { 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 Financial Being
</DialogTitle>
<DialogContent>
<Mutation mutation={GET_AZURE_SAS} >
{(getAzureSAS, { loading, error, data }) => {
console.log('getAzureSAS: ', loading, error, data, fb)
const AzureStorageUrl = process.env.AZURE_STORAGE_URL
const containerName = slug
let AzureSASURL
if (!loading && data !== undefined) {
AzureSASURL = data.getAzureSAS
} else {
getAzureSAS({ variables: { teamSlug: containerName } })
}
let avatar = null
if (fb.avatar !== undefined && fb.avatar !== null) avatar = fb.avatar
if (this.state.avatar !== null) avatar = this.state.avatar
if (this.state.avatar === null && fb.avatar === undefined) avatar = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-avatar-default.png'
if (loading || data === undefined) {
return (<MessageCard message='Loading...' />)
} else {
return (
<React.Fragment>
<DialogContentText>Update Financial Beings's Avatar</DialogContentText>
<ImageUpload
key='avatar'
handleUrl={this.handleAvatar}
fileName={`${fb.slug}-fb-avatar.jpg`}
containerName={containerName}
existingImage={avatar}
cropContainer={{ x: 10, y: 10, width: 200, height: 200 }}
cropPreviewBox={{ width: 350, height: 350 }}
saveImageConfig={{
quality: 0.6,
maxWidth: 200,
maxHeight: 200,
autoRotate: true,
mimeType: 'image/jpeg'
}}
AzureStorageUrl={AzureStorageUrl}
AzureSASURL={AzureSASURL}
cropRatio={1}
debug
/>
</React.Fragment>
)
}
}}
</Mutation>
<TextField
autoFocus
margin='dense'
id='name'
label='Team Name'
type='text'
fullWidth
disabled
value={fb.name}
/>
{loader}
{errors}
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button
onClick={e => {
this.handleSubmit(e, updateFB, fb.id, slug)
}}
color='primary'
>
Update Financial Being
</Button>
</DialogActions>
</div>
</Dialog>
</div>
)
}}
</Mutation>
)
}
handleClickOpen () {
this.setState({ open: true })
}
handleClose () {
this.setState({ open: false })
}
handleAvatar (avatarUrl) {
console.log('handleAvatar: ', avatarUrl)
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
}
async handleSubmit (e, updateFB, fbId, slug) {
console.log('handleSubmit: ', this.state)
e.preventDefault()
await updateFB({
variables: {
fbId,
avatar: this.state.avatar
}
})
this.setState({ open: false })
}
}
ManageFBEdit.propTypes = {
classes: PropTypes.object.isRequired,
slug: PropTypes.string.isRequired,
fb: PropTypes.object,
authId: PropTypes.string.isRequired
}
export default withStyles(styles)(ManageFBEdit)
@@ -0,0 +1,53 @@
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 ManageFBEdit from './ManageFBEdit'
export const ManageFBItem = ({ classes, team, authId }) => {
console.log('ManageFBItem', team, team.fb[0], team.profile.avatar)
let avatar
if (team.fb[0].avatar !== undefined && team.fb[0].avatar !== 'a') {
avatar = team.fb[0].avatar
} else {
avatar = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-avatar-default.png'
}
return (
<Grid item xs={12}>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image={avatar}
title={team.name}
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant='headline' component='h2'>
{team.fb[0].name}
</Typography>
<Typography variant='caption' color='textSecondary'>
{team.fb[0].kind}
</Typography>
</CardContent>
<CardActions>
<ManageFBEdit slug={team.slug} fb={team.fb[0]} authId={authId} />
</CardActions>
</div>
</Card>
</Grid>
)
}
ManageFBItem.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired,
authId: PropTypes.string.isRequired
}
export default ManageFBItem
@@ -0,0 +1,150 @@
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 { MessageCard } from '@advancedalgos/web-components'
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 ManageFBItem from './ManageFBItem'
import CreateTeamDialog from './CreateTeamDialog'
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 ManageFBList = ({ 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}
variables={{ authId }}
fetchPolicy='network-only'
>
{({ 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 => (
<ManageFBItem
key={team.id}
team={team}
classes={classes}
authId={authId}
/>
))}
{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...' />
{errors !== null && <MessageCard message={errors} />}
</Grid>
</Grid>
)
}
}}
</Query>
)
}
}
ManageFBList.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 ManageFBListAuthId = compose(
mapStateToProps,
getUserOnMount
)(ManageFBList)
export default withStyles(styles)(ManageFBListAuthId)
@@ -0,0 +1,152 @@
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 PersonAddDisabledIcon from '@material-ui/icons/PersonAddDisabled'
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 REMOVE_TEAM_MEMBER from '../../../graphql/teams/RemoveTeamMemberMutation'
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 ManageMemberDelete 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
}
}
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.teamId)
const { classes, teamId, authId } = this.props
return (
<Mutation
mutation={REMOVE_TEAM_MEMBER}
refetchQueries={[{ query: GET_TEAMS_BY_OWNER }]}
>
{(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 (
<React.Fragment>
<Button
size='small'
color='primary'
className={classes.buttonRight}
onClick={this.handleClickOpen}
>
<PersonAddDisabledIcon /> Remove
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={classes.dialogContainer}>
<DialogTitle id='form-dialog-title'>
Remove Team Member
</DialogTitle>
<DialogContent>
<Typography variant='subheading' color='primary'>
DANGER - Removing your team cannot be undone
</Typography>
<Typography variant='subheading'>
Are you sure you want to remove this team member?
</Typography>
{loader}
{errors}
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button
onClick={e => {
this.handleSubmit(
e,
deleteTeam,
teamId,
authId
)
}}
color='primary'
>
Remove Member
</Button>
</DialogActions>
</div>
</Dialog>
</React.Fragment>
)
}}
</Mutation>
)
}
async handleSubmit (e, deleteTeam, teamId, memberId) {
e.preventDefault()
await deleteTeam({ variables: { teamId, memberId } })
this.setState({ open: false })
}
}
ManageMemberDelete.propTypes = {
classes: PropTypes.object.isRequired,
teamId: PropTypes.string.isRequired,
authId: PropTypes.string
}
export default withStyles(styles)(ManageMemberDelete)
@@ -0,0 +1,169 @@
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 AssignmentIndIcon from '@material-ui/icons/AssignmentInd'
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 TextField from '@material-ui/core/TextField'
import MenuItem from '@material-ui/core/MenuItem'
import SET_TEAM_MEMBER_ROLE from '../../../graphql/teams/SetTeamMemberRoleMutation'
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'
}
})
const roles = [
{ role: 'Admin' },
{ role: 'Member' }
]
export class ManageMemberRole 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,
role: ''
}
}
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.teamId)
const { classes, teamId, authId } = this.props
return (
<Mutation
mutation={SET_TEAM_MEMBER_ROLE}
refetchQueries={[{ query: GET_TEAMS_BY_OWNER }]}
>
{(setTeamMemberRole, { 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 (
<React.Fragment>
<Button
size='small'
color='primary'
className={classes.buttonRight}
onClick={this.handleClickOpen}
>
<AssignmentIndIcon /> Set Role
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={classes.dialogContainer}>
<DialogTitle id='form-dialog-title'>
Set Team Member Role
</DialogTitle>
<DialogContent>
<Typography variant='subheading'>
Admin members can approve new team members, send team invitations, and remove team members.
</Typography>
<TextField
select
label='Team Role'
value={this.state.role}
onChange={(e) => this.setState({ role: e.target.value })}
fullWidth
>
{roles.map(role => (
<MenuItem key={role.role} value={role.role}>{role.role}</MenuItem>
))}
</TextField>
{loader}
{errors}
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button
onClick={e => {
this.handleSubmit(
e,
setTeamMemberRole,
teamId,
authId
)
}}
color='primary'
>
Set Member Role
</Button>
</DialogActions>
</div>
</Dialog>
</React.Fragment>
)
}}
</Mutation>
)
}
async handleSubmit (e, setTeamMemberRole, teamId, authId) {
e.preventDefault()
const role = this.state.role
await setTeamMemberRole({ variables: { teamId, memberId: authId, role: role } })
this.setState({ open: false })
}
}
ManageMemberRole.propTypes = {
classes: PropTypes.object.isRequired,
teamId: PropTypes.string.isRequired,
authId: PropTypes.string.isRequired
}
export default withStyles(styles)(ManageMemberRole)
@@ -0,0 +1,99 @@
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 HistoryIcon from '@material-ui/icons/History'
import Dialog from '@material-ui/core/Dialog'
import Typography from '@material-ui/core/Typography'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
import CardActions from '@material-ui/core/CardActions'
const styles = theme => ({
dialogContainer: {
display: 'block',
margin: '3em',
minWidth: 400
},
buttonRight: {
justifyContent: 'flex-end'
}
})
export class ManageMemberStatus extends Component {
constructor (props) {
super(props)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.state = {
open: false,
role: ''
}
}
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.status)
const { classes, status } = this.props
return (
<div>
<Button
size='small'
color='primary'
className={this.props.classes.buttonRight}
onClick={this.handleClickOpen}
>
<HistoryIcon /> Status Details
</Button>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={classes.dialogContainer}>
<Card className={classes.card}>
<CardContent>
<Typography variant='title' id='form-dialog-title'>
Member Status Details
</Typography>
{status !== null && status.map(stat => {
return (
<Typography variant='title' id='form-dialog-title' key={stat.createdAt}>
{stat.status} | {stat.reason} | {stat.createdAt}
</Typography>
)
})}
<CardActions>
<Button onClick={this.handleClose} color='primary'>
Close
</Button>
</CardActions>
</CardContent>
</Card>
</div>
</Dialog>
</div>
)
}
}
ManageMemberStatus.propTypes = {
classes: PropTypes.object.isRequired,
status: PropTypes.array
}
export default withStyles(styles)(ManageMemberStatus)
@@ -0,0 +1,64 @@
import React from 'react'
import PropTypes from 'prop-types'
import Grid from '@material-ui/core/Grid'
import Typography from '@material-ui/core/Typography'
import ManageMemberDelete from './ManageMemberDelete'
import ManageMemberRole from './ManageMemberRole'
import ManageMemberStatus from './ManageMemberStatus'
import { MessageCard } from '@advancedalgos/web-components'
export const ManageMembersItem = ({ classes, member, teamId, authId }) => {
console.log('ManageMembersItem', member)
let profile = null
let email = null
let memberStatus = null
if (member.member !== null) {
profile = member.member
}
if (member.member === null && member.email !== null) {
email = member.email
}
if (member.status.length > 0) memberStatus = member.status
if (profile !== null || email !== null) {
return (
<Grid container className={classes.memberContainer}>
<Grid item xs={4}>
<Typography gutterBottom variant='subheading' component='h2' align='left' >
{profile !== null ? profile.alias : email}
</Typography>
</Grid>
<Grid item xs={4}>
<Typography color='textSecondary' variant='body2' component='h4' align='left' >
{email !== null ? `${memberStatus[memberStatus.length - 1].status} | Sent: ${memberStatus[memberStatus.length - 1].createdAt}` : `${member.role} | Member since: `}
</Typography>
</Grid>
<Grid item xs={4}>
{ profile !== null && member.role !== 'OWNER' && (
<ManageMemberRole teamId={teamId} authId={profile !== null ? profile.authId : null} className={classes.buttonRight} />
)}
{(email !== null || member.role !== 'OWNER') && (
<ManageMemberStatus status={member.status} className={classes.buttonRight} />
)}
{(email !== null || member.role !== 'OWNER') && (
<ManageMemberDelete teamId={teamId} authId={profile !== null ? profile.authId : null} email={email !== null ? email : null} className={classes.buttonRight} />
)}
</Grid>
</Grid>
)
} else {
return (
<MessageCard message='No Member info found' />
)
}
}
ManageMembersItem.propTypes = {
classes: PropTypes.object.isRequired,
member: PropTypes.object.isRequired,
teamId: PropTypes.string.isRequired,
authId: PropTypes.string.isRequired
}
export default ManageMembersItem
@@ -0,0 +1,116 @@
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 CardMedia from '@material-ui/core/CardMedia'
import Typography from '@material-ui/core/Typography'
import InviteMemberDialog from './InviteMemberDialog'
import ManageMembersItem from './ManageMembersItem'
import { MessageCard } from '@advancedalgos/web-components'
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'
},
cardMemberMedia: {
flex: 1,
width: 36,
height: 36,
maxWidth: 36,
justifyContent: 'flex-start'
},
memberContainer: {
margin: 10,
padding: 6
}
})
export const ManageMembersTeamItem = ({ classes, team, authId }) => {
console.log('ManageMembersItem', team, team.profile, team.profile.avatar)
let avatar
if (team.profile.avatar !== undefined && team.profile.avatar !== 'a') {
avatar = team.profile.avatar
} else {
avatar = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-avatar-default.png'
}
if (team.members.length > 0) {
return (
<React.Fragment>
<Grid item xs={12} key={team.id}>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardContent className={classes.cardContent}>
<Grid container>
<Grid item xs={2}>
<CardMedia
className={classes.cardMedia}
image={avatar}
title={team.name}
/>
</Grid>
<Grid item xs={9}>
<Typography gutterBottom variant='headline' component='h2'>
{team.name}
</Typography>
</Grid>
<Grid item xs={1}>
<InviteMemberDialog teamId={team.id} />
</Grid>
</Grid>
{team.members.length > 0 && team.members.map((member, index) => (
<ManageMembersItem
member={member}
teamId={team.id}
authId={authId}
classes={classes}
key={`${team.id}-${index}`}
/>
))}
</CardContent>
</div>
</Card>
</Grid>
</React.Fragment>
)
} else {
return <MessageCard message='No Member info found' />
}
}
ManageMembersTeamItem.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired,
authId: PropTypes.string.isRequired
}
export default withStyles(styles)(ManageMembersTeamItem)
@@ -123,8 +123,7 @@ export class ManageTeamDelete extends Component {
this.handleSubmit(
e,
deleteTeam,
this.props.slug,
authId
this.props.slug
)
}}
color='primary'
@@ -141,9 +140,9 @@ export class ManageTeamDelete extends Component {
)
}
async handleSubmit (e, deleteTeam, slug, authId) {
async handleSubmit (e, deleteTeam, slug) {
e.preventDefault()
await deleteTeam({ variables: { slug, owner: authId } })
await deleteTeam({ variables: { slug } })
this.setState({ open: false })
}
}
@@ -67,15 +67,15 @@ export class ManageTeamDetails extends Component {
const { name, members, profile, createdAt } = this.props.team
let avatar
let banner
if (profile.avatar !== undefined && profile.avatar !== 'a') {
if (profile.avatar !== undefined && profile.avatar !== null) {
avatar = profile.avatar
} else {
avatar = '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%3EAvatar%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'
avatar = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-avatar-default.png'
}
if (profile.banner !== undefined && profile.banner !== 'a') {
if (profile.banner !== undefined && profile.banner !== null) {
banner = profile.banner
} else {
banner = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22800%22%20height%3D%22200%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Asvg%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23banner_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%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%3E%3Ctitle%3EBanner%20Placeholder%3C%2Ftitle%3E%3Crect%20stroke%3D%22null%22%20id%3D%22svg_1%22%20fill%3D%22%2355595c%22%20height%3D%22200%22%20width%3D%22800%22%2F%3E%3Cg%20stroke%3D%22null%22%20id%3D%22banner_164edaf95ee%22%3E%3Ctext%20stroke%3D%22null%22%20id%3D%22svg_3%22%20y%3D%22106.5%22%20x%3D%22367.73438%22%3EBanner%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'
banner = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-banner-default.png'
}
return (
<div>
@@ -127,10 +127,11 @@ export class ManageTeamEdit extends Component {
let avatar = null
if (this.state.avatar !== null) avatar = this.state.avatar
if (team.profile !== null && team.profile.avatar !== undefined && team.profile.avatar !== null) avatar = team.profile.avatar
if (this.state.avatar === null && team.profile !== null && team.profile.avatar === undefined) avatar = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-avatar-default.png'
let banner = null
if (this.state.banner !== null) banner = this.state.banner
if (team.profile !== null && team.profile.banner !== undefined && team.profile.banner !== null) banner = team.profile.banner
if (this.state.banner === null && team.profile !== null && team.profile.banner === undefined) banner = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-banner-default.png'
console.log('team images: ', avatar, banner)
if (loading || data === undefined) {
@@ -0,0 +1,142 @@
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 ManageMembersTeamItem from './ManageMembersTeamItem'
import { MessageCard } from '@advancedalgos/web-components'
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 ManageTeamMembersList = ({ 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}
variables={{ authId }}
fetchPolicy='network-only'
>
{({ 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 => (
<ManageMembersTeamItem key={team.id} team={team} authId={authId} />
))
}
{errors}
</Grid>
</React.Fragment>
)
} else {
return (
<Grid container spacing={40}>
<Grid item xs={10}>
<MessageCard message='You don&rsquo;t have any members. Somethings not right...' />
</Grid>
</Grid>
)
}
} else {
return (
<Grid container spacing={40}>
<Grid item xs={12}>
<MessageCard message='Loading...' />
{errors !== null && <MessageCard message={errors} />}
</Grid>
</Grid>
)
}
}}
</Query>
)
}
}
ManageTeamMembersList.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 ManageTeamMembersListAuthId = compose(
mapStateToProps,
getUserOnMount
)(ManageTeamMembersList)
export default withStyles(styles)(ManageTeamMembersListAuthId)
@@ -18,7 +18,7 @@ export const ManageTeamsItem = ({ classes, team, authId }) => {
if (team.profile.avatar !== undefined && team.profile.avatar !== 'a') {
avatar = team.profile.avatar
} else {
avatar = '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'
avatar = 'https://algobotcommstorage.blob.core.windows.net/aateammodule/aa-avatar-default.png'
}
return (
<Grid item xs={12}>
@@ -39,7 +39,7 @@ export const ManageTeamsItem = ({ classes, team, authId }) => {
</CardContent>
<CardActions>
<ManageTeamDetails team={team} />
<ManageTeamEdit slug={team.slug} authId={authId} team={team} />
<ManageTeamEdit slug={team.slug} team={team} authId={authId} />
<ManageTeamDelete slug={team.slug} authId={authId} />
</CardActions>
</div>
@@ -68,8 +68,8 @@ export const ManageTeamsList = ({ classes, user = null }) => {
return (
<Query
query={GET_TEAMS_BY_OWNER}
fetchPolicy='network-only'
variables={{ authId }}
fetchPolicy='network-only'
>
{({ loading, error, data }) => {
console.log('GET_TEAMS_BY_OWNER: ', loading, error, data)
@@ -94,9 +94,6 @@ export const ManageTeamsList = ({ classes, user = null }) => {
authId={authId}
/>
))}
<MessageCard message='Create another team'>
<CreateTeamDialog authId={authId} />
</MessageCard>
{errors}
</Grid>
</React.Fragment>
+2
View File
@@ -5,6 +5,7 @@ import Home from './home'
import About from './about'
import Dashboard from './dashboard'
import Teams from './teams'
import AcceptTeamInvite from './teams/AcceptTeamInvite'
import { theme, globalStyles } from './theme'
@@ -12,6 +13,7 @@ export {
NavBar,
Footer,
Callback,
AcceptTeamInvite,
Home,
About,
Dashboard,
@@ -0,0 +1,31 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import queryString from 'query-string'
import { MessageCard } from '@advancedalgos/web-components'
class AcceptTeamInvite extends Component {
render () {
const { location, auth } = this.props
const param = queryString.parse(location.search)
console.log(param, auth)
if (auth === undefined) {
return <MessageCard message='Loading...' />
}
auth.loginInvite(param.token)
return (
<MessageCard message='Verifying Invitation' />
)
}
}
AcceptTeamInvite.propTypes = {
location: PropTypes.object.isRequired,
auth: PropTypes.object
}
export default AcceptTeamInvite
@@ -12,6 +12,7 @@ const Dotenv = require('dotenv-webpack')
module.exports = merge(commonConfig, {
mode: 'production',
devtool: 'inline-sourcemap',
output: {
path: path.resolve(__dirname, '../../build'),
filename: 'script.js'