Merge branch 'master' into feature/member-mangement

This commit is contained in:
Barry Low committed 2018-10-06 00:39:15 -07:00
commit 4fe572411e
44 files changed
+1307 -919

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
# Contributing
We are thrilled you are interested in contributing to the this module. Your help may be essential to the project's success and we want you in!
Advanced Algos LTD is a startup, currently at a very early stage. Although this module is not open source software yet, we do welcome contributions from the community.
Contributions are deemed _work for hire_ and --as such-- are compensated on a case by case basis, with ALGO tokens. Contributors agree to provide an express grant of patent rights for their contributions. Our Balanced Contributor Intellectual Property Agreement is a direct implementation of GitHub's [Balanced Employee Intellectual Property Agreement 1.0.1](https://github.com/github/balanced-employee-ip-agreement/blob/master/Balanced_Employee_IP_Agreement.md).
# Balanced Contributor Intellectual Property Agreement 1.0.1
This BALANCED CONTRIBUTOR INTELLECTUAL PROPERTY AGREEMENT is between person named below and Advanced Algos ("Company").
**What is this?** This is the Company's Intellectual Property Agreement ("Agreement"). If you've worked in the technology space before, there's a good chance that you've run across one or more of these in the past. This document is the official, entire and exclusive agreement on what intellectual property ("IP") is yours, and what belongs to the Company.
**Why is this?** The Company needs to be clear on what IP it owns and has rights to. Its customers, contributors, and investors depend on the Company having the legal rights to the products and services it is providing so that the Company can continue operating and doing business.
The Company also believes that it's important to be clear on what it doesn't own. The Company doesn't want you looking over your shoulder every time you work on something personal or worrying that the Company will someday seize your open source non-lethal mousetrap simulation software. In other words, the Company isn't interested in appropriating your personal projects.
**Read this.** Please read this document and be sure you understand it before you sign it. Due to legal risk and corporate obligations, the Company cannot, by and large, negotiate its terms. If you feel you have a particular circumstance that keeps you from signing, please let the Company's legal department ("Legal") know. And, of course, you're always free and encouraged to get your own legal counsel to explain anything you're not clear on.
Cool? Then, by signing this Agreement, and as a condition of your contribution, you agree to the following:
1. **What the Company owns.** The Company owns any IP ("Company IP") that you create, or help create _as its contributor or contractor_, but only if it meets one or more of these additional conditions: The IP was (i) related to an existing or prospective Company product or service at the time you developed, invented, or created it, (ii) developed for use by the Company, or (iii) developed or promoted with existing Company IP or with the Company's endorsement. You hereby grant and assign, and will grant and assign, to the Company all rights and interests in all Company IP.
"Company IP" includes concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, and works of authorship.
2. **What the Company doesn't own.** If you create IP that doesn't fit into one of the categories listed above, the Company doesn't own it. In other words, the Company doesn't own concepts, designs, developments, discoveries, ideas, improvements, inventions, trade secrets, trademarks, or works of authorship that you develop, invent, or create that do not meet any of the conditions in Section 1. This is true regardless of the computer you use to develop your IP, including the one furnished to you by the Company.
3. **Contribution of your IP to Company projects.** If you include your own IP – such as IP you created prior to working for the Company – into a Company product or service, it's still yours, of course, but you grant the Company a non-exclusive, irrevocable, fully paid-up, royalty-free, perpetual, sublicensable, transferable, worldwide license to use it without restriction in any way or implementation, in modified form, or as is, by itself, or incorporated into another product or service ("License"). If you include your name in any project over which the Company would have rights under this Agreement, such as in a copyright notice or a comment in code or documentation, then the License covers the rights associated with that use of your name as well.
4. **Your contributions to non-Company projects.** The Company recognizes that you may be engaged in work that requires you to submit IP to entities other than the Company, such as open source projects used by the Company. Please make sure that Legal is aware of what you're working on so that Legal can help with any licensing issues. If any project asks you to sign a contribution agreement, you should check with Legal before doing so. These agreements may be legally binding on the Company and so should be run through Legal first.
5. **Works made for hire.** All Company IP that you are involved with or create as part of your work for the Company is [work made for hire](http://www.copyright.gov/circs/circ09.pdf) under copyright law and compensated by your regular salary or pay.
6. **No conflicts.** You agree that you don't have any outstanding agreements or obligations that conflict with those in this Agreement, and that you won't enter into conflicting agreements in the future.
7. **IP protection.** The Company might someday need to show the work that went into the development of IP that it uses or has used, or to make government filings to establish that it owns the IP or has rights over it. To help in those situations, you agree not to destroy any records you maintain relating to the development of any Company IP or IP under License (together, "All IP"), and, if the Company asks, to provide those records to the Company. You agree to help the Company secure and defend its rights in All IP, including after you leave the Company. For your help the Company will compensate you at a reasonable rate. If the Company is unable to secure your help, you authorize the Company to act on your behalf (as your agent and attorney-in-fact) in securing all rights related to All IP.
8. **Confidentiality.** As an contributor or contractor of the Company, you will have access to sensitive confidential information that is important to the Company's business. You are responsible for keeping this information confidential, including after you end your work for the Company.
"Confidential Information" includes non-public technical details about our products or services, financial information, business strategies and forecasts, customer data, or any other information, data or know-how that is valuable to the Company because it is not publicly known.
The Company's Confidential Information is sensitive and we expect that you'll treat it as such. You agree to only use the Company's Confidential Information for the purpose of performing your job and for the benefit of the Company. You will do your best to keep Confidential Information secret. If you are not sure if something is Confidential Information, you should assume that it is, until you can confirm otherwise.
Print Name: _____________
Sign: ________________
Date: ________________
+42
View File
@@ -0,0 +1,42 @@
COPYRIGHT NOTICE
© Copyright 2018 Advanced Algos LTD.
LICENSE NOTICE
Advanced Algos LTD. is a startup, currently at a very early stage. Because certain aspects of
the business are still not clearly defined, we have chosen to --temporarily-- grant a
restrictive license for the use of this module, until those business aspects are clarified
and a proper open source license can be granted.
Advanced Algos LTD. is publishing the code of this software for several reasons. However,
it shall not be considered free software yet. Advanced Algos LTD. is a for-profit company
in the crypto space and this module is still proprietary software.
The reasons for opening up the code of this module at this stage are the following:
* We believe that transparency is paramount to achieving our goals, so we want everyone
to be able to understand what this module does and how. Opening up the code of this module allows
anyone to take a close look.
* We welcome contributions from the community. Contributions are compensated on a case
by case basis with ALGO tokens. Contributions are deemed _work for hire_ and contributors
agree to provide an express grant of patent rights for their contributions.
LICENSE WORDING
Advanced Algos LTD. grants a limited license for the following uses:
1. CODE REVIEW AND AUDITS. You are granted permission to clone the AACoud repository to analyze,
review or audit the code.
2. COLLABORATING WITH ADVANCED ALGOS LTD. You are granted permission to fork the this module repository
with the sole purpose of correcting bugs and developing functionality in agreement with
Advanced Algos LTD. Before your contributions are merged into our repository, you will be requested
to sign a Contributors Work for Hire Agreement.
PLEASE SEE CONTRIBUTING.MD FOR MORE DETAILS ON THE IMPLICATIONS SURROUNDING YOUR CONTRIBUTIONS TO THIS REPOSITORY.
+1
View File
@@ -1,4 +1,5 @@
/.env
/.env.prod
/database/.env
/database/docker-compose.prisma.prod.yml
/docker-compose.prisma.prod.yml
+3
View File
@@ -96,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 -5
View File
@@ -1,11 +1,8 @@
version: '3'
services:
api:
image: aateamapi-dev:v1
build:
context: .
dockerfile: ./Dockerfile
env_file: ./.env
image: advancedalgosteamsmodule/teammodule-api:v2
env_file: ./.env.prod
container_name: aateamapi
tty: true
stdin_open: true
+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()
}
+66 -1
View File
@@ -67,6 +67,71 @@ const sendTeamMemberInvite = function(email, team) {
return sendVerify
}
const sendTeamCreateConfirmation = function(email, teamName, botName, sessionToken) {
const dev = process.env.NODE_ENV === 'development' ? true : false
console.log('sendTeamMemberInvite', email, team)
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: {
"aateamdeveloplink": "https://develop.advancedalgos.net/index.html?" + sessionToken,
"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
@@ -84,4 +149,4 @@ const verifyInviteToken = async function(token) {
return verifiedToken
}
module.exports = { sendTeamMemberInvite, verifyInviteToken }
module.exports = { sendTeamMemberInvite, sendTeamCreateConfirmation, verifyInviteToken }
+145 -1
View File
@@ -1,5 +1,5 @@
# source: http://localhost:4466
# timestamp: Thu Sep 27 2018 23:28:16 GMT-0700 (Pacific Daylight Time)
# timestamp: Fri Oct 05 2018 13:16:40 GMT-0700 (Pacific Daylight Time)
type AggregateFBStatus {
count: Int!
@@ -249,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!
@@ -268,6 +271,9 @@ type FinancialBeingsConnection {
input FinancialBeingsCreateInput {
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
team: TeamCreateOneWithoutFbInput
status: FBStatusCreateManyInput
}
@@ -280,6 +286,9 @@ input FinancialBeingsCreateManyWithoutTeamInput {
input FinancialBeingsCreateWithoutTeamInput {
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
status: FBStatusCreateManyInput
}
@@ -299,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
@@ -309,6 +324,9 @@ type FinancialBeingsPreviousValues {
id: ID!
type: FBType
kind: FBKind
name: String!
slug: String!
avatar: String
updatedAt: DateTime!
createdAt: DateTime!
}
@@ -355,6 +373,9 @@ input FinancialBeingsSubscriptionWhereInput {
input FinancialBeingsUpdateInput {
type: FBType
kind: FBKind
name: String
slug: String
avatar: String
team: TeamUpdateOneWithoutFbInput
status: FBStatusUpdateManyInput
}
@@ -371,6 +392,9 @@ input FinancialBeingsUpdateManyWithoutTeamInput {
input FinancialBeingsUpdateWithoutTeamDataInput {
type: FBType
kind: FBKind
name: String
slug: String
avatar: String
status: FBStatusUpdateManyInput
}
@@ -454,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."""
@@ -15,6 +15,7 @@ const TEAMS_CONNECTIONS_FRAGMENT = gql`{
createdAt
profile {
avatar
banner
description
motto
updatedAt
@@ -33,6 +34,18 @@ const TEAMS_CONNECTIONS_FRAGMENT = gql`{
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
}
}`
+12
View File
@@ -31,6 +31,18 @@ const TEAMS_FRAGMENT = gql`{
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}`
module.exports = TEAMS_FRAGMENT
+69 -86
View File
@@ -8,34 +8,20 @@ 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')
const { validateIdToken } = require('./auth/validateIdToken')
const { directiveResolvers } = require('./auth/authDirectives')
const { sendTeamMemberInvite, verifyInviteToken } = require('./email/sendgrid')
const {
createStoragePipline,
createServiceURL,
listContainers,
createContainer,
createBlob,
uploadBlob,
downloadBlob,
deleteBlob,
deleteContainer
} = require('./storage/azure')
//var azure = require('azure-storage')
const Azure = require("@azure/storage-blob")
const { createSASQueryURL } = require('./storage/azure')
const azureAccount = process.env.AZURE_STORAGE_ACCOUNT
const azureKey = process.env.AZURE_STORAGE_ACCESS_KEY
const azureStorageUrl = process.env.AZURE_STORAGE_URL
var debug = require('debug')('http')
, http = require('http')
, name = 'My App';
const { sendTeamMemberInvite, sendTeamCreateConfirmation, verifyInviteToken } = require('./email/sendgrid')
const TEAMS_FRAGMENT = require('./graphql/TeamsFragment')
const TEAMS_CONNECTIONS_FRAGMENT = require('./graphql/TeamsConnectionsFragment')
@@ -93,9 +79,8 @@ const resolvers = {
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, args, ctx, info) {
const authId = ctx.request.user.sub
return ctx.db.query.teams({where: { owner: authId }, orderBy:'updatedAt_DESC'}, TEAMS_FRAGMENT)
async teamsByOwner(parent, { ownerId }, ctx, info) {
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
@@ -136,6 +121,16 @@ const resolvers = {
}
return member
},
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
@@ -154,16 +149,52 @@ const resolvers = {
}
return team
},
async createTeam(parent, { name, slug }, ctx, info) {
async createTeam(parent, { name, slug, botName, botSlug }, ctx, info) {
console.log('createTeam ctx:', ctx.request.user)
const authId = ctx.request.user.sub
return ctx.db.mutation.createTeam({ data: { name: name, slug: slug, owner: authId, members: { create: { member:{ connect:{ authId: authId} }, role: 'OWNER' } }, profile:{create:{avatar:"a"} } } }, TEAMS_FRAGMENT)
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`)
console.log('getUserModuleUserId: ', await getUserModuleUser.data)
const alias = await getUserModuleUser.data.data.userByAuthId.alias
const email = await 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'
console.log('getUserModuleUserId: ', alias, email)
/*
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
})
*/
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)
/* update session token on user module
const updateUserSession = await axios.post('https://users-api.advancedalgos.net/graphql', {
query: 'mutation { language }'
})
*/
sendTeamCreateConfirmation(email, name, botName, sessionToken)
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, { teamId, avatar}, ctx, info) {
return ctx.db.mutation.updateTeam({data:{profile: {update: {avatar: avatar}}}, where:{slug: teamId}}, info)
.catch((err) => {
console.log('createTeam error: ', err)
return err
@@ -179,66 +210,9 @@ const resolvers = {
})
},
async getAzureSAS(parent, { teamSlug }, ctx, info) {
// Create start and expiry times
let today = new Date()
let week = new Date()
week.setDate(today.getDate() + 7)
// Create SharedKeyCredential and attach to pipline
const SKC = new Azure.SharedKeyCredential(azureAccount, azureKey)
const pipeline = Azure.StorageURL.newPipeline(SKC)
// Create container URL
const serviceURL = new Azure.ServiceURL(azureStorageUrl, pipeline)
const containerName = teamSlug
const containerURL = Azure.ContainerURL.fromServiceURL(serviceURL, containerName)
let marker
let containerCheck = null
do {
const listContainersResponse = await serviceURL.listContainersSegment(
Azure.Aborter.None,
marker,
)
console.log(`ContainerCheck marker: `, listContainersResponse)
marker = listContainersResponse.marker;
for (const container of listContainersResponse.containerItems) {
console.log(`ContainerCheck: ${container.name} | ${containerName} | ${marker}`)
if(container.name === containerName){
containerCheck = container.name
}
}
} while (marker)
let newContainer
if(containerCheck === null){
newContainer = await containerURL.create(Azure.Aborter.None, { access: 'blob' })
}
console.log('getAzureSAS container: ', containerURL, containerCheck, newContainer)
// Set permissions for service, resource types and containers
const SASServicePerms = Azure.AccountSASServices.parse('b')
const SASResourceTypes = Azure.AccountSASResourceTypes.parse('co')
const SASContainerPerms = Azure.ContainerSASPermissions.parse('rwl')
// Generate SAS url
const SASQueryParameters = Azure.generateAccountSASQueryParameters(
{
version: '2017-11-09',
permissions: SASContainerPerms,
startTime: today,
expiryTime: week,
protocol: 'https',
services: SASServicePerms,
resourceTypes: SASResourceTypes
}, SKC )
return SASQueryParameters
},
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
const SASUrl = createSASQueryURL(teamSlug)
console.log('createSASQueryURL: ', SASUrl)
return SASUrl
}
}
}
@@ -283,13 +257,22 @@ server.express.use(
server.express.post(server.options.endpoint, (req, res, done) => getUser(req, res, done, db))
const whitelist = [
let whitelist = [
'http://localhost:3000',
'http://localhost:3001',
'http://localhost:3002',
'http://localhost:4000',
'http://localhost:4002'
'http://localhost:4002',
'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'
]
const corsOptionsDelegate = (req, callback) => {
var corsOptions
if (whitelist.indexOf(req.header('Origin')) !== -1) {
+3 -2
View File
@@ -11,7 +11,7 @@ type Query {
teamByName(name: String!): Team
teamById(teamId: String!): Team
teamWithRole(teamId: String!, role: String!): Team
teamsByOwner: [Team!]! @isAuthenticated
teamsByOwner(ownerId: String!): [Team!]! @isAuthenticated
teamsByRole: [Team!]! @isAuthenticated
owner: Member @isAuthenticated
currentMember: Member @isAuthenticated
@@ -21,8 +21,9 @@ type Query {
type Mutation {
authenticate(idToken: String!): Member
verifyTeamInvite(token: String!): TeamMembers
createTeam(name: String!, slug: String!): Team @isAuthenticated
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(teamId: String!, avatar: String): FinancialBeings @isAuthenticated
deleteTeam(slug: String!): Team @isAuthenticated @isOwner(type: "Team")
getAzureSAS(teamSlug: String!):String
sendMemberInviteSG(email: String!, teamId: String!): String
+61 -3
View File
@@ -1,10 +1,13 @@
const Azure = require('@azure/storage-blob')
const Azure = require("@azure/storage-blob")
const azureAccount = process.env.AZURE_STORAGE_ACCOUNT
const azureKey = process.env.AZURE_STORAGE_ACCESS_KEY
const azureStorageUrl = process.env.AZURE_STORAGE_URL
/* Create Azure Storage pipeline
* @accountName: Azure Blob Storage account name (required) - Environment variable
* @accountKey: Azure Blob Storage storage key (required) - Environment variable
*/
const createStoragePipline = async (accountNAme, accountKey) => {
const createStoragePipline = async (accountName, accountKey) => {
return Azure.StorageURL.newPipeline(
new Azure.SharedKeyCredential(accountName, accountKey)
)
@@ -120,6 +123,60 @@ const deleteContainer = async (containerURL) => {
return true
}
const createSASQueryURL = async (containerName) => {
let today = new Date()
let week = new Date()
week.setDate(today.getDate() + 7)
// Create SharedKeyCredential and attach to pipline
const SKC = new Azure.SharedKeyCredential(azureAccount, azureKey)
const pipeline = Azure.StorageURL.newPipeline(SKC)
// Create container URL
const serviceURL = new Azure.ServiceURL(azureStorageUrl, pipeline)
const containerURL = Azure.ContainerURL.fromServiceURL(serviceURL, containerName)
//list container and check if already exists.
let marker
let containerCheck = null
do {
const listContainersResponse = await serviceURL.listContainersSegment(
Azure.Aborter.None,
marker,
)
// console.log(`ContainerCheck marker: `, listContainersResponse)
marker = listContainersResponse.marker;
for (const container of listContainersResponse.containerItems) {
// console.log(`ContainerCheck: ${container.name} | ${containerName} | ${marker}`)
if(container.name === containerName){
containerCheck = container.name
}
}
} while (marker)
// if container doesn't exist, create one
let newContainer
if(containerCheck === null){
newContainer = await containerURL.create(Azure.Aborter.None, { access: 'blob' })
}
console.log('createSASQueryURL container: ', containerURL, containerCheck, newContainer)
// Set permissions for service, resource types and containers
const SASContainerPerms = await Azure.AccountSASPermissions.parse('rwlacu')
const SASServicePerms = await Azure.AccountSASServices.parse('b')
const SASResourceTypes = await Azure.AccountSASResourceTypes.parse('co')
console.log('createSASQueryURL permissions: ', SASServicePerms, SASResourceTypes, SASContainerPerms)
// Generate SAS url
const SASQueryParameters = await Azure.generateBlobSASQueryParameters(
{
version: '2017-11-09',
protocol: 'https,http',
expiryTime: week,
permissions: 'rwlac',
containerName: containerName
}, SKC )
console.log('createSASQueryURL SASQueryParameters: ', containerURL.url, SASQueryParameters)
return SASQueryParameters
}
module.exports = {
createStoragePipline,
createServiceURL,
@@ -129,5 +186,6 @@ module.exports = {
uploadBlob,
downloadBlob,
deleteBlob,
deleteContainer
deleteContainer,
createSASQueryURL
}
+1
View File
@@ -56,6 +56,7 @@ typings/
# dotenv environment variables file
.env
.env.prod
# Build folder js files
build/
+311 -208
View File
@@ -4,6 +4,133 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@advancedalgos/web-components": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@advancedalgos/web-components/-/web-components-0.0.5.tgz",
"integrity": "sha512-mgaS2AwtVcbP0W8/Rk95TNFTrDmp21rDmoqrO177WGLCcTH5n3rHisQtrdVaEvLiUGoZ/8us/GeclljVJP1edw==",
"requires": {
"@azure/storage-blob": "^10.1.0-preview",
"@material-ui/core": "^3.0.3",
"@material-ui/icons": "^3.0.1",
"apollo-client": "^2.4.2",
"blob-util": "^2.0.2",
"browser-image-resizer": "^1.2.0",
"graphql": "^14.0.2",
"graphql-tag": "^2.9.2",
"prop-types": "^15.6.2",
"react-apollo": "^2.1.11",
"react-dropzone": "^5.1.0",
"react-image-crop": "^5.0.0",
"react-image-cropper": "^1.3.0"
},
"dependencies": {
"@azure/storage-blob": {
"version": "10.1.0-preview",
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-10.1.0-preview.tgz",
"integrity": "sha512-vKY4dF8VCRCOOQ03Z1ResyjZbWg4jPOnQtRtlSEKE6a7WwFE3EuIbhE7OQfEihuNYyE+XWLkTjrHxeHDFx1hFg==",
"requires": {
"events": "3.0.0",
"ms-rest-js": "0.22.425",
"tslib": "^1.9.3"
}
},
"@types/node": {
"version": "9.6.34",
"resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.34.tgz",
"integrity": "sha512-PzJpSs2afoYqBA4yLBgaKUdZRk8+1yvkxcUBW6958h4vYOC+pc4k4C+QmQ6AO5Pt7uA4EIIboFog6YNCuITD0g=="
},
"apollo-cache": {
"version": "1.1.17",
"resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.1.17.tgz",
"integrity": "sha512-7sp24n2HZO4vXgTaKNomLyIfGxG4gDdDkBB0jkRzRi7HhnKmfwhiF/RCiKNbgLdrPX151INdls0KwIVliD0dHQ==",
"requires": {
"apollo-utilities": "^1.0.21"
}
},
"apollo-client": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.4.2.tgz",
"integrity": "sha512-g1z23umaVSoKLj9xNl0aAnk2KBF4JeBi7MeKFc9CGTixH7TkqeQUQtxcjrC7j2h4KmDbuhOAHOFUGf8YshN+ag==",
"requires": {
"@types/async": "2.0.49",
"@types/zen-observable": "^0.8.0",
"apollo-cache": "1.1.17",
"apollo-link": "^1.0.0",
"apollo-link-dedup": "^1.0.0",
"apollo-utilities": "1.0.21",
"symbol-observable": "^1.0.2",
"zen-observable": "^0.8.0"
}
},
"apollo-utilities": {
"version": "1.0.21",
"resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.21.tgz",
"integrity": "sha512-ZcxELlEl+sDCYBgEMdNXJAsZtRVm8wk4HIA58bMsqYfd1DSAJQEtZ93F0GZgYNAGy3QyaoBeZtbb0/01++G8JQ==",
"requires": {
"fast-json-stable-stringify": "^2.0.0",
"fclone": "^1.0.11"
}
},
"events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
"integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA=="
},
"graphql": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz",
"integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==",
"requires": {
"iterall": "^1.2.2"
}
},
"ms-rest-js": {
"version": "0.22.425",
"resolved": "https://registry.npmjs.org/ms-rest-js/-/ms-rest-js-0.22.425.tgz",
"integrity": "sha512-PM9l0Si+T3kFxce8hwB/LlCxPJSHwSlFUSpjdJHzPvF+2w8lw7huE/FCPnRfdaUm7IikQESkJhydnbqX6g2OuQ==",
"requires": {
"@types/express": "^4.11.1",
"@types/form-data": "^2.2.1",
"@types/node": "^9.4.6",
"@types/uuid": "^3.4.3",
"axios": "^0.18.0",
"form-data": "^2.3.2",
"tough-cookie": "^2.4.3",
"tslib": "^1.9.2",
"uuid": "^3.2.1",
"xml2js": "^0.4.19"
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
}
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
}
}
},
"@azure/storage-blob": {
"version": "10.0.0-preview",
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-10.0.0-preview.tgz",
@@ -22,18 +149,18 @@
}
},
"@babel/core": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.0.1.tgz",
"integrity": "sha512-7Yy2vRB6KYbhWeIrrwJmKv9UwDxokmlo43wi6AV84oNs4Gi71NTNGh3YxY/hK3+CxuSc6wcKSl25F2tQOhm1GQ==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.1.2.tgz",
"integrity": "sha512-IFeSSnjXdhDaoysIlev//UzHZbdEmm7D0EIH2qtse9xK7mXEZQpYjs2P00XlP1qYsYvid79p+Zgg6tz1mp6iVw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/generator": "^7.0.0",
"@babel/helpers": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/template": "^7.0.0",
"@babel/traverse": "^7.0.0",
"@babel/types": "^7.0.0",
"@babel/generator": "^7.1.2",
"@babel/helpers": "^7.1.2",
"@babel/parser": "^7.1.2",
"@babel/template": "^7.1.2",
"@babel/traverse": "^7.1.0",
"@babel/types": "^7.1.2",
"convert-source-map": "^1.1.0",
"debug": "^3.1.0",
"json5": "^0.5.0",
@@ -43,22 +170,13 @@
"source-map": "^0.5.0"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/generator": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0.tgz",
"integrity": "sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.1.2.tgz",
"integrity": "sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0",
"@babel/types": "^7.1.2",
"jsesc": "^2.5.1",
"lodash": "^4.17.10",
"source-map": "^0.5.0",
@@ -66,67 +184,44 @@
}
},
"@babel/helper-function-name": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0.tgz",
"integrity": "sha512-Zo+LGvfYp4rMtz84BLF3bavFTdf8y4rJtMPTe2J+rxYmnDOIeH8le++VFI/pRJU+rQhjqiXxE4LMaIau28Tv1Q==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
"integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
"dev": true,
"requires": {
"@babel/helper-get-function-arity": "^7.0.0",
"@babel/template": "^7.0.0",
"@babel/template": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"@babel/helper-get-function-arity": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
"integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
},
"@babel/helper-split-export-declaration": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz",
"integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
}
"@babel/parser": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.2.tgz",
"integrity": "sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==",
"dev": true
},
"@babel/template": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0.tgz",
"integrity": "sha512-VLQZik/G5mjYJ6u19U3W2u7eM+rA/NGzH+GtHDFFkLTKLW66OasFrxZ/yK7hkyQcswrmvugFyZpDFRW0DjcjCw==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz",
"integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/types": "^7.0.0"
"@babel/parser": "^7.1.2",
"@babel/types": "^7.1.2"
}
},
"@babel/traverse": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0.tgz",
"integrity": "sha512-ka/lwaonJZTlJyn97C4g5FYjPOx+Oxd3ab05hbDr1Mx9aP1FclJ+SUHyLx3Tx40sGmOVJApDxE6puJhd3ld2kw==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.0.tgz",
"integrity": "sha512-bwgln0FsMoxm3pLOgrrnGaXk18sSM9JNf1/nHC/FksmNGFbYnPWY4GYCfLxyP1KRmfsxqkRpfoa6xr6VuuSxdw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/generator": "^7.0.0",
"@babel/helper-function-name": "^7.0.0",
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0",
"debug": "^3.1.0",
"globals": "^11.1.0",
@@ -134,21 +229,15 @@
}
},
"@babel/types": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0.tgz",
"integrity": "sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.2.tgz",
"integrity": "sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==",
"dev": true,
"requires": {
"esutils": "^2.0.2",
"lodash": "^4.17.10",
"to-fast-properties": "^2.0.0"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
}
}
},
@@ -1183,100 +1272,55 @@
}
},
"@babel/helpers": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.0.0.tgz",
"integrity": "sha512-jbvgR8iLZPnyk6m/UqdXYsSxbVtRi7Pd3CzB4OPwPBnmhNG1DWjiiy777NTuoyIcniszK51R40L5pgfXAfHDtw==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.1.2.tgz",
"integrity": "sha512-Myc3pUE8eswD73aWcartxB16K6CGmHDv9KxOmD2CeOs/FaEAQodr3VYGmlvOmog60vNQ2w8QbatuahepZwrHiA==",
"dev": true,
"requires": {
"@babel/template": "^7.0.0",
"@babel/traverse": "^7.0.0",
"@babel/types": "^7.0.0"
"@babel/template": "^7.1.2",
"@babel/traverse": "^7.1.0",
"@babel/types": "^7.1.2"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz",
"integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==",
"dev": true,
"requires": {
"@babel/highlight": "^7.0.0"
}
},
"@babel/generator": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0.tgz",
"integrity": "sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0",
"jsesc": "^2.5.1",
"lodash": "^4.17.10",
"source-map": "^0.5.0",
"trim-right": "^1.0.1"
}
},
"@babel/helper-function-name": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0.tgz",
"integrity": "sha512-Zo+LGvfYp4rMtz84BLF3bavFTdf8y4rJtMPTe2J+rxYmnDOIeH8le++VFI/pRJU+rQhjqiXxE4LMaIau28Tv1Q==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz",
"integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==",
"dev": true,
"requires": {
"@babel/helper-get-function-arity": "^7.0.0",
"@babel/template": "^7.0.0",
"@babel/template": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"@babel/helper-get-function-arity": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz",
"integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
},
"@babel/helper-split-export-declaration": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz",
"integrity": "sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag==",
"dev": true,
"requires": {
"@babel/types": "^7.0.0"
}
},
"@babel/highlight": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz",
"integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==",
"dev": true,
"requires": {
"chalk": "^2.0.0",
"esutils": "^2.0.2",
"js-tokens": "^4.0.0"
}
"@babel/parser": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.1.2.tgz",
"integrity": "sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==",
"dev": true
},
"@babel/template": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0.tgz",
"integrity": "sha512-VLQZik/G5mjYJ6u19U3W2u7eM+rA/NGzH+GtHDFFkLTKLW66OasFrxZ/yK7hkyQcswrmvugFyZpDFRW0DjcjCw==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.2.tgz",
"integrity": "sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/types": "^7.0.0"
"@babel/parser": "^7.1.2",
"@babel/types": "^7.1.2"
}
},
"@babel/traverse": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0.tgz",
"integrity": "sha512-ka/lwaonJZTlJyn97C4g5FYjPOx+Oxd3ab05hbDr1Mx9aP1FclJ+SUHyLx3Tx40sGmOVJApDxE6puJhd3ld2kw==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.1.0.tgz",
"integrity": "sha512-bwgln0FsMoxm3pLOgrrnGaXk18sSM9JNf1/nHC/FksmNGFbYnPWY4GYCfLxyP1KRmfsxqkRpfoa6xr6VuuSxdw==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.0.0",
"@babel/generator": "^7.0.0",
"@babel/helper-function-name": "^7.0.0",
"@babel/helper-function-name": "^7.1.0",
"@babel/helper-split-export-declaration": "^7.0.0",
"@babel/parser": "^7.0.0",
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0",
"debug": "^3.1.0",
"globals": "^11.1.0",
@@ -1284,21 +1328,15 @@
}
},
"@babel/types": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0.tgz",
"integrity": "sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q==",
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.1.2.tgz",
"integrity": "sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==",
"dev": true,
"requires": {
"esutils": "^2.0.2",
"lodash": "^4.17.10",
"to-fast-properties": "^2.0.0"
}
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
}
}
},
@@ -3433,48 +3471,10 @@
}
},
"babel-core": {
"version": "6.26.3",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
"integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
"dev": true,
"requires": {
"babel-code-frame": "^6.26.0",
"babel-generator": "^6.26.0",
"babel-helpers": "^6.24.1",
"babel-messages": "^6.23.0",
"babel-register": "^6.26.0",
"babel-runtime": "^6.26.0",
"babel-template": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"convert-source-map": "^1.5.1",
"debug": "^2.6.9",
"json5": "^0.5.1",
"lodash": "^4.17.4",
"minimatch": "^3.0.4",
"path-is-absolute": "^1.0.1",
"private": "^0.1.8",
"slash": "^1.0.0",
"source-map": "^0.5.7"
},
"dependencies": {
"babylon": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
"version": "7.0.0-bridge.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
"integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==",
"dev": true
},
"babel-eslint": {
"version": "9.0.0",
@@ -3728,15 +3728,14 @@
}
},
"babel-loader": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.2.tgz",
"integrity": "sha512-Law0PGtRV1JL8Y9Wpzc0d6EE0GD7LzXWCfaeWwboUMcBWNG6gvaWTK1/+BK7a4X5EmeJiGEuDDFxUsOa8RSWCw==",
"version": "7.1.5",
"resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.5.tgz",
"integrity": "sha512-iCHfbieL5d1LfOQeeVJEUyD9rTwBcP/fcEbRCfempxTDuqrKpu0AZjLAQHEQa3Yqyj9ORKe2iHfoj4rHLf7xpw==",
"dev": true,
"requires": {
"find-cache-dir": "^1.0.0",
"loader-utils": "^1.0.2",
"mkdirp": "^0.5.1",
"util.promisify": "^1.0.0"
"mkdirp": "^0.5.1"
}
},
"babel-messages": {
@@ -3757,6 +3756,50 @@
"babel-core": "^6.26.0",
"babel-preset-minify": "^0.3.0",
"webpack-sources": "^1.0.1"
},
"dependencies": {
"babel-core": {
"version": "6.26.3",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
"integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
"dev": true,
"requires": {
"babel-code-frame": "^6.26.0",
"babel-generator": "^6.26.0",
"babel-helpers": "^6.24.1",
"babel-messages": "^6.23.0",
"babel-register": "^6.26.0",
"babel-runtime": "^6.26.0",
"babel-template": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"convert-source-map": "^1.5.1",
"debug": "^2.6.9",
"json5": "^0.5.1",
"lodash": "^4.17.4",
"minimatch": "^3.0.4",
"path-is-absolute": "^1.0.1",
"private": "^0.1.8",
"slash": "^1.0.0",
"source-map": "^0.5.7"
}
},
"babylon": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"babel-plugin-check-es2015-constants": {
@@ -4534,6 +4577,50 @@
"lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"source-map-support": "^0.4.15"
},
"dependencies": {
"babel-core": {
"version": "6.26.3",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz",
"integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==",
"dev": true,
"requires": {
"babel-code-frame": "^6.26.0",
"babel-generator": "^6.26.0",
"babel-helpers": "^6.24.1",
"babel-messages": "^6.23.0",
"babel-register": "^6.26.0",
"babel-runtime": "^6.26.0",
"babel-template": "^6.26.0",
"babel-traverse": "^6.26.0",
"babel-types": "^6.26.0",
"babylon": "^6.18.0",
"convert-source-map": "^1.5.1",
"debug": "^2.6.9",
"json5": "^0.5.1",
"lodash": "^4.17.4",
"minimatch": "^3.0.4",
"path-is-absolute": "^1.0.1",
"private": "^0.1.8",
"slash": "^1.0.0",
"source-map": "^0.5.7"
}
},
"babylon": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"babel-runtime": {
@@ -4543,6 +4630,13 @@
"requires": {
"core-js": "^2.4.0",
"regenerator-runtime": "^0.11.0"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
}
}
},
"babel-template": {
@@ -5799,10 +5893,13 @@
"dev": true
},
"convert-source-map": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz",
"integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=",
"dev": true
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
"integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.1"
}
},
"cookiejar": {
"version": "2.1.2",
@@ -7646,6 +7743,11 @@
}
}
},
"fclone": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz",
"integrity": "sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA="
},
"figures": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
@@ -13625,6 +13727,12 @@
"integrity": "sha512-piXx9N2WT8hWb7PBbX1glAuJVIkEyUV9F5fMXFINpZ0x3otVOFKKeGmeuiclFJlP/UrgTckyV606VjH2rNK4bw==",
"dev": true
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
"dev": true
},
"regexpp": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz",
@@ -14259,11 +14367,6 @@
"regenerate": "^1.4.0"
}
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"regenerator-transform": {
"version": "0.13.3",
"resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.13.3.tgz",
+4 -2
View File
@@ -18,13 +18,14 @@
"author": "Barry Low <bearcanrun@gmail.com>",
"license": "AGPL-3.0",
"devDependencies": {
"@babel/core": "^7.0.1",
"@babel/core": "^7.1.2",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"autoprefixer": "^8.6.5",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^9.0.0",
"babel-loader": "^8.0.2",
"babel-loader": "^7.1.5",
"babel-minify-webpack-plugin": "^0.3.1",
"babel-plugin-dynamic-import-node": "^1.2.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
@@ -67,6 +68,7 @@
"workbox-cli": "^3.4.1"
},
"dependencies": {
"@advancedalgos/web-components": "0.0.5",
"@azure/storage-blob": "^10.0.0-preview",
"@material-ui/core": "^3.0.3",
"@material-ui/icons": "^3.0.1",
@@ -2,8 +2,8 @@
import gql from 'graphql-tag'
const CREATE_TEAM = gql`
mutation CreateTeamMutation($name: String!, $slug: String!) {
createTeam(name: $name, slug: $slug) {
mutation CreateTeamMutation($name: String!, $slug: String!, $botName: String!, $botSlug: String!) {
createTeam(name: $name, slug: $slug, botName: $botName, botSlug: $botSlug) {
id
name
slug
@@ -18,6 +18,7 @@ export const GET_ALL_TEAMS_QUERY = gql`
createdAt
profile {
avatar
banner
description
motto
updatedAt
@@ -36,6 +37,18 @@ export const GET_ALL_TEAMS_QUERY = gql`
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
}
}
@@ -2,8 +2,8 @@
import gql from 'graphql-tag'
export const GET_TEAMS_BY_OWNER = gql`
query teamsByOwnerQuery {
teamsByOwner {
query teamsByOwnerQuery($authId: String!) {
teamsByOwner(ownerId: $authId) {
id
name
slug
@@ -16,6 +16,7 @@ export const GET_TEAMS_BY_OWNER = gql`
createdAt
profile {
avatar
banner
description
motto
updatedAt
@@ -34,6 +35,18 @@ export const GET_TEAMS_BY_OWNER = gql`
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
}
`
+13
View File
@@ -15,6 +15,7 @@ export const TEAMS_FRAGMENT = gql`
createdAt
profile {
avatar
banner
description
motto
updatedAt
@@ -33,6 +34,18 @@ export const TEAMS_FRAGMENT = gql`
createdAt
}
}
fb {
id
name
slug
avatar
kind
status {
status
reason
createdAt
}
}
}
`
@@ -0,0 +1,42 @@
/* eslint-disable no-unused-vars */
import gql from 'graphql-tag'
const UPDATE_FB = gql`
mutation UpdateFBMutation($teamSlug: String!, $avatar: String) {
updateFB(teamId, $teamSlug, avatar: $avatar) {
id
name
slug
owner
status {
status
reason
createdAt
}
createdAt
profile {
avatar
banner
description
motto
updatedAt
}
members {
role
email
member {
alias
authId
}
status {
id
status
reason
createdAt
}
}
}
}
`
export default UPDATE_FB
@@ -28,6 +28,7 @@ const UPDATE_TEAM_PROFILE = gql`
createdAt
profile {
avatar
banner
description
motto
updatedAt
+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
-45
View File
@@ -1,45 +0,0 @@
import React from 'react'
import PropTypes from 'prop-types'
import Typography from '@material-ui/core/Typography'
import { withStyles } from '@material-ui/core/styles'
import Card from '@material-ui/core/Card'
import CardContent from '@material-ui/core/CardContent'
const styles = theme => ({
card: {
display: 'flex',
width: '100%',
margin: '1.5em',
padding: '3em'
},
cardDetails: {
display: 'flex',
flex: 1
},
cardContent: {
flex: 1
}
})
export const MessageCard = ({ classes, message, children }) => (
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardContent className={classes.cardContent}>
<Typography variant='headline' align='center' gutterBottom>
{message}
</Typography>
{children}
</CardContent>
</div>
</Card>
)
MessageCard.propTypes = {
children: PropTypes.node,
classes: PropTypes.object.isRequired,
message: PropTypes.string
}
export default withStyles(styles)(MessageCard)
-344
View File
@@ -1,344 +0,0 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { Mutation } from 'react-apollo'
import Dropzone from 'react-dropzone'
import { Cropper } from 'react-image-cropper'
import * as Azure from '@azure/storage-blob'
import readAndCompressImage from 'browser-image-resizer'
import { imgSrcToBlob } from 'blob-util'
import { withStyles } from '@material-ui/core/styles'
import Dialog from '@material-ui/core/Dialog'
import DialogActions from '@material-ui/core/DialogActions'
import DialogContent from '@material-ui/core/DialogContent'
import Button from '@material-ui/core/Button'
import Typography from '@material-ui/core/Typography'
import MessageCard from './MessageCard'
import GET_AZURE_SAS from '../../graphql/teams/GetAzureSASMutation'
const styles = theme => ({
dropzoneContainer: {
display: 'block',
margin: '1em',
minWidth: 200,
height: 200
},
dropzone: {
display: 'block',
padding: '3em',
minWidth: 200,
height: 200,
borderWidth: 2,
borderColor: '#999',
backgroundColor: '#eee'
},
dropzoneText: {
textAlign: 'center'
},
cropAvatarPreview: {
width: 100,
height: 100
},
cropBannerPreview: {
width: 400,
height: 100
},
cropAvatarBox: {
width: 350,
height: 350
},
cropBannerBox: {
width: 650,
height: 350
},
reactCrop: {
}
})
const imageAvatarConfig = {
quality: 0.6,
maxWidth: 200,
maxHeight: 200,
autoRotate: true,
debug: true,
mimeType: 'image/jpeg'
}
const imageBannerConfig = {
quality: 0.6,
maxWidth: 800,
maxHeight: 200,
autoRotate: true,
debug: true,
mimeType: 'image/jpeg'
}
class UploadImage extends Component {
constructor (props) {
super(props)
this.onDrop = this.onDrop.bind(this)
this.handleCrop = this.handleCrop.bind(this)
this.handleClickOpen = this.handleClickOpen.bind(this)
this.handleClose = this.handleClose.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
this.handleChangeImage = this.handleChangeImage.bind(this)
this.cancelChangeImage = this.cancelChangeImage.bind(this)
this.saveImage = this.saveImage.bind(this)
this.state = {
open: false,
edit: 'avatar',
change: false,
file: null,
src: null,
crop: {
x: 10,
y: 10,
width: 200,
height: 200
},
cropping: false,
cropped: null,
rejected: null,
uploaded: false,
avatarImage: null,
bannerImage: null,
sasqr: null
}
}
render () {
const { classes, team, handleAvatar, handleBanner } = this.props
let avatar = null
let banner = null
if ((team.profile !== null && team.profile.avatar !== undefined && team.profile.avatar !== null && team.profile.avatar !== 'a') || this.state.avatarImage !== null) {
avatar = this.state.avatarImage !== null ? this.state.avatarImage : `${team.profile.avatar}?${Math.random()}`
} 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'
}
if ((team.profile !== null && team.profile.banner !== undefined && team.profile.banner !== null && team.profile.banner !== '') || this.state.bannerImage !== null) {
banner = this.state.bannerImage !== null ? this.state.bannerImage : `${team.profile.banner}?${Math.random()}`
} 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'
}
let handleImage
this.state.edit === 'avatar' ? handleImage = handleAvatar : handleImage = handleBanner
console.log('UploadImage', team, avatar, banner, this.state)
return (
<div className={classes.dropzoneContainer}>
<Mutation mutation={GET_AZURE_SAS} >
{(getAzureSAS, { loading, error, data }) => {
console.log('getAzureSAS: ', error, data)
if (!this.state.uploaded) {
if (data !== undefined && data.getAzureSAS !== null) {
this.saveImage(data.getAzureSAS, team.slug, handleImage)
}
// this.handleSubmit(getAzureSAS, team.slug)
}
if (this.state.cropping) {
return (
<div>
<MessageCard message='Cropping...' />
</div>
)
} else {
return (
<React.Fragment>
<div>
{(this.state.change || (avatar === null && banner === null && this.state.src === null)) && (
<React.Fragment>
<Dropzone
accept='image/jpeg, image/png'
onDrop={(e) => this.onDrop(e, this.state.edit)}
className={classes.dropzone}
multiple={false}
>
<p>Drag-and-drop team {this.state.edit} here or click to select files. <br /> Only *.jpeg and *.png images</p>
</Dropzone>
<Button
onClick={(e) => this.cancelChangeImage()}
color='primary'
>
Cancel Image Change
</Button>
</React.Fragment>
)}
{!this.state.change && (
<React.Fragment>
<img src={banner} className={classes.cropBannerPreview} alt='Banner Crop Preview' />
<Button
onClick={(e) => this.handleChangeImage(e, 'banner')}
color='primary'
>
Change Banner
</Button>
</React.Fragment>
)}
{!this.state.change && (
<React.Fragment>
<img src={avatar} className={classes.cropAvatarPreview} alt='AvatarCrop Preview' />
<Button
onClick={(e) => this.handleChangeImage(e, 'avatar')}
color='primary'
>
Change Avatar
</Button>
</React.Fragment>
)}
{(this.state.avatarImage !== null || this.state.bannerImage !== null) && (
<Typography variant='title' color='primary' >Click Update Team to save image changes!</Typography>
)}
</div>
<Dialog
open={this.state.open}
onClose={this.handleClose}
aria-labelledby='form-dialog-title'
>
<div classes={classes.dialogContainer}>
<DialogContent>
<div className={this.state.edit === 'avatar' ? classes.cropAvatarBox : classes.cropBannerBox}>
{this.state.src !== null && (
<Cropper
src={this.state.src}
originX={100}
originY={100}
ref={ref => { this.cropper = ref }}
onImgLoad={this.onImageLoaded}
ratio={this.state.edit === 'avatar' ? 1 : 4}
/>
)}
</div>
</DialogContent>
<DialogActions>
<Button onClick={this.handleClose} color='primary'>
Cancel
</Button>
<Button
onClick={(e) => this.handleCrop(e, getAzureSAS, team.slug, handleImage)}
color='primary'
>
Crop Image
</Button>
</DialogActions>
</div>
</Dialog>
</React.Fragment>
)
}
}}
</Mutation>
</div>
)
}
onDrop (file, editImageType) {
console.log('onDrop', file)
this.onSelectFile(file, editImageType)
this.setState({
file
})
}
onSelectFile (file, editImageType) {
if (file && file.length > 0) {
const reader = new window.FileReader()
reader.addEventListener(
'load',
() =>
this.setState({
src: reader.result,
open: true
}),
false
)
reader.readAsDataURL(file[0])
}
}
async handleCrop (e, getAzureSAS, teamSlug, handleImage) {
e.preventDefault()
const data = await getAzureSAS({ variables: { teamSlug } })
console.log('handleCrop', this.cropper, data)
const cropped = this.cropper.crop()
const croppedBlob = await imgSrcToBlob(cropped, 'image/png', 'Anonymous')
let imageConfig = this.state.edit === 'avatar' ? imageAvatarConfig : imageBannerConfig
let resizedBlob = await readAndCompressImage(croppedBlob, imageConfig)
return this.setState({ cropped: resizedBlob, cropping: true, open: false }, () => {
this.saveImage(data.getAzureSAS, teamSlug, handleImage)
})
}
async handleSubmit (e, getAzureSAS, teamSlug) {
e.preventDefault()
let SAS = await getAzureSAS({ variables: { teamSlug } })
console.log('handleSubmit: ', await SAS)
return this.setState({ sasqr: SAS })
}
async saveImage (SASQueryParameters, teamSlug, handleImage) {
const fileName = this.state.edit === 'avatar' ? `${teamSlug}-avatar.jpg` : `${teamSlug}-banner.jpg`
let SASurl
if (this.state.sasqr === null) {
SASurl = process.env.AZURE_SAS_URL
} else {
SASurl = `${process.env.AZURE_STORAGE_URL}?${this.state.sasqr}`
}
console.log('getAzureSAS2 SASurl: ', SASurl, this.state.sasqr)
const pipeline = Azure.StorageURL.newPipeline(new Azure.AnonymousCredential())
const serviceURL = new Azure.ServiceURL(SASurl, pipeline)
const containerName = teamSlug
const containerURL = Azure.ContainerURL.fromServiceURL(serviceURL, containerName)
const blockBlobURL = Azure.BlockBlobURL.fromContainerURL(containerURL, fileName)
await Azure.uploadBrowserDataToBlockBlob(Azure.Aborter.None, this.state.cropped, blockBlobURL)
const image = `${process.env.AZURE_STORAGE_URL}${teamSlug}/${fileName}?${Math.random()}`
handleImage(image)
let avatarImage = this.state.edit === 'avatar' ? image : null
let bannerImage = this.state.edit === 'banner' ? image : null
this.setState({ cropping: false, uploaded: true, change: false, avatarImage: avatarImage, bannerImage: bannerImage })
return image
}
handleChangeImage (e, editImageType) {
e.preventDefault()
let crop = editImageType === 'avatar' ? { x: 10, y: 10, width: 200, height: 200 } : { x: 10, y: 10, width: 800, height: 200 }
this.setState({
edit: editImageType,
change: true,
crop: crop
})
}
cancelChangeImage () {
this.setState({ change: false })
}
handleClickOpen (e, editImageType) {
e.preventDefault()
this.setState({
open: true
})
}
handleClose () {
this.setState({ open: false, src: null, change: false })
}
}
UploadImage.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object,
handleAvatar: PropTypes.func.isRequired,
handleBanner: PropTypes.func.isRequired
}
export default withStyles(styles)(UploadImage)
-4
View File
@@ -1,4 +0,0 @@
import MessageCard from './MessageCard'
import UploadImage from './UploadImage'
export { MessageCard, UploadImage }
@@ -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 '../common/'
import { MessageCard } from '@advancedalgos/web-components'
const styles = theme => ({
tableContainer: {
@@ -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: ''
}
}
@@ -96,43 +106,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)
}}
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>
)
@@ -150,27 +186,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 } })
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: '' })
}
}
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { Query } from 'react-apollo'
import { withStateHandlers, lifecycle, compose } from 'recompose'
import { Link } from 'react-router-dom'
import Typography from '@material-ui/core/Typography'
@@ -10,9 +11,31 @@ import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import DashTeamItem from './DashTeamItem'
import CreateTeamDialog from './CreateTeamDialog'
import { MessageCard } from '../../common/'
import { MessageCard } from '@advancedalgos/web-components'
export const DashTeam = ({ classes }) => {
import { isDefined, isString } from '../../../utils/js-helpers'
import { getItem } from '../../../utils/local-storage'
export const DashTeam = ({ classes, user = null }) => {
let owner
let authId = null
console.log('DashTeam: ', user)
if (user !== null && isString(user)) {
owner = JSON.parse(user)
if (isDefined(owner.authId)) authId = owner.authId
}
console.log('DashTeam 2: ', owner, authId)
if (authId === undefined || authId === null) {
return (
<Grid item md={6}>
<Typography variant='display1' gutterBottom>
Teams
</Typography>
<Typography variant='caption'>Loading...</Typography>
</Grid>
)
}
return (
<Grid item md={6} style={{ position: 'relative' }}>
<Typography variant='display1' gutterBottom>
@@ -21,7 +44,7 @@ export const DashTeam = ({ classes }) => {
Manage Teams
</Link>
</Typography>
<Query query={GET_TEAMS_BY_OWNER}>
<Query query={GET_TEAMS_BY_OWNER} variables={{ authId }}>
{({ loading, error, data }) => {
console.log('GET_TEAMS_BY_OWNER: ', loading, error, data)
let errors
@@ -76,7 +99,25 @@ export const DashTeam = ({ classes }) => {
}
DashTeam.propTypes = {
user: PropTypes.any,
classes: PropTypes.object.isRequired
}
export default DashTeam
const getUserOnMount = lifecycle({
componentDidMount () {
getItem('user').then(user => {
this.setState({ user })
}) // Set user to state
}
})
const mapStateToProps = withStateHandlers(() => ({ user: null }), {
user: ({ user }) => () => ({ user })
})
const DashTeamAuthId = compose(
mapStateToProps,
getUserOnMount
)(DashTeam)
export default DashTeamAuthId
@@ -35,39 +35,34 @@ const styles = theme => ({
}
})
export const DashTeamItem = ({ classes, team }) => {
let avatar
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'
}
return (
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image={avatar}
title={team.name}
/>
<CardContent className={classes.cardContent}>
<Typography variant='headline'>{team.name}</Typography>
<Typography variant='subheading' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='subheading' paragraph>
Members: {team.members.length}
</Typography>
</CardContent>
<CardActions>
<ManageTeamDetails team={team} />
</CardActions>
</div>
</Card>
</Grid>
)
}
const placeholder = 'data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
export const DashTeamItem = ({ classes, team }) => (
<Grid item>
<Card className={classes.card}>
<div className={classes.cardDetails}>
<CardMedia
className={classes.cardMedia}
image={team.profile !== null && team.profile.avatar !== undefined && team.profile.avatar !== null ? team.profile.avatar : placeholder}
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography variant='headline'>{team.name}</Typography>
<Typography variant='subheading' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='subheading' paragraph>
Members: {team.members.length}
</Typography>
</CardContent>
<CardActions>
<ManageTeamDetails team={team} />
</CardActions>
</div>
</Card>
</Grid>
)
DashTeamItem.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired
@@ -7,7 +7,7 @@ import Typography from '@material-ui/core/Typography'
import ManageMemberDelete from './ManageMemberDelete'
import ManageMemberRole from './ManageMemberRole'
import ManageMemberStatus from './ManageMemberStatus'
import { MessageCard } from '../../common/'
import { MessageCard } from '@advancedalgos/web-components'
export const ManageMembersItem = ({ classes, member, teamId, authId }) => {
console.log('ManageMembersItem', member)
@@ -10,7 +10,7 @@ import Typography from '@material-ui/core/Typography'
import InviteMemberDialog from './InviteMemberDialog'
import ManageMembersItem from './ManageMembersItem'
import { MessageCard } from '../../common/'
import { MessageCard } from '@advancedalgos/web-components'
const styles = theme => ({
heroContent: {
@@ -61,7 +61,7 @@ export const ManageMembersTeamItem = ({ 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'
}
if (team.members.length > 0) {
return (
@@ -55,13 +55,14 @@ export class ManageTeamDelete extends Component {
render () {
console.log(this.props, this.props.slug)
const { classes } = this.props
const { classes, authId } = this.props
return (
<Mutation
mutation={DELETE_TEAM}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER
query: GET_TEAMS_BY_OWNER,
variables: { authId }
}
]}
>
@@ -148,7 +149,8 @@ export class ManageTeamDelete extends Component {
ManageTeamDelete.propTypes = {
classes: PropTypes.object.isRequired,
slug: PropTypes.string.isRequired
slug: PropTypes.string.isRequired,
authId: PropTypes.string.isRequired
}
export default withStyles(styles)(ManageTeamDelete)
@@ -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>
@@ -13,12 +13,14 @@ 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_TEAM_PROFILE from '../../../graphql/teams/UpdateTeamProfileMutation'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import { checkGraphQLError } from '../../../utils/graphql-errors'
import { UploadImage } from '../../common'
import GET_AZURE_SAS from '../../../graphql/teams/GetAzureSASMutation'
const styles = theme => ({
dialogContainer: {
@@ -47,6 +49,7 @@ export class ManageTeamEdit extends Component {
const description = props.team.profile.description || ''
const avatar = props.team.profile.avatar || ''
const banner = props.team.profile.banner || ''
this.authId = props.authId
this.state = {
open: false,
@@ -58,14 +61,15 @@ export class ManageTeamEdit extends Component {
}
render () {
console.log(this.props, this.props.slug)
console.log('ManageTeamEdit ', this.props, this.props.slug, this.props.team)
const { classes, team } = this.props
return (
<Mutation
mutation={UPDATE_TEAM_PROFILE}
refetchQueries={[
{
query: GET_TEAMS_BY_OWNER
query: GET_TEAMS_BY_OWNER,
variables: { authId: this.authId }
}
]}
>
@@ -108,11 +112,78 @@ export class ManageTeamEdit extends Component {
Edit Team Details
</DialogTitle>
<DialogContent>
<UploadImage
team={team}
handleAvatar={this.handleAvatar}
handleBanner={this.handleBanner}
/>
<Mutation mutation={GET_AZURE_SAS} >
{(getAzureSAS, { loading, error, data }) => {
console.log('getAzureSAS: ', loading, error, data, team.profile)
const AzureStorageUrl = process.env.AZURE_STORAGE_URL
const containerName = team.slug
let AzureSASURL
if (!loading && data !== undefined) {
AzureSASURL = data.getAzureSAS
} else {
getAzureSAS({ variables: { teamSlug: containerName } })
}
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) {
return (<MessageCard message='Loading...' />)
} else {
return (
<React.Fragment>
<ImageUpload
key='banner'
handleUrl={this.handleBanner}
fileName={`${team.slug}-banner.jpg`}
containerName={containerName}
existingImage={banner}
cropContainer={{ x: 10, y: 10, width: 800, height: 200 }}
cropPreviewBox={{ width: 650, height: 200 }}
saveImageConfig={{
quality: 0.6,
maxWidth: 800,
maxHeight: 200,
autoRotate: true,
mimeType: 'image/jpeg'
}}
AzureStorageUrl={AzureStorageUrl}
AzureSASURL={AzureSASURL}
cropRatio={4}
debug
/>
<ImageUpload
key='avatar'
handleUrl={this.handleAvatar}
fileName={`${team.slug}-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'
@@ -192,12 +263,12 @@ export class ManageTeamEdit extends Component {
handleAvatar (avatarUrl) {
console.log('handleAvatar: ', avatarUrl)
this.setState({ avatar: avatarUrl })
this.setState({ avatar: `${avatarUrl}?${Math.random()}` })
}
handleBanner (bannerUrl) {
console.log('handleBanner: ', bannerUrl)
this.setState({ banner: bannerUrl })
this.setState({ banner: `${bannerUrl}?${Math.random()}` })
}
async handleSubmit (e, updateTeamProfile, slug) {
@@ -207,6 +278,7 @@ export class ManageTeamEdit extends Component {
await updateTeamProfile({
variables: {
slug,
owner: this.authId,
description: this.state.description,
motto: this.state.motto,
avatar: this.state.avatar,
@@ -220,7 +292,8 @@ export class ManageTeamEdit extends Component {
ManageTeamEdit.propTypes = {
classes: PropTypes.object.isRequired,
slug: PropTypes.string.isRequired,
team: PropTypes.object
team: PropTypes.object,
authId: PropTypes.string.isRequired
}
export default withStyles(styles)(ManageTeamEdit)
@@ -12,7 +12,7 @@ import { getItem } from '../../../utils/local-storage'
import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import ManageMembersTeamItem from './ManageMembersTeamItem'
import { MessageCard } from '../../common/'
import { MessageCard } from '@advancedalgos/web-components'
const styles = theme => ({
heroContent: {
@@ -67,6 +67,7 @@ export const ManageTeamMembersList = ({ classes, user = null }) => {
return (
<Query
query={GET_TEAMS_BY_OWNER}
variables={{ authId }}
fetchPolicy='network-only'
>
{({ loading, error, data }) => {
@@ -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,8 +39,8 @@ export const ManageTeamsItem = ({ classes, team, authId }) => {
</CardContent>
<CardActions>
<ManageTeamDetails team={team} />
<ManageTeamEdit slug={team.slug} team={team} />
<ManageTeamDelete slug={team.slug} />
<ManageTeamEdit slug={team.slug} team={team} authId={authId} />
<ManageTeamDelete slug={team.slug} authId={authId} />
</CardActions>
</div>
</Card>
@@ -4,6 +4,7 @@ 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'
@@ -13,7 +14,6 @@ import GET_TEAMS_BY_OWNER from '../../../graphql/teams/GetTeamsByOwnerQuery'
import ManageTeamsItem from './ManageTeamsItem'
import CreateTeamDialog from './CreateTeamDialog'
import { MessageCard } from '../../common/'
const styles = theme => ({
heroContent: {
@@ -68,6 +68,7 @@ export const ManageTeamsList = ({ classes, user = null }) => {
return (
<Query
query={GET_TEAMS_BY_OWNER}
variables={{ authId }}
fetchPolicy='network-only'
>
{({ loading, error, data }) => {
@@ -93,9 +94,6 @@ export const ManageTeamsList = ({ classes, user = null }) => {
authId={authId}
/>
))}
<MessageCard message='Create another team'>
<CreateTeamDialog authId={authId} />
</MessageCard>
{errors}
</Grid>
</React.Fragment>
+1 -1
View File
@@ -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 '../common/'
import { MessageCard } from '@advancedalgos/web-components'
const styles = theme => ({
tableContainer: {
+1 -1
View File
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import queryString from 'query-string'
import { MessageCard } from '../common/'
import { MessageCard } from '@advancedalgos/web-components'
class AcceptTeamInvite extends Component {
render () {
@@ -42,49 +42,68 @@ const styles = theme => ({
maxWidth: 600,
margin: '0 auto',
padding: `${theme.spacing.unit * 8}px 0 ${theme.spacing.unit * 6}px`
},
banner: {
maxWidth: '100%',
height: 'auto',
margin: '0 auto'
},
avatar: {
maxWidth: 100,
height: 100,
margin: '0 1em 1em 0',
float: 'left'
}
})
const TeamsDetails = ({ classes, team }) => (
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<Paper className={classes.paper}>
<div className={classes.heroContent}>
<Link to='/teams'>&larr; Back to all teams</Link>
<Typography
variant='display3'
align='center'
color='textPrimary'
gutterBottom
>
{team.name}
</Typography>
<Typography variant='subheading' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='subheading' color='textSecondary'>
Motto: {team.profile.motto}
</Typography>
<Typography variant='subheading' color='textSecondary'>
Description: {team.profile.description}
</Typography>
<Typography variant='subheading' paragraph gutterBottom>
Members: {team.members.length}
</Typography>
<Typography variant='subheading' color='primary'>
Team Admin:&nbsp;
{team.members.map(member => {
if (member.role === 'OWNER' || member.role === 'ADMIN') {
return member.member.alias
}
})}
</Typography>
</div>
</Paper>
</main>
</React.Fragment>
)
const TeamsDetails = ({ classes, team }) => {
let avatar
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'
}
let banner
if (team.profile.banner !== undefined && team.profile.banner !== 'a') {
banner = team.profile.banner
} else {
banner = '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'
}
return (
<React.Fragment>
<CssBaseline />
<main className={classes.layout}>
<Paper className={classes.paper}>
<div className={classes.heroContent}>
<Link to='/teams'>&larr; Back to all teams</Link>
<img src={banner} alt={team.name} className={classes.banner} />
<img src={avatar} alt={team.name} className={classes.avatar} />
<Typography
variant='display3'
align='center'
color='textPrimary'
gutterBottom
>
{team.name}
</Typography>
<Typography variant='subheading' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='subheading' color='textSecondary'>
Motto: {team.profile.motto}
</Typography>
<Typography variant='subheading' color='textSecondary'>
Description: {team.profile.description}
</Typography>
<Typography variant='subheading' paragraph gutterBottom>
Members: {team.members.length}
</Typography>
</div>
</Paper>
</main>
</React.Fragment>
)
}
TeamsDetails.propTypes = {
classes: PropTypes.object.isRequired,
team: PropTypes.object.isRequired
+48 -34
View File
@@ -12,40 +12,54 @@ import Typography from '@material-ui/core/Typography'
import { Link } from 'react-router-dom'
export const TeamsItem = ({ classes, team }) => (
<Grid item sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.cardMedia}
image='data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%22288%22%20height%3D%22225%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20288%20225%22%20preserveAspectRatio%3D%22none%22%3E%3Cdefs%3E%3Cstyle%20type%3D%22text%2Fcss%22%3E%23holder_164edaf95ee%20text%20%7B%20fill%3A%23eceeef%3Bfont-weight%3Abold%3Bfont-family%3AArial%2C%20Helvetica%2C%20Open%20Sans%2C%20sans-serif%2C%20monospace%3Bfont-size%3A14pt%20%7D%20%3C%2Fstyle%3E%3C%2Fdefs%3E%3Cg%20id%3D%22holder_164edaf95ee%22%3E%3Crect%20width%3D%22288%22%20height%3D%22225%22%20fill%3D%22%2355595c%22%3E%3C%2Frect%3E%3Cg%3E%3Ctext%20x%3D%2296.32500076293945%22%20y%3D%22118.8%22%3EThumbnail%3C%2Ftext%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E' // eslint-disable-line max-len
title='Image title'
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant='headline' component='h2'>
{team.name}
</Typography>
<Typography variant='caption' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='caption' paragraph gutterBottom>
Members: 1
</Typography>
<Typography variant='subheading' color='primary'>
Now recruiting!
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link to={`/teams/${team.slug}`}>
<Button size='small' color='primary' className={classes.buttonRight}>
Details
</Button>
</Link>
</CardActions>
</Card>
</Grid>
)
export const TeamsItem = ({ classes, team }) => {
/* let avatar
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'
} */
let banner
if (team.profile.banner !== undefined && team.profile.banner !== null) {
banner = team.profile.banner
} else {
banner = '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'
}
return (
<Grid item sm={6} md={4} lg={3}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.cardMedia}
image={banner}
title={team.name}
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant='headline' component='h2'>
{team.name}
</Typography>
<Typography variant='caption' color='textSecondary'>
{team.createdAt}
</Typography>
<Typography variant='caption' paragraph gutterBottom>
Members: 1
</Typography>
<Typography variant='subheading' color='primary'>
Now recruiting!
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Link to={`/teams/${team.slug}`}>
<Button size='small' color='primary' className={classes.buttonRight}>
Details
</Button>
</Link>
</CardActions>
</Card>
</Grid>
)
}
TeamsItem.propTypes = {
classes: PropTypes.object.isRequired,
@@ -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'