Remove packages dir
No files matched your search
@@ -1,14 +0,0 @@
|
||||
PRISMA_SECRET=prisma-secret
|
||||
PRISMA_ENDPOINT=http://localhost:4466
|
||||
|
||||
AUTH0_DOMAIN=auth0-domain
|
||||
AUTH0_AUDIENCE=auth0-audience
|
||||
AUTH0_ISSUER=auth0-issuer
|
||||
|
||||
NODE_ENV=development
|
||||
GENERATE_SOURCEMAP=false
|
||||
COMPOSE_HTTP_TIMEOUT=2000
|
||||
VIRTUAL_HOST=teams.advancedalgos.net
|
||||
LETSENCRYPT_HOST=teams.advancedalgos.net
|
||||
LETSENCRYPT_EMAIL=support@advancedalgos.net
|
||||
VIRTUAL_PORT=4000
|
||||
@@ -1,9 +0,0 @@
|
||||
/.env
|
||||
/database/.env
|
||||
/database/docker-compose.prisma.prod.yml
|
||||
/docker-compose.prisma.prod.yml
|
||||
dist
|
||||
node_modules
|
||||
.idea
|
||||
.vscode
|
||||
*.log
|
||||
@@ -1,12 +0,0 @@
|
||||
projects:
|
||||
app:
|
||||
schemaPath: src/schema.graphql
|
||||
extensions:
|
||||
endpoints:
|
||||
dev1:
|
||||
url: http://0.0.0.0:4000/graphql
|
||||
|
||||
database:
|
||||
schemaPath: src/generated/prisma.graphql
|
||||
extensions:
|
||||
prisma: database/prisma.yml
|
||||
@@ -1,21 +0,0 @@
|
||||
FROM mhart/alpine-node:8
|
||||
|
||||
ENV PORT=4000
|
||||
|
||||
RUN mkdir -p /var/www/app && \
|
||||
mkdir -p /var/www/app/node_modules
|
||||
|
||||
# Use app directory as current workdir
|
||||
WORKDIR /var/www/app
|
||||
ENV PATH /var/www/app/node_modules/.bin:$PATH
|
||||
# Copy package.json to workdir
|
||||
COPY package.json /var/www/app/package.json
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --silent
|
||||
|
||||
# Copy application code
|
||||
COPY . /var/www/app
|
||||
|
||||
# Expose Port
|
||||
EXPOSE $PORT
|
||||
@@ -1,114 +0,0 @@
|
||||
# Advance Algos Team GraphQL API Server
|
||||
|
||||
This is the Advanced Algos Team Module server-side GraphQL API server. We use Webpack 4 + Docker to develop locally, then build to a distribution bundle that can then be uploaded via FTP, git or used with a continuous integration workflow.
|
||||
|
||||
This modules client-side component is the [aateam-client package](../aateam-client#README).
|
||||
|
||||
For more information on the general architecture and related technology of this module, please visit this repos [main documenation](../../docs#README).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Run
|
||||
```
|
||||
npm install
|
||||
```
|
||||
then
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
|
||||
Get latest Docker and Docker Compose: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/)
|
||||
|
||||
#### Using Docker container for Development
|
||||
|
||||
To run starter kit in development mode with hot code reload execute:
|
||||
|
||||
Run docker container in background:
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
If image issues, try running:
|
||||
```
|
||||
docker-compose up --build
|
||||
```
|
||||
then run
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To view logs
|
||||
```
|
||||
docker-compose logs -f -t
|
||||
```
|
||||
Hit Ctrl + c to exit logs without stopping service
|
||||
|
||||
To stop service
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## Overview
|
||||
The development architecture of the Algobots Community site is to allow both rapid initial development and ongoing rapid-evolution. As with any opinionated workflow, there's a learning curve, but the libraries and architecture of this application have been chosen to maximize collaboration, flexibility, and extensibility.
|
||||
|
||||
### Main Libraries
|
||||
|
||||
- [*Webpack 4*](https://webpack.js.org/):web application bundler. Allows us to us latest newer JS ECMA features that increase productivity and code clarity. Also allows additional libraries such as modular and programmable CSS via SASS/SCSS/LESS. Provides webserver for local development.
|
||||
- [*Bulma CSS Framework*](https://bulma.io/documentation/): Bulma is a highly-customizable CSS-only framework allowing us to quickly prototype the layout of a webapp without being complicated by opinionated functionality represented in libraries such as Bootstrap or Material UI. Grants us the flexibility to create a custom look at top speed whilst minimizing baggage.
|
||||
- [*HyperApp*](https://github.com/hyperapp/hyperapp): Allows a controlled Flux-styled approach to state-management and data-flow through components like React minus the excess boilerplate and learning curve.
|
||||
- [*Docker*](https://www.docker.com/what-docker)
|
||||
|
||||
### File organization
|
||||
|
||||
```
|
||||
. # Top level directory located at your choice
|
||||
├── src
|
||||
│ ├── actions # Actions are functions that change the app state
|
||||
│ ├── assets # images, logos and other static assets
|
||||
│ ├── state # Schema of state — the single source of truth concerning app state.
|
||||
│ ├── styles # SASS/SCSS styles
|
||||
│ ├── utils # Local storage and other utils
|
||||
│ └── views # Main folder for editing site content
|
||||
│ ├─ nav # Nav components — header, footer, etc.
|
||||
│ ├─ pages # Nav components — header, footer, etc.
|
||||
│ │ ├─ landing # Homepage
|
||||
│ │ │ ├─ sections # Homepage sections. Collated in its own index.js
|
||||
│ │ │ └─ index.js # Main homepage component. Pulls in sections
|
||||
│ │ └─ ... # Other page/component folders
|
||||
│ └─ index.js # Main view container. Main router
|
||||
├── html
|
||||
│ └── index.html # index.html template pulled in by Webpack to attach transpiled bundles
|
||||
├── index.js # JS entry point to website
|
||||
│
|
||||
├── tools # utility scripts, mostly for webpack configuration
|
||||
│
|
||||
├── node_modules # created by running `npm install` in this packages root dir
|
||||
├── dist # created by `npm run develop` - a cache of bundled files served by Webpack for local development
|
||||
└── build # created by `npm run build` - location of deployable production bundle
|
||||
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
The simplest deployment is compiling/transpiling the site to a distribution bundle and then uploading the files in the bundle to a web-accessible folder or storage.
|
||||
|
||||
For this application we use Webpack to transpile the application which combines and minimizes all code and assets into an optimized bundle without and of the development code and resources.
|
||||
|
||||
To build, run:
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
Looking in this packages directory, there should be a ./build directory with the final built files that can be uploaded.
|
||||
|
||||
### Create Docker image
|
||||
|
||||
```
|
||||
docker build -t aateamapi/prod .
|
||||
```
|
||||
|
||||
### Deployment instructions
|
||||
|
||||
Coming soon...
|
||||
@@ -1,94 +0,0 @@
|
||||
enum Role {
|
||||
OWNER
|
||||
ADMIN
|
||||
MEMBER
|
||||
}
|
||||
|
||||
enum Status {
|
||||
ACTIVE
|
||||
INACTIVE
|
||||
APPLY
|
||||
INVITE
|
||||
}
|
||||
|
||||
enum FBType {
|
||||
BOT
|
||||
NET
|
||||
ADVANCED
|
||||
}
|
||||
|
||||
enum FBKind {
|
||||
TRADER
|
||||
INDICATOR
|
||||
EXTRACTOR
|
||||
PLOTTER
|
||||
}
|
||||
|
||||
type Team {
|
||||
id: ID! @unique
|
||||
name: String! @unique
|
||||
slug: String! @unique
|
||||
profile: TeamProfile
|
||||
members: [TeamMembers!]! @relation (name: "TeamMembers", onDelete: SET_NULL)
|
||||
wallet: Wallet
|
||||
fb: [FinancialBeings!]! @relation (name: "TeamFinancialBeing", onDelete: SET_NULL)
|
||||
status: [TeamStatus!]!
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type TeamProfile {
|
||||
description: String
|
||||
motto: String
|
||||
avatar: String
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type TeamStatus {
|
||||
status: Status @Default(value: inactive)
|
||||
reason: String
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type TeamMembers {
|
||||
member: Member @relation (name: "MemberOnTeam", onDelete: SET_NULL)
|
||||
team: Team @relation (name: "TeamMembers", onDelete: SET_NULL)
|
||||
role: Role @default(value: MEMBER)
|
||||
status: [MemberStatus!]!
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type Member {
|
||||
id: ID! @unique
|
||||
nickname: String
|
||||
team: TeamMembers @relation (name: "MemberOnTeam", onDelete: SET_NULL)
|
||||
authId: String! @unique
|
||||
visible: Boolean! @default(value: true)
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type MemberStatus {
|
||||
status: Status @Default(value: inactive)
|
||||
reason: String
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type Wallet {
|
||||
balance: Float
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
type FinancialBeings {
|
||||
id: ID! @unique
|
||||
type: FBType
|
||||
kind: FBKind
|
||||
team: Team @relation (name: "TeamFinancialBeing", onDelete: SET_NULL)
|
||||
status: [FBStatus!]!
|
||||
updatedAt: DateTime!
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type FBStatus {
|
||||
status: Status @Default(value: inactive)
|
||||
reason: String
|
||||
createdAt: DateTime!
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
secret: ${env:PRISMA_SECRET}
|
||||
endpoint: ${env:PRISMA_ENDPOINT}
|
||||
datamodel: datamodel.graphql
|
||||
|
||||
hooks:
|
||||
post-deploy:
|
||||
- graphql get-schema --project database
|
||||
- graphql prepare
|
||||
@@ -1,29 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
prisma:
|
||||
image: prismagraphql/prisma:1.15.1
|
||||
restart: always
|
||||
ports:
|
||||
- "4466:4466"
|
||||
environment:
|
||||
PRISMA_CONFIG: |
|
||||
managementApiSecret: ${PRISMA_SECRET}
|
||||
port: 4466
|
||||
databases:
|
||||
default:
|
||||
connector: postgres
|
||||
host: postgres
|
||||
port: 5432
|
||||
user: prisma
|
||||
password: prisma
|
||||
migrations: true
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: prisma
|
||||
POSTGRES_PASSWORD: prisma
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
volumes:
|
||||
postgres:
|
||||
@@ -1,25 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
node:
|
||||
image: aateamapi-prod:v1
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
env_file: ./.env
|
||||
container_name: aateamapi
|
||||
tty: true
|
||||
stdin_open: true
|
||||
volumes:
|
||||
- ./:/var/www/app
|
||||
- /var/www/app/node_modules
|
||||
working_dir: /var/www/app
|
||||
restart: always
|
||||
command: node -r dotenv/config src/index.js
|
||||
expose:
|
||||
- 4000
|
||||
- 80
|
||||
network_mode: webproxy
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: webproxy
|
||||
@@ -1,50 +0,0 @@
|
||||
version: '3'
|
||||
services:
|
||||
prisma:
|
||||
image: prismagraphql/prisma:1.15.1
|
||||
depends_on:
|
||||
- postgres
|
||||
restart: always
|
||||
ports:
|
||||
- "4466:4466"
|
||||
environment:
|
||||
PRISMA_CONFIG: |
|
||||
managementApiSecret: ${PRISMA_SECRET}
|
||||
port: 4466
|
||||
databases:
|
||||
default:
|
||||
connector: postgres
|
||||
host: postgres
|
||||
port: 5432
|
||||
user: prisma
|
||||
password: prisma
|
||||
migrations: true
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: prisma
|
||||
POSTGRES_PASSWORD: prisma
|
||||
volumes:
|
||||
- postgres:/var/lib/postgresql/data
|
||||
node:
|
||||
image: aateamapi-dev:v1
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
depends_on:
|
||||
- prisma
|
||||
env_file: ./.env
|
||||
container_name: aateamapi
|
||||
tty: true
|
||||
stdin_open: true
|
||||
volumes:
|
||||
- ./:/var/www/app
|
||||
- /var/www/app/node_modules
|
||||
working_dir: /var/www/app
|
||||
restart: always
|
||||
command: node -r dotenv/config src/index.js
|
||||
ports:
|
||||
- "4000:4000"
|
||||
volumes:
|
||||
postgres:
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "aa-team-api",
|
||||
"scripts": {
|
||||
"start": "nodemon -e js,graphql -x node -r dotenv/config src/index.js",
|
||||
"debug": "nodemon -e js,graphql -x node --inspect -r dotenv/config src/index.js",
|
||||
"playground": "graphql playground --dotenv dotenv/config",
|
||||
"dev": "npm-run-all --parallel debug playground"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.4",
|
||||
"express-jwt": "^5.3.1",
|
||||
"graphql-import": "^0.6.0",
|
||||
"graphql-tools": "3.1.1",
|
||||
"graphql-yoga": "^1.16.1",
|
||||
"jsonwebtoken": "^8.3.0",
|
||||
"jwks-rsa": "^1.3.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"nodemon": "^1.18.3",
|
||||
"prisma-binding": "2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"graphql-cli": "^2.16.5",
|
||||
"npm-run-all": "4.1.3",
|
||||
"prisma": "^1.14.0"
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
const _get = require('lodash.get')
|
||||
const validateAndParseIdToken = require('./helpers/validateAndParseIdToken')
|
||||
const createMember = require('./index')
|
||||
|
||||
const memberLocationOnContext = 'request.member'
|
||||
const bearerAccessToken = 'request.headers.authorization'
|
||||
|
||||
const ctxMember = ctx => _get(ctx, memberLocationOnContext)
|
||||
const ctxToken = ctx => _get(ctx, bearerAccessToken)
|
||||
|
||||
const isLoggedIn = async ctx => {
|
||||
let member = ctxMember(ctx, memberLocationOnContext)
|
||||
let token = ctxToken(ctx, bearerAccessToken)
|
||||
let memberToken
|
||||
|
||||
if (!member && token) {
|
||||
let scheme, credentials
|
||||
const tokenParts = token.split(' ')
|
||||
if (tokenParts.length === 2) {
|
||||
scheme = tokenParts[0]
|
||||
credentials = tokenParts[1]
|
||||
}
|
||||
|
||||
if (/^Bearer$/i.test(scheme)) {
|
||||
token = credentials
|
||||
//verify token
|
||||
try {
|
||||
memberToken = await validateAndParseIdToken(token)
|
||||
const auth0id = memberToken.sub
|
||||
|
||||
member = await ctx.db.query.member({ where: { auth0id } })
|
||||
|
||||
if (!member) {
|
||||
member = await createMember(ctx, memberToken)
|
||||
}
|
||||
} catch (err) {
|
||||
memberToken = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!member) {
|
||||
throw new Error(`Not logged in`)
|
||||
}
|
||||
return member
|
||||
}
|
||||
|
||||
|
||||
const isRequestingMemberAlsoOwner = ({ ctx, memberId, type, typeId }) =>
|
||||
ctx.db.exists[type]({ id: typeId, owner: { auth0id: memberId } })
|
||||
const isRequestingMember = ({ ctx, memberId }) => ctx.db.exists.Member({ auth0id: memberId })
|
||||
|
||||
const directiveResolvers = {
|
||||
isAuthenticated: async (next, source, args, ctx) => {
|
||||
let result = await isLoggedIn(ctx)
|
||||
console.log('directive isAuthenticated: ', result)
|
||||
return next(result)
|
||||
},
|
||||
hasRole: (next, source, { roles }, ctx) => {
|
||||
const { role } = isLoggedIn(ctx)
|
||||
console.log('directive hasRole: ', role)
|
||||
if (roles.includes(role)) {
|
||||
return next()
|
||||
}
|
||||
throw new Error(`Unauthorized, incorrect role`)
|
||||
},
|
||||
isOwner: async (next, source, { type }, ctx) => {
|
||||
const { id: typeId } =
|
||||
source && source.id
|
||||
? source
|
||||
: ctx.request.body.variables ? ctx.request.body.variables : { id: null }
|
||||
const { auth0id: memberId } = await isLoggedIn(ctx)
|
||||
console.log('directive isOwner 0: ', type, typeId, memberId)
|
||||
const isOwner =
|
||||
type === `Member`
|
||||
? memberId === typeId
|
||||
: await isRequestingMemberAlsoOwner({ ctx, memberId, type, typeId })
|
||||
console.log('directive isOwner 1: ', source, isOwner, type, typeId, memberId)
|
||||
if (isOwner) {
|
||||
return next()
|
||||
}
|
||||
throw new Error(`Unauthorized, must be owner`)
|
||||
},
|
||||
isOwnerOrHasRole: async (next, source, { roles, type }, ctx, ...p) => {
|
||||
const { auth0id: memberId } = await isLoggedIn(ctx)
|
||||
console.log('directive isOwnerOrHasRole 1: ', memberId, roles, type)
|
||||
if(memberId === undefined && role=== undefined) throw new Error(`Not logged in`)
|
||||
if (roles.includes(role)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const { auth0id: typeId } = ctx.request.body.variables
|
||||
const isOwner = await isRequestingMemberAlsoOwner({
|
||||
ctx,
|
||||
memberId,
|
||||
type,
|
||||
typeId
|
||||
})
|
||||
console.log('directive isOwnerOrHasRole 2: ', typeId, memberId, role, isOwner)
|
||||
if (isOwner) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const hasRole = await isRequestingMember({
|
||||
ctx,
|
||||
memberId,
|
||||
type,
|
||||
typeId
|
||||
})
|
||||
if(hasRole){
|
||||
return next()
|
||||
}
|
||||
throw new Error(`Unauthorized, not owner or incorrect role`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { directiveResolvers }
|
||||
@@ -1,29 +0,0 @@
|
||||
const jwksClient = require('jwks-rsa')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const jwks = jwksClient({
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
jwksRequestsPerMinute: 1,
|
||||
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
|
||||
})
|
||||
|
||||
const validateAndParseIdToken = idToken =>
|
||||
new Promise((resolve, reject) => {
|
||||
const { header, payload } = jwt.decode(idToken, { complete: true })
|
||||
if (!header || !header.kid || !payload) reject(new Error('Invalid Token'))
|
||||
jwks.getSigningKey(header.kid, (err, key) => {
|
||||
if (err) reject(new Error('Error getting signing key: ' + err.message))
|
||||
jwt.verify(
|
||||
idToken,
|
||||
key.publicKey,
|
||||
{ algorithms: ['RS256'] },
|
||||
(err, decoded) => {
|
||||
if (err) reject('jwt verify error: ' + err.message)
|
||||
console.log('jwks-verify: ', decoded)
|
||||
resolve(decoded)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
module.exports = validateAndParseIdToken
|
||||
@@ -1,247 +0,0 @@
|
||||
const { GraphQLServer } = require('graphql-yoga')
|
||||
const {
|
||||
Prisma,
|
||||
extractFragmentReplacements,
|
||||
forwardTo
|
||||
} = require('prisma-binding')
|
||||
const { makeExecutableSchema } = require('graphql-tools')
|
||||
const { importSchema } = require('graphql-import')
|
||||
const { checkJwt } = require('./middleware/jwt')
|
||||
const { getMember } = require('./middleware/getMember')
|
||||
const validateAndParseIdToken = require('./helpers/validateAndParseIdToken')
|
||||
const { directiveResolvers } = require('./directives')
|
||||
|
||||
export const createMember = async function (ctx, info, idToken) {
|
||||
console.log('createPrismaMember', idToken)
|
||||
const member = await ctx.db.mutation.upsertMember({
|
||||
where: {
|
||||
auth0id: idToken.sub,
|
||||
},
|
||||
create: {
|
||||
auth0id: idToken.sub,
|
||||
nickname: idToken.nickname,
|
||||
profile: {
|
||||
create: {
|
||||
email: idToken.email,
|
||||
avatar: idToken.picture
|
||||
}
|
||||
}
|
||||
},
|
||||
update: {
|
||||
auth0id: idToken.sub,
|
||||
nickname: idToken.nickname,
|
||||
profile: {
|
||||
update: {
|
||||
email: idToken.email,
|
||||
avatar: idToken.picture
|
||||
}
|
||||
}
|
||||
}
|
||||
}, info)
|
||||
return member
|
||||
}
|
||||
const ctxMember = ctx => ctx.request.member
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
async member(parent, { auth0id }, ctx, info) {
|
||||
return ctx.db.query.member({ where: { auth0id } }, info)
|
||||
},
|
||||
teams(parent, args, ctx, info) {
|
||||
return ctx.db.query.teamsConnection({},
|
||||
`{
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
profile {
|
||||
avatar
|
||||
description
|
||||
motto
|
||||
}
|
||||
owner {
|
||||
nickname
|
||||
visible
|
||||
profile {
|
||||
avatar
|
||||
}
|
||||
}
|
||||
members {
|
||||
role
|
||||
member {
|
||||
nickname
|
||||
visible
|
||||
profile {
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
},
|
||||
async teamById(parent, { id }, ctx, info) {
|
||||
return ctx.db.query.team({ where: { id } }, info)
|
||||
},
|
||||
async teamByName(parent, { name }, ctx, info) {
|
||||
return ctx.db.query.team({ where: { name: name } }, `{ name }`)
|
||||
},
|
||||
async teamByOwner(parent, { ownerId }, ctx, info) {
|
||||
return ctx.db.query.teamsConnection({where: { owner: { auth0id: ownerId }}},
|
||||
`{
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
profile {
|
||||
avatar
|
||||
description
|
||||
motto
|
||||
}
|
||||
owner {
|
||||
auth0id
|
||||
nickname
|
||||
visible
|
||||
profile {
|
||||
avatar
|
||||
}
|
||||
}
|
||||
members {
|
||||
role
|
||||
member {
|
||||
nickname
|
||||
visible
|
||||
profile {
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
},
|
||||
async owner(parent, args, ctx, info) {
|
||||
return ctx.db.query.member({ where: { id: ctxMember(ctx).auth0id } }, info)
|
||||
.catch((res) => {
|
||||
const errors = res.graphQLErrors.map((error) => {
|
||||
return error.message
|
||||
})
|
||||
})
|
||||
},
|
||||
members: forwardTo('db')
|
||||
},
|
||||
Mutation: {
|
||||
async authenticate(parent, { idToken }, ctx, info) {
|
||||
let memberToken = null
|
||||
|
||||
try {
|
||||
memberToken = await validateAndParseIdToken(idToken)
|
||||
} catch (err) {
|
||||
throw new Error(err.message)
|
||||
}
|
||||
|
||||
const auth0id = memberToken.sub
|
||||
let member = await ctx.db.query.member({ where: { auth0id } }, info)
|
||||
|
||||
if (!member) {
|
||||
try {
|
||||
return createMember(ctx, info, memberToken)
|
||||
} catch (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
return member
|
||||
},
|
||||
async createTeam(parent, { name, slug, owner }, ctx, info) {
|
||||
return ctx.db.mutation.createTeam({
|
||||
data: {
|
||||
name: name,
|
||||
slug: slug,
|
||||
owner: {connect:{auth0id: owner}},
|
||||
members: {create: {role: 'OWNER', member: {connect: {auth0id: owner}}}}
|
||||
}
|
||||
}, info)
|
||||
.catch((res) => {
|
||||
console.log('createTeam error: ', res)
|
||||
const errors = res.graphQLErrors.map((error) => {
|
||||
return error.message
|
||||
})
|
||||
})
|
||||
},
|
||||
async deleteTeam(parent, { id }, ctx, info) {
|
||||
return ctx.db.mutation.deleteTeam({ where: { id } }, info)
|
||||
.catch((res) => {
|
||||
console.log('deleteTeam error: ', res)
|
||||
const errors = res.graphQLErrors.map((error) => {
|
||||
return error.message
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const db = new Prisma({
|
||||
fragmentReplacements: extractFragmentReplacements(resolvers),
|
||||
typeDefs: 'src/generated/prisma.graphql',
|
||||
endpoint: process.env.PRISMA_ENDPOINT,
|
||||
secret: process.env.PRISMA_SECRET,
|
||||
debug: true
|
||||
})
|
||||
|
||||
console.log(process.env.PRISMA_ENDPOINT)
|
||||
const schema = makeExecutableSchema({
|
||||
typeDefs: importSchema('src/schema.graphql'),
|
||||
resolvers,
|
||||
resolverValidationOptions :{ requireResolversForResolveType: false },
|
||||
directiveResolvers
|
||||
})
|
||||
|
||||
const server = new GraphQLServer({
|
||||
schema,
|
||||
context: req => ({
|
||||
...req,
|
||||
db
|
||||
})
|
||||
})
|
||||
|
||||
server.express.post(
|
||||
server.options.endpoint,
|
||||
checkJwt,
|
||||
(err, req, res, next) => {
|
||||
console.log('server.express.post:',err, req, res, next, req.token)
|
||||
if (err) return res.status(401).send(err.message)
|
||||
next()
|
||||
}
|
||||
)
|
||||
|
||||
server.express.post(server.options.endpoint, (req, res, done) =>
|
||||
getMember(req, res, done, db)
|
||||
)
|
||||
|
||||
const whitelist = [
|
||||
'http://localhost:1337',
|
||||
'http://0.0.0.0:5000'
|
||||
]
|
||||
const corsOptionsDelegate = (req, callback) => {
|
||||
var corsOptions
|
||||
if (whitelist.indexOf(req.header('Origin')) !== -1) {
|
||||
corsOptions = { origin: true, credentials: true } // reflect (enable) the requested origin in the CORS response
|
||||
} else {
|
||||
corsOptions = { origin: false, credentials: true } // disable CORS for this request
|
||||
}
|
||||
callback(null, corsOptions) // callback expects two parameters: error and options
|
||||
}
|
||||
|
||||
const options = {
|
||||
cors: corsOptionsDelegate,
|
||||
endpoint: '/graphql',
|
||||
subscriptions: '/graphql',
|
||||
playground: '/playground'
|
||||
}
|
||||
|
||||
server.start(options, () => console.log(`Server is running on http://localhost:4000${server.options.endpoint}`))
|
||||
|
||||
module.exports= { createPrismaMember: createPrismaMember }
|
||||
@@ -1,10 +0,0 @@
|
||||
const getMember = async (req, res, next, db) => {
|
||||
if (!req.user) return next()
|
||||
const member = await db.query.member({
|
||||
where: { auth0id: req.user.sub }
|
||||
})
|
||||
req.user = { token: req.member, ...member }
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports = { getMember }
|
||||
@@ -1,24 +0,0 @@
|
||||
const jwt = require('express-jwt')
|
||||
const jwksRsa = require('jwks-rsa')
|
||||
// Authentication middleware. When used, the
|
||||
// if the access token exists, it be verified against
|
||||
// the Auth0 JSON Web Key Set
|
||||
const checkJwt = jwt({
|
||||
// Dynamically provide a signing key
|
||||
// based on the kid in the header and
|
||||
// the signing keys provided by the JWKS endpoint.
|
||||
secret: jwksRsa.expressJwtSecret({
|
||||
cache: true,
|
||||
rateLimit: true,
|
||||
jwksRequestsPerMinute: 1,
|
||||
jwksUri: `https://${process.env.AUTH0_DOMAIN}/.well-known/jwks.json`
|
||||
}),
|
||||
|
||||
// Validate the audience and the issuer.
|
||||
credentialsRequired: false,
|
||||
audience: process.env.AUTH0_AUDIENCE,
|
||||
issuer: process.env.AUTH0_ISSUER,
|
||||
algorithms: [`RS256`]
|
||||
})
|
||||
|
||||
module.exports = { checkJwt }
|
||||
@@ -1,35 +0,0 @@
|
||||
# import Query.members, Query.TeamConnection, Team from "./generated/prisma.graphql"
|
||||
|
||||
directive @isAuthenticated on QUERY | FIELD | MUTATION
|
||||
directive @hasRole(roles: [String]) on QUERY | FIELD | MUTATION
|
||||
directive @isOwner(type: String) on QUERY | MUTATION
|
||||
directive @isOwnerOrHasRole(type: String, roles: [String]) on QUERY | MUTATION
|
||||
|
||||
type Query {
|
||||
teams: TeamConnection
|
||||
teamDetails: [Team!]! @isAuthenticated
|
||||
teamByName(name: String!): Team
|
||||
teamById(teamId: String!): Team
|
||||
teamByOwner(ownerId: String!): TeamConnection
|
||||
owner: Member @isAuthenticated
|
||||
member(auth0id: String!): Member @isAuthenticated
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
authenticate(idToken: String!): Member
|
||||
createTeam(name: String!, slug: String, owner: String): Team @isAuthenticated
|
||||
deleteTeam(id: ID!): Team @isAuthenticated @isOwner(type: "Team")
|
||||
updateProfile(data: MemberProfileInput): Member @isAuthenticated
|
||||
}
|
||||
|
||||
input MemberProfileInput {
|
||||
auth0id: String!
|
||||
nickname: String!
|
||||
anonymous: Boolean
|
||||
visible: Boolean
|
||||
avatar: String
|
||||
email: String
|
||||
repo: String
|
||||
bio: String
|
||||
status: String
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
["@babel/env", {
|
||||
"modules": false
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"syntax-dynamic-import",
|
||||
["transform-react-jsx", { "pragma": "h" }]
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
.git
|
||||
.cache
|
||||
dist
|
||||
build
|
||||
.DS_Store
|
||||
node_modules
|
||||
npm-debug.log
|
||||
@@ -1,11 +0,0 @@
|
||||
PORT=5000
|
||||
NODE_ENV=development
|
||||
|
||||
DEV_GRAPHQL=http://localhost:9090/graphql
|
||||
PROD_GRAPHQL=https://production.com/graphql
|
||||
|
||||
AUTH0_DOMAIN=<auth0-domain>
|
||||
AUTH0_AUDIENCE=<auth0-audience>
|
||||
AUTH0_ISSUER=<auth0-issuer>
|
||||
AUTH0_CLIENTID=<auth0-clientid>
|
||||
AUTH0_CALLBACKURL=<auth0-callbackurl>
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "standard"
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# Build folder js files
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode
|
||||
|
||||
# JetBrains ide
|
||||
.idea
|
||||
|
||||
#Mac specific files
|
||||
.DS_Store
|
||||
@@ -1,29 +0,0 @@
|
||||
FROM mhart/alpine-node
|
||||
|
||||
ENV PORT=5000
|
||||
ENV NODE_ENV=development
|
||||
ENV APP_PATH=/var/www/application
|
||||
|
||||
RUN apk update && apk upgrade && \
|
||||
apk add --no-cache bash git
|
||||
|
||||
# Create app directory
|
||||
RUN mkdir -p $APP_PATH
|
||||
|
||||
# Use app directory as current workdir
|
||||
WORKDIR $APP_PATH
|
||||
|
||||
# Copy package.json to workdir
|
||||
COPY package.json .
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Expose Port
|
||||
EXPOSE $PORT
|
||||
|
||||
# Run command
|
||||
CMD npm run develop
|
||||
@@ -1,108 +0,0 @@
|
||||
# Advance Algos Team Client-side Application
|
||||
|
||||
This is the Advanced Algos Team Module client-side web app a.k.a. single-page-application (SPA). We use Webpack 4 + Docker to develop locally, then build to a distribution bundle that can then be uploaded via FTP, git or used with a continuous integration workflow.
|
||||
|
||||
This webapp's server-side component is the [aateam-api package](../aateam-api#README).
|
||||
|
||||
For more information on the general architecture and related technology of this module, please visit this repos [main documenation](../../docs#README).
|
||||
|
||||
## Getting Started
|
||||
|
||||
Run
|
||||
```
|
||||
npm install
|
||||
```
|
||||
then
|
||||
```
|
||||
npm run develop
|
||||
```
|
||||
|
||||
### Using Docker
|
||||
|
||||
Get latest Docker and Docker Compose: [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/)
|
||||
|
||||
#### Using Docker container for Development
|
||||
|
||||
To run starter kit in development mode with hot code reload execute:
|
||||
|
||||
Run docker container in background:
|
||||
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
If image issues, try running:
|
||||
```
|
||||
docker-compose up --build
|
||||
```
|
||||
then run
|
||||
```
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
To view logs
|
||||
```
|
||||
docker-compose logs -f -t
|
||||
```
|
||||
Hit Ctrl + c to exit logs without stopping service
|
||||
|
||||
To stop service
|
||||
```
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
## Overview
|
||||
The development architecture of the Algobots Community site is to allow both rapid initial development and ongoing rapid-evolution. As with any opinionated workflow, there's a learning curve, but the libraries and architecture of this application have been chosen to maximize collaboration, flexibility, and extensibility.
|
||||
|
||||
### Main Libraries
|
||||
|
||||
- [*Webpack 4*](https://webpack.js.org/):web application bundler. Allows us to us latest newer JS ECMA features that increase productivity and code clarity. Also allows additional libraries such as modular and programmable CSS via SASS/SCSS/LESS. Provides webserver for local development.
|
||||
- [*Bulma CSS Framework*](https://bulma.io/documentation/): Bulma is a highly-customizable CSS-only framework allowing us to quickly prototype the layout of a webapp without being complicated by opinionated functionality represented in libraries such as Bootstrap or Material UI. Grants us the flexibility to create a custom look at top speed whilst minimizing baggage.
|
||||
- [*HyperApp*](https://github.com/hyperapp/hyperapp): Allows a controlled Flux-styled approach to state-management and data-flow through components like React minus the excess boilerplate and learning curve.
|
||||
- [*Docker*](https://www.docker.com/what-docker)
|
||||
|
||||
### File organization
|
||||
|
||||
```
|
||||
. # Top level directory located at your choice
|
||||
├── src
|
||||
│ ├── actions # Actions are functions that change the app state
|
||||
│ ├── assets # images, logos and other static assets
|
||||
│ ├── state # Schema of state — the single source of truth concerning app state.
|
||||
│ ├── styles # SASS/SCSS styles
|
||||
│ ├── utils # Local storage and other utils
|
||||
│ └── views # Main folder for editing site content
|
||||
│ ├─ nav # Nav components — header, footer, etc.
|
||||
│ ├─ pages # Nav components — header, footer, etc.
|
||||
│ │ ├─ landing # Homepage
|
||||
│ │ │ ├─ sections # Homepage sections. Collated in its own index.js
|
||||
│ │ │ └─ index.js # Main homepage component. Pulls in sections
|
||||
│ │ └─ ... # Other page/component folders
|
||||
│ └─ index.js # Main view container. Main router
|
||||
├── html
|
||||
│ └── index.html # index.html template pulled in by Webpack to attach transpiled bundles
|
||||
├── index.js # JS entry point to website
|
||||
│
|
||||
├── tools # utility scripts, mostly for webpack configuration
|
||||
│
|
||||
├── node_modules # created by running `npm install` in this packages root dir
|
||||
├── dist # created by `npm run develop` - a cache of bundled files served by Webpack for local development
|
||||
└── build # created by `npm run build` - location of deployable production bundle
|
||||
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
The simplest deployment is compiling/transpiling the site to a distribution bundle and then uploading the files in the bundle to a web-accessible folder or storage.
|
||||
|
||||
For this application we use Webpack to transpile the application which combines and minimizes all code and assets into an optimized bundle without and of the development code and resources.
|
||||
|
||||
To build, run:
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
Looking in this packages directory, there should be a ./build directory with the final built files that can be uploaded.
|
||||
|
||||
### Deployment instructions
|
||||
|
||||
Coming soon...
|
||||
@@ -1,19 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
ports:
|
||||
- "5000:5000"
|
||||
- "4536:4536"
|
||||
expose:
|
||||
- "5000"
|
||||
- "4536"
|
||||
volumes:
|
||||
- .:/var/www/application
|
||||
- /var/www/application/node_modules
|
||||
container_name: aateam-client
|
||||
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 502 B |
|
Before Width: | Height: | Size: 758 B |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,26 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>AlgoCommunity</title>
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="The AlgoCommunity is a collaborative setting for developing, competing and evolving Advanced Algos financial beings">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#e3493c">
|
||||
<meta name="apple-mobile-web-app-title" content="Algobots Community">
|
||||
<meta name="application-name" content="Algobots Community">
|
||||
<meta name="msapplication-TileColor" content="#e3493c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link href="https://fonts.googleapis.com/css?family=Saira+Condensed:300,600|Saira:300,600&subset=latin-ext,vietnamese" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "Advanced Algos",
|
||||
"short_name": "Advanced Algos",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="1365.333" height="1365.333" viewBox="0 0 1024.000000 1024.000000"><path d="M685.9 27.4c-.2.2-3 .7-6.1 1-12.6 1.5-34.6 8.5-45.2 14.5-2 1.2-5.3 3.1-7.3 4.2-14.3 8.1-30.2 22.3-43.5 38.8-5.9 7.4-16.2 22.1-15.5 22.1.2 0 3.6-1.7 7.5-3.9 17.4-9.6 28.8-12.1 52.2-11.7 13.4.3 17.7.8 23.1 2.5 25.1 8 45.2 36.6 58.9 83.7 1.8 6 3.3 11.6 3.5 12.4 2.3 10.8 3.5 17.1 4 20 .3 1.9 1 6 1.5 9 1.2 7.3 1.8 11.3 2.5 18.5.4 3.3.8 7.6 1.1 9.5 1.6 14.3 2.2 51.4 1 69.5-1.4 21.3-1.5 26-.5 31 .5 2.7 1.2 6.3 1.5 8 2.3 12.9 20.6 69 33.4 102.5 1.6 4.1 4.8 12.4 7.1 18.5 17.1 45.2 47.3 118.6 49.1 119.3.4.2.8.9.8 1.5.1 3.8 18.3 22 29.2 29.1 4.7 3.1 8.8 5.6 9 5.6.3 0-3.2-10.9-7.7-24.3-4.5-13.3-8.4-25.3-8.7-26.7-.4-1.4-2-7.2-3.7-13-4.3-14.5-12.2-45.3-13.7-54-.3-1.4-.7-3.3-1-4.2-.3-1-.7-3-1-4.5-.2-1.6-.9-4.8-1.4-7.3-.5-2.5-1.4-7.2-2-10.5-.5-3.3-1.2-7.1-1.5-8.5-.3-1.4-1-5.7-1.5-9.5-.6-3.9-1.2-8.1-1.5-9.5-.3-2.3-.7-5.4-2-16.3-3.1-25.8-3.8-58.6-1.5-73.2.7-4.4 2.2-10.7 3.5-14 2.3-6.1 9-28.9 9.9-33.5.3-1.4 1.2-5.2 2-8.5s1.7-7.4 1.9-9c.3-1.7 1.4-8.2 2.6-14.6 1.1-6.3 2.3-14.7 2.6-18.5.4-3.8.9-8.3 1.1-9.9 2.2-16.2 2.2-69.4 0-83.5-.2-1.4-.7-5-1.1-8-6.7-56.6-26.4-102.4-53.1-123.1-11.1-8.5-31-16.4-49.9-19.7-9.6-1.7-38.4-2.9-39.6-1.8zM465 215.4c-2.3 3-9.7 11.7-14.5 17-1.1 1.2-5.6 6.2-10 11.1-8.3 9.3-43.9 45.2-53.5 54-51.1 46.9-108.8 86-132.5 90-2.2.4-4.4.8-5 .9-.5.2-2.8.6-5 1.1-24.5 4.7-70 20.4-91 31.3-2.7 1.4-5.2 2.6-5.5 2.7-.7.1-12.3 6.2-16.7 8.7-2.1 1.2-5.4 3.1-7.5 4.2C62.4 471.4 16.3 519 4.3 559.8.9 571.3.5 590 3.4 603.5c2.6 12.1 7.2 25.6 11.8 35 19.6 39.1 52.7 65.8 95.3 76.8 5.5 1.4 12 2.9 14.5 3.2 2.5.4 5.4.9 6.5 1.1 2.8.5 11.7 1.4 19.5 1.9l6.4.4-3.4-2.1c-20.8-12.5-29.7-21.3-39-38.3-1.8-3.3-4.3-7.8-5.5-10-6.1-11.2-8.2-22.8-6.4-35.1 7.4-48 72.3-111.2 159.4-155 10.5-5.3 21-11.2 23.5-13.1 8.7-6.7 26.9-25.3 45.4-46.3 1.7-2 4.9-5.6 7.1-8.1 2.2-2.4 6-6.9 8.5-9.8 2.5-3 5-5.9 5.5-6.5.6-.6 3-3.6 5.5-6.6 6.5-8 7.1-8.6 15.2-18.5 12.7-15.4 51.5-64.5 61.3-77.6 1.1-1.5 5.9-7.8 10.6-14 11.5-15.2 13.3-18.1 16.8-26.9 3-7.7 3.5-9.3 5.1-19 1.2-7.1 1.9-21.9 1.1-22.4-.3-.2-1.7 1.1-3.1 2.8zM563.9 315c-21.5 5.4-43.9 28.6-59.4 61.3-5.2 11-10.1 24.9-12.5 35.7-2 8.8-6.1 16.2-21 37.4-1.8 2.6-17.8 23.2-20.7 26.6-14.7 17.6-22.4 26.2-31.2 35.1-8.7 8.6-12.2 11.3-19.6 15.2-16.7 8.7-25.6 14.5-37 23.8-22.2 18.1-36.8 38.2-41 56.4-1.6 6.6-2.2 17.6-1.1 19.3.3.5.7 2.1 1 3.7.8 4.3 5.3 12.7 9.1 16.8 10.1 11.1 27.7 18.5 50.5 21.3 12.2 1.5 36.1.8 49-1.6 11.6-2 12.6-2.3 20.5-4.5s31.7-2.2 43.5 0c.8.2 4.4.6 8 1 3.6.4 7.4.9 8.4 1.1 1.1.2 4.2.6 6.9.9 2.8.3 5.3.8 5.6.9.3.2 2.9.7 5.6 1 9.1 1.3 39 7.5 47.9 10.1 8.9 2.6 18.5 7 20.6 9.6 1.9 2.3 30.1 16.5 39.5 20 9.1 3.3 26.6 7.7 33.7 8.6 9.2 1.1 23.9.7 30-.7 19.5-4.6 33.3-18.5 36.3-36.7.7-4.2.8-19 .1-20.1-.2-.4-.7-2.4-1-4.6-1-6.1-5.5-18.3-10.3-28.1-4.2-8.3-15.4-26.1-20.1-31.6-5.8-7-14.5-17-18.6-21.4-2.6-2.8-6.5-8-8.7-11.7-6.3-10.8-17.6-35.1-25.4-55.3-1.5-3.9-3.3-8.4-4-10-1.4-3.5-4.9-12.8-5-13.5 0-.3-1.2-3.7-2.5-7.5-1.3-3.9-2.5-7.5-2.6-8-.1-.6-.9-3.5-1.8-6.6-2.6-8.9-2.8-10-2.6-17.4.7-25.5.3-39.3-1.3-49.5-.3-1.4-.8-4.5-1.2-7-.3-2.4-.8-4.7-1-5.1-.2-.3-.7-2.1-1-4-.9-5.4-6.3-20.6-9.7-27.3-7.5-14.9-16.6-24.9-27.5-30.3-6.3-3.1-8.3-3.6-16-3.9-4.8-.2-10.4.1-12.4.6zM340 752.1c-12.3 2.1-18.9 4.4-31.5 10.7-10.5 5.3-11.8 4.4 13 9.2 69 13.5 135.5 34.9 186.8 60.2 24.5 12.1 41 22.8 49.7 32.3 24.3 26.6 37.9 39.4 60 56.6 50.3 39.2 116.2 69.8 162.5 75.5 2.8.3 6.8.9 9 1.3 4.8.9 28.3.6 34-.3 23.2-4 37.1-11.8 56.6-31.7 33-33.8 46.8-81.2 37.4-128.4-3.2-16-8-29.9-16.2-46.5l-4.2-8.5-.1 8c-.1 4.4-.3 8.7-.4 9.5-.2.8-.6 3.8-1 6.6-1.8 14.4-15.1 40.8-26.4 52.7-17.8 18.7-53.6 23.1-99.2 12.2-5.8-1.4-11.5-2.7-12.6-3-1.2-.3-7.7-2.3-14.5-4.6-36.3-12-78-32.8-111.4-55.7-6-4.1-13-8.4-15.5-9.4-2.5-1.1-5.4-2.3-6.5-2.8-5.6-2.4-39.7-11.2-52.5-13.5-1.9-.3-4.2-.7-5-.9-.8-.1-4-.8-7-1.4-9.8-2-26.4-5.1-30-5.6-3-.5-29.1-4.8-37-6.1-1.4-.2-4.3-.7-6.5-1-6.9-1.1-9.8-1.5-15.5-2.5-3-.5-8.2-1.2-11.5-1.6-3.3-.3-6.2-.7-6.5-.9-.3-.2-4.4-.9-9-1.5-4.7-.6-9.6-1.3-11-1.5-1.4-.2-6.3-.9-11-1.5s-9.6-1.3-11-1.5c-5.9-.8-10.8-1.5-15-2-5.1-.6-9.4-1.2-16-2.1-7.1-.9-20.1-1.1-25-.3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,95 +0,0 @@
|
||||
{
|
||||
"name": "aateam-client",
|
||||
"version": "0.0.1",
|
||||
"description": "Advanced Algos Team client-side web app",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"develop": "NODE_ENV=development PORT=5000 webpack-serve --config ./webpack.config.js",
|
||||
"build": "NODE_ENV=production webpack --mode production --config ./webpack.config.js",
|
||||
"lint": "node_modules/.bin/eslint src",
|
||||
"format": "prettier-standard 'src/**/*.js'",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [
|
||||
"hyperapp",
|
||||
"bulma"
|
||||
],
|
||||
"repository": "",
|
||||
"author": "Barry Low <bearcanrun@gmail.com>",
|
||||
"license": "AGPL-3.0",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"autoprefixer": "^8.6.5",
|
||||
"babel-eslint": "8.2.2",
|
||||
"babel-loader": "^8.0.0",
|
||||
"babel-minify-webpack-plugin": "^0.3.1",
|
||||
"babel-plugin-dynamic-import-node": "^1.2.0",
|
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
||||
"babel-plugin-transform-es2015-spread": "^6.22.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
||||
"bulma-o-steps": "^1.0.8",
|
||||
"connect-history-api-fallback": "^1.5.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-standard": "^11.0.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-node": "^6.0.1",
|
||||
"eslint-plugin-promise": "^3.8.0",
|
||||
"eslint-plugin-standard": "^3.1.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"html-webpack-harddisk-plugin": "^0.2.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"koa-connect": "^2.0.1",
|
||||
"mini-css-extract-plugin": "^0.4.2",
|
||||
"minilog": "^3.1.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"optimize-css-assets-webpack-plugin": "^4.0.3",
|
||||
"postcss-loader": "^2.1.6",
|
||||
"prettier-standard": "^8.0.1",
|
||||
"sass-loader": "^6.0.7",
|
||||
"script-ext-html-webpack-plugin": "^2.0.1",
|
||||
"standard": "^12.0.1",
|
||||
"style-loader": "^0.21.0",
|
||||
"uglifyjs-webpack-plugin": "^1.3.0",
|
||||
"webpack": "^4.17.1",
|
||||
"webpack-cli": "^2.1.5",
|
||||
"webpack-hot-client": "^4.1.1",
|
||||
"webpack-merge": "^4.1.4",
|
||||
"webpack-serve": "^1.0.4",
|
||||
"workbox-build": "^3.4.1",
|
||||
"workbox-cli": "^3.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hyperapp/fx": "^0.7.0",
|
||||
"@hyperapp/router": "^0.7.0",
|
||||
"apollo-cache-inmemory": "^1.2.9",
|
||||
"apollo-client": "^2.4.1",
|
||||
"apollo-link": "^1.2.2",
|
||||
"apollo-link-context": "^1.0.8",
|
||||
"apollo-link-error": "^1.1.0",
|
||||
"apollo-link-http": "^1.5.4",
|
||||
"apollo-link-ws": "^1.0.8",
|
||||
"apollo-utilities": "^1.0.20",
|
||||
"auth0-lock": "^11.9.0",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"bulma": "^0.7.1",
|
||||
"bulma-timeline": "^2.0.3",
|
||||
"cross-fetch": "^2.2.2",
|
||||
"dotenv-webpack": "^1.5.7",
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-tag": "^2.9.2",
|
||||
"hyperapp": "^1.2.8",
|
||||
"subscriptions-transport-ws": "^0.9.14"
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint",
|
||||
"ignore": [
|
||||
"dist/",
|
||||
"build/"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"semi": false
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { action, log } from '@hyperapp/fx'
|
||||
import { location } from '@hyperapp/router'
|
||||
|
||||
import { nav } from '../modules/nav/actions'
|
||||
import { team } from '../modules/team/actions'
|
||||
import { algobot } from '../modules/algobots/actions'
|
||||
import { community } from '../modules/community/actions'
|
||||
|
||||
export const actions = {
|
||||
location: location.actions,
|
||||
isLoggedIn: authStatus => async (state, actions) => {
|
||||
authStatus.then((result) => {
|
||||
if (result) {
|
||||
actions.setLoggedIn(result)
|
||||
actions.redirect('dashboard')
|
||||
} else {
|
||||
actions.loggedIn(result)
|
||||
actions.redirect('/')
|
||||
}
|
||||
})
|
||||
},
|
||||
loggedIn: loggedIn => ({ loggedIn }),
|
||||
redirect: redirect => ({ redirect }),
|
||||
setLoggedIn: profile => [
|
||||
log('setLoggedIn', profile, window.location.pathname),
|
||||
action('loggedIn', true),
|
||||
action('setUserProfile', profile),
|
||||
action('redirect', window.location.pathname)
|
||||
],
|
||||
setLoggedOut: () => [
|
||||
log('setLoggedOut', true),
|
||||
action('loggedIn', false),
|
||||
action('setUserProfileEmpty')
|
||||
],
|
||||
setUserProfile: profile => {
|
||||
const user = {
|
||||
id: profile.sub,
|
||||
name: profile.name,
|
||||
username: profile.nickname,
|
||||
avatar: profile.picture,
|
||||
email: profile.email
|
||||
}
|
||||
return { user }
|
||||
},
|
||||
setUserProfileEmpty: () => ({ user: '' }),
|
||||
toggleModal: form => (state, actions) => {
|
||||
return {
|
||||
modal: {
|
||||
content: form,
|
||||
isActive: !state.modal.isActive
|
||||
}
|
||||
}
|
||||
},
|
||||
updateTeamView: () => [
|
||||
action('toggleModal', ''),
|
||||
action('team.form', { form: '', message: '' })
|
||||
],
|
||||
nav,
|
||||
team,
|
||||
algobot,
|
||||
community
|
||||
}
|
||||
|
Before Width: | Height: | Size: 78 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 98 140"><defs><style>.cls-1,.cls-3{fill:none;}.cls-2{clip-path:url(#clip-path);}.cls-3{stroke:#2a96ff;stroke-miterlimit:10;stroke-width:0.5px;}.cls-4{fill:#4d4d4d;}.cls-5{fill:#e3493c;}</style><clipPath id="clip-path"><rect class="cls-1" x="144.54" y="59.59" width="600" height="600"/></clipPath></defs><title>aa-logo-vert</title><g id="Layer_1" data-name="Layer 1"><g class="cls-2"><circle class="cls-3" cx="210.14" cy="243.28" r="277.97"/></g></g><g id="Layer_4" data-name="Layer 4"><path class="cls-4" d="M11.27,108.29l-.38-3H8.62l-.38,3h-3L7.49,93.1h4.57l2.28,15.19ZM9.83,96.08H9.68L8.9,102.6h1.71Z"/><path class="cls-4" d="M18,93.1h3.69a4.53,4.53,0,0,1,2.79.67,3.22,3.22,0,0,1,1,2.11,41.13,41.13,0,0,1,.19,4.82,41,41,0,0,1-.19,4.81,3.19,3.19,0,0,1-1,2.11,4.46,4.46,0,0,1-2.79.67H18Zm3.69,12.43a.89.89,0,0,0,.71-.22,2.31,2.31,0,0,0,.25-1.15c0-.61,0-1.77,0-3.46s0-2.85,0-3.47a2.31,2.31,0,0,0-.25-1.15.89.89,0,0,0-.71-.22H21v9.67Z"/><path class="cls-4" d="M31.16,108.29,29.11,93.1h3l1.25,12.06h.23L34.8,93.1h3l-2.05,15.19Z"/><path class="cls-4" d="M46.35,108.29l-.38-3H43.7l-.38,3h-3L42.57,93.1h4.57l2.28,15.19ZM44.91,96.08h-.15L44,102.6h1.7Z"/><path class="cls-4" d="M58.44,108.29l-2.36-7.62H56v7.62H53.12V93.1h2.8L58.09,100h.11V93.1h2.89v15.19Z"/><path class="cls-4" d="M70.19,108.36a6.43,6.43,0,0,1-1.12.11,4,4,0,0,1-2.61-.71,3.72,3.72,0,0,1-1.1-2.28,31.28,31.28,0,0,1-.26-4.78,29.46,29.46,0,0,1,.28-4.77,3.82,3.82,0,0,1,1.12-2.3,3.9,3.9,0,0,1,2.57-.71,7.72,7.72,0,0,1,1.18.1,3.21,3.21,0,0,1,.85.24v2.53a11.75,11.75,0,0,0-1.48-.11,1.48,1.48,0,0,0-1.05.29,2.16,2.16,0,0,0-.35,1.23c0,.62-.08,1.79-.08,3.5s0,2.87.08,3.5a2.15,2.15,0,0,0,.35,1.22,1.48,1.48,0,0,0,1.05.29,9.89,9.89,0,0,0,1.53-.11v2.47A4.23,4.23,0,0,1,70.19,108.36Z"/><path class="cls-4" d="M75,108.29V93.1h6.09v2.76H78v3.38h2.7V102H78v3.53h3.07v2.76Z"/><path class="cls-4" d="M85.08,93.1h3.69a4.55,4.55,0,0,1,2.79.67,3.16,3.16,0,0,1,1,2.11,38.45,38.45,0,0,1,.2,4.82,38.37,38.37,0,0,1-.2,4.81,3.14,3.14,0,0,1-1,2.11,4.48,4.48,0,0,1-2.79.67H85.08Zm3.69,12.43a.89.89,0,0,0,.71-.22,2.31,2.31,0,0,0,.25-1.15c0-.61,0-1.77,0-3.46s0-2.85,0-3.47a2.31,2.31,0,0,0-.25-1.15.89.89,0,0,0-.71-.22h-.69v9.67Z"/><path class="cls-5" d="M19.79,134.79l-2.1-5.3H9.08L7,134.79H5.24l7.15-18.23h2l7.16,18.23Zm-6.33-16.24h-.14L9.64,127.9h7.5Z"/><path class="cls-5" d="M26.41,134.79V116.56h1.7v16.58H37v1.65Z"/><path class="cls-5" d="M48.1,126.76V125.2h6.2v9.3c-.87.14-1.84.26-2.9.35s-2,.15-2.8.15a12.88,12.88,0,0,1-4.9-.69,3.89,3.89,0,0,1-2.15-2.6,22.5,22.5,0,0,1-.58-6,22.11,22.11,0,0,1,.6-6A3.87,3.87,0,0,1,43.72,117a13.15,13.15,0,0,1,4.88-.67c.88,0,1.85.05,2.92.14s2,.22,2.7.36v1.51a33.49,33.49,0,0,0-4.66-.42,18.69,18.69,0,0,0-4.55.37,2.7,2.7,0,0,0-1.81,1.93,18.81,18.81,0,0,0-.53,5.43,23.5,23.5,0,0,0,.39,5.13,3,3,0,0,0,1.59,2.09,10.15,10.15,0,0,0,4,.55c.56,0,1.25,0,2.07-.08s1.47-.12,2-.19v-6.41Z"/><path class="cls-5" d="M61.91,134.32A3.33,3.33,0,0,1,60,131.86a31.94,31.94,0,0,1-.41-6.19,31.89,31.89,0,0,1,.41-6.18A3.33,3.33,0,0,1,61.91,117a13.45,13.45,0,0,1,5.07-.67A13.42,13.42,0,0,1,72,117a3.33,3.33,0,0,1,1.89,2.47,31.89,31.89,0,0,1,.41,6.18,31.94,31.94,0,0,1-.41,6.19A3.33,3.33,0,0,1,72,134.32,13.38,13.38,0,0,1,67,135,13.4,13.4,0,0,1,61.91,134.32Zm9-1.29a2.34,2.34,0,0,0,1.37-1.86,29.55,29.55,0,0,0,.34-5.5,29.7,29.7,0,0,0-.34-5.5,2.32,2.32,0,0,0-1.37-1.85,13.69,13.69,0,0,0-4-.38,13.73,13.73,0,0,0-4,.38,2.32,2.32,0,0,0-1.37,1.85,28.82,28.82,0,0,0-.34,5.5,28.68,28.68,0,0,0,.34,5.5A2.34,2.34,0,0,0,63,133a13.73,13.73,0,0,0,4,.38A13.69,13.69,0,0,0,70.94,133Z"/><path class="cls-5" d="M82.89,134.91c-1.17-.07-2.18-.15-3-.25v-1.59c.91.08,1.93.16,3,.23s2,.11,2.76.11a17.72,17.72,0,0,0,2.32-.12,4,4,0,0,0,1.49-.49,1.67,1.67,0,0,0,.81-1.07,7.68,7.68,0,0,0,.2-1.92,9.6,9.6,0,0,0-.15-2.07,1.14,1.14,0,0,0-.71-.83,7.63,7.63,0,0,0-2-.42L83,126a4.91,4.91,0,0,1-2.42-.74,2.61,2.61,0,0,1-1-1.47,10.4,10.4,0,0,1-.23-2.45,5.81,5.81,0,0,1,.71-3.17,3.47,3.47,0,0,1,2-1.44,14.24,14.24,0,0,1,3.66-.37c1,0,2,0,3.09.1a19.49,19.49,0,0,1,2.44.29v1.51c-.7-.08-1.55-.16-2.54-.22s-1.9-.09-2.73-.09a15.54,15.54,0,0,0-2.52.16,4.18,4.18,0,0,0-1.51.5,1.74,1.74,0,0,0-.76,1,6,6,0,0,0-.19,1.72,6.83,6.83,0,0,0,.17,1.76,1.32,1.32,0,0,0,.67.82,4.9,4.9,0,0,0,1.57.39l4.51.47a10.3,10.3,0,0,1,2.69.56,2.23,2.23,0,0,1,1.25,1.43,9.9,9.9,0,0,1,.3,2.91,6.84,6.84,0,0,1-.72,3.54,3.17,3.17,0,0,1-2,1.47,14.17,14.17,0,0,1-3.38.32C85.14,135,84.05,135,82.89,134.91Z"/><path class="cls-5" d="M47.08,49.62c1.17-.36,8.61.83,9.61,1.53,3.13,2.21,7.71,3.41,9.55,1.15s-.08-6.13-3-9.1c-.88-.88-3.7-7.75-3.56-9,.42-3.87-.85-8.5-3.73-9s-5.61,3.66-6.3,7.16c-.24,1.22-5,7.11-6.14,7.62-3.48,1.62-6.77,5-5.73,7.69S43.7,50.65,47.08,49.62Z"/><path class="cls-5" d="M60,9.61c4.68.2,6.65,9.72,5.89,17.18-.24,2.37,5.27,15.71,6.24,17.91a7.29,7.29,0,0,0,3,3C71.63,38.07,71.32,30.32,72,28.51c3-8.4,1.71-19.69-2.85-22.32-3.07-1.77-9.79-2.48-14.07,4.56A6.91,6.91,0,0,1,60,9.61Z"/><path class="cls-5" d="M22.61,50.2C20.Line truncated
|
||||
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 502 B |
|
Before Width: | Height: | Size: 758 B |
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,9 +0,0 @@
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#e3493c">
|
||||
<meta name="apple-mobile-web-app-title" content="Advanced Algos">
|
||||
<meta name="application-name" content="Advanced Algos">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -1 +0,0 @@
|
||||
<svg version="1" xmlns="http://www.w3.org/2000/svg" width="1365.333" height="1365.333" viewBox="0 0 1024.000000 1024.000000"><path d="M685.9 27.4c-.2.2-3 .7-6.1 1-12.6 1.5-34.6 8.5-45.2 14.5-2 1.2-5.3 3.1-7.3 4.2-14.3 8.1-30.2 22.3-43.5 38.8-5.9 7.4-16.2 22.1-15.5 22.1.2 0 3.6-1.7 7.5-3.9 17.4-9.6 28.8-12.1 52.2-11.7 13.4.3 17.7.8 23.1 2.5 25.1 8 45.2 36.6 58.9 83.7 1.8 6 3.3 11.6 3.5 12.4 2.3 10.8 3.5 17.1 4 20 .3 1.9 1 6 1.5 9 1.2 7.3 1.8 11.3 2.5 18.5.4 3.3.8 7.6 1.1 9.5 1.6 14.3 2.2 51.4 1 69.5-1.4 21.3-1.5 26-.5 31 .5 2.7 1.2 6.3 1.5 8 2.3 12.9 20.6 69 33.4 102.5 1.6 4.1 4.8 12.4 7.1 18.5 17.1 45.2 47.3 118.6 49.1 119.3.4.2.8.9.8 1.5.1 3.8 18.3 22 29.2 29.1 4.7 3.1 8.8 5.6 9 5.6.3 0-3.2-10.9-7.7-24.3-4.5-13.3-8.4-25.3-8.7-26.7-.4-1.4-2-7.2-3.7-13-4.3-14.5-12.2-45.3-13.7-54-.3-1.4-.7-3.3-1-4.2-.3-1-.7-3-1-4.5-.2-1.6-.9-4.8-1.4-7.3-.5-2.5-1.4-7.2-2-10.5-.5-3.3-1.2-7.1-1.5-8.5-.3-1.4-1-5.7-1.5-9.5-.6-3.9-1.2-8.1-1.5-9.5-.3-2.3-.7-5.4-2-16.3-3.1-25.8-3.8-58.6-1.5-73.2.7-4.4 2.2-10.7 3.5-14 2.3-6.1 9-28.9 9.9-33.5.3-1.4 1.2-5.2 2-8.5s1.7-7.4 1.9-9c.3-1.7 1.4-8.2 2.6-14.6 1.1-6.3 2.3-14.7 2.6-18.5.4-3.8.9-8.3 1.1-9.9 2.2-16.2 2.2-69.4 0-83.5-.2-1.4-.7-5-1.1-8-6.7-56.6-26.4-102.4-53.1-123.1-11.1-8.5-31-16.4-49.9-19.7-9.6-1.7-38.4-2.9-39.6-1.8zM465 215.4c-2.3 3-9.7 11.7-14.5 17-1.1 1.2-5.6 6.2-10 11.1-8.3 9.3-43.9 45.2-53.5 54-51.1 46.9-108.8 86-132.5 90-2.2.4-4.4.8-5 .9-.5.2-2.8.6-5 1.1-24.5 4.7-70 20.4-91 31.3-2.7 1.4-5.2 2.6-5.5 2.7-.7.1-12.3 6.2-16.7 8.7-2.1 1.2-5.4 3.1-7.5 4.2C62.4 471.4 16.3 519 4.3 559.8.9 571.3.5 590 3.4 603.5c2.6 12.1 7.2 25.6 11.8 35 19.6 39.1 52.7 65.8 95.3 76.8 5.5 1.4 12 2.9 14.5 3.2 2.5.4 5.4.9 6.5 1.1 2.8.5 11.7 1.4 19.5 1.9l6.4.4-3.4-2.1c-20.8-12.5-29.7-21.3-39-38.3-1.8-3.3-4.3-7.8-5.5-10-6.1-11.2-8.2-22.8-6.4-35.1 7.4-48 72.3-111.2 159.4-155 10.5-5.3 21-11.2 23.5-13.1 8.7-6.7 26.9-25.3 45.4-46.3 1.7-2 4.9-5.6 7.1-8.1 2.2-2.4 6-6.9 8.5-9.8 2.5-3 5-5.9 5.5-6.5.6-.6 3-3.6 5.5-6.6 6.5-8 7.1-8.6 15.2-18.5 12.7-15.4 51.5-64.5 61.3-77.6 1.1-1.5 5.9-7.8 10.6-14 11.5-15.2 13.3-18.1 16.8-26.9 3-7.7 3.5-9.3 5.1-19 1.2-7.1 1.9-21.9 1.1-22.4-.3-.2-1.7 1.1-3.1 2.8zM563.9 315c-21.5 5.4-43.9 28.6-59.4 61.3-5.2 11-10.1 24.9-12.5 35.7-2 8.8-6.1 16.2-21 37.4-1.8 2.6-17.8 23.2-20.7 26.6-14.7 17.6-22.4 26.2-31.2 35.1-8.7 8.6-12.2 11.3-19.6 15.2-16.7 8.7-25.6 14.5-37 23.8-22.2 18.1-36.8 38.2-41 56.4-1.6 6.6-2.2 17.6-1.1 19.3.3.5.7 2.1 1 3.7.8 4.3 5.3 12.7 9.1 16.8 10.1 11.1 27.7 18.5 50.5 21.3 12.2 1.5 36.1.8 49-1.6 11.6-2 12.6-2.3 20.5-4.5s31.7-2.2 43.5 0c.8.2 4.4.6 8 1 3.6.4 7.4.9 8.4 1.1 1.1.2 4.2.6 6.9.9 2.8.3 5.3.8 5.6.9.3.2 2.9.7 5.6 1 9.1 1.3 39 7.5 47.9 10.1 8.9 2.6 18.5 7 20.6 9.6 1.9 2.3 30.1 16.5 39.5 20 9.1 3.3 26.6 7.7 33.7 8.6 9.2 1.1 23.9.7 30-.7 19.5-4.6 33.3-18.5 36.3-36.7.7-4.2.8-19 .1-20.1-.2-.4-.7-2.4-1-4.6-1-6.1-5.5-18.3-10.3-28.1-4.2-8.3-15.4-26.1-20.1-31.6-5.8-7-14.5-17-18.6-21.4-2.6-2.8-6.5-8-8.7-11.7-6.3-10.8-17.6-35.1-25.4-55.3-1.5-3.9-3.3-8.4-4-10-1.4-3.5-4.9-12.8-5-13.5 0-.3-1.2-3.7-2.5-7.5-1.3-3.9-2.5-7.5-2.6-8-.1-.6-.9-3.5-1.8-6.6-2.6-8.9-2.8-10-2.6-17.4.7-25.5.3-39.3-1.3-49.5-.3-1.4-.8-4.5-1.2-7-.3-2.4-.8-4.7-1-5.1-.2-.3-.7-2.1-1-4-.9-5.4-6.3-20.6-9.7-27.3-7.5-14.9-16.6-24.9-27.5-30.3-6.3-3.1-8.3-3.6-16-3.9-4.8-.2-10.4.1-12.4.6zM340 752.1c-12.3 2.1-18.9 4.4-31.5 10.7-10.5 5.3-11.8 4.4 13 9.2 69 13.5 135.5 34.9 186.8 60.2 24.5 12.1 41 22.8 49.7 32.3 24.3 26.6 37.9 39.4 60 56.6 50.3 39.2 116.2 69.8 162.5 75.5 2.8.3 6.8.9 9 1.3 4.8.9 28.3.6 34-.3 23.2-4 37.1-11.8 56.6-31.7 33-33.8 46.8-81.2 37.4-128.4-3.2-16-8-29.9-16.2-46.5l-4.2-8.5-.1 8c-.1 4.4-.3 8.7-.4 9.5-.2.8-.6 3.8-1 6.6-1.8 14.4-15.1 40.8-26.4 52.7-17.8 18.7-53.6 23.1-99.2 12.2-5.8-1.4-11.5-2.7-12.6-3-1.2-.3-7.7-2.3-14.5-4.6-36.3-12-78-32.8-111.4-55.7-6-4.1-13-8.4-15.5-9.4-2.5-1.1-5.4-2.3-6.5-2.8-5.6-2.4-39.7-11.2-52.5-13.5-1.9-.3-4.2-.7-5-.9-.8-.1-4-.8-7-1.4-9.8-2-26.4-5.1-30-5.6-3-.5-29.1-4.8-37-6.1-1.4-.2-4.3-.7-6.5-1-6.9-1.1-9.8-1.5-15.5-2.5-3-.5-8.2-1.2-11.5-1.6-3.3-.3-6.2-.7-6.5-.9-.3-.2-4.4-.9-9-1.5-4.7-.6-9.6-1.3-11-1.5-1.4-.2-6.3-.9-11-1.5s-9.6-1.3-11-1.5c-5.9-.8-10.8-1.5-15-2-5.1-.6-9.4-1.2-16-2.1-7.1-.9-20.1-1.1-25-.3z"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "Advanced Algos",
|
||||
"short_name": "Advanced Algos",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
import Auth0Lock from 'auth0-lock'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
import { getItem, setItem, removeItem } from '../utils/local-storage'
|
||||
import { validObject, deleteCookie } from '../utils/js-helpers'
|
||||
|
||||
import { actions } from '../actions'
|
||||
import { client } from '../index'
|
||||
|
||||
export const AUTH_CONFIG = {
|
||||
api_audience: process.env.AUTH0_AUDIENCE,
|
||||
domain: process.env.AUTH0_DOMAIN,
|
||||
clientId: process.env.AUTH0_CLIENTID,
|
||||
callbackUrl: process.env.AUTH0_CALLBACK_URL,
|
||||
logoutUrl: process.env.AUTH0_LOGOUT_URL
|
||||
}
|
||||
|
||||
console.log(process.env.AUTH0_CLIENTID)
|
||||
|
||||
const AUTHENTICATE = gql`
|
||||
mutation authenticate($idToken: String!) {
|
||||
authenticate(idToken: $idToken) {
|
||||
id
|
||||
nickname
|
||||
auth0id
|
||||
profile {
|
||||
email
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
const lock = new Auth0Lock(AUTH_CONFIG.clientId, AUTH_CONFIG.domain, {
|
||||
oidcConformant: true,
|
||||
autoclose: true,
|
||||
auth: {
|
||||
sso: true,
|
||||
redirectUrl: window.location.origin + '/callback',
|
||||
responseType: 'token id_token',
|
||||
audience: `${AUTH_CONFIG.api_audience}`,
|
||||
params: {
|
||||
scope: `openid profile email user_metadata app_metadata picture`
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
class Auth {
|
||||
constructor (cb, apolloClient) {
|
||||
this.handleAuthentication()
|
||||
// binds functions to keep this context
|
||||
this.apolloClient = apolloClient
|
||||
this.cb = cb.bind(this)
|
||||
this.login = this.login.bind(this)
|
||||
this.logout = this.logout.bind(this)
|
||||
this.isAuthenticated = this.isAuthenticated.bind(this)
|
||||
this.checkSession = this.checkSession.bind(this)
|
||||
}
|
||||
|
||||
login () {
|
||||
// Call the show method to display the widget.
|
||||
lock.show()
|
||||
}
|
||||
|
||||
handleAuthentication () {
|
||||
// Add a callback for Lock's `authenticated` event
|
||||
lock.on('authenticated', this.setSession.bind(this))
|
||||
// Add a callback for Lock's `authorization_error` event
|
||||
lock.on('authorization_error', err => {
|
||||
console.log(err)
|
||||
const data = { status: `error`, errMessage: err.error }
|
||||
this.cb(data)
|
||||
})
|
||||
}
|
||||
|
||||
checkSession () {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Add a callback for Lock's `authenticated` event
|
||||
lock.checkSession(
|
||||
{
|
||||
responseType: 'token id_token',
|
||||
audience: AUTH_CONFIG.api_audience,
|
||||
scope:
|
||||
'openid email profile read:teams write:teams read:member write:member',
|
||||
connection: 'github',
|
||||
prompt: 'none'
|
||||
},
|
||||
function (err, authResult) {
|
||||
if (!err) {
|
||||
resolve(authResult)
|
||||
} else {
|
||||
reject(err)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
setSession (authResult) {
|
||||
console.log('setSession')
|
||||
if (authResult && authResult.accessToken && authResult.idToken) {
|
||||
// Set the time that the access token will expire at
|
||||
let expiresAt = JSON.stringify(
|
||||
authResult.expiresIn * 1000 + new Date().getTime()
|
||||
)
|
||||
setItem('access_token', authResult.accessToken)
|
||||
setItem('id_token', authResult.idToken)
|
||||
setItem('expires_at', expiresAt)
|
||||
const data = {
|
||||
status: `success`,
|
||||
accessToken: authResult.accessToken,
|
||||
idToken: authResult.idToken,
|
||||
expiresAt
|
||||
}
|
||||
console.log('setSession idTokenPayload: ', authResult.idTokenPayload)
|
||||
|
||||
this.signinOrCreateAccount({ ...data })
|
||||
this.cb(data)
|
||||
console.log(authResult.idTokenPayload)
|
||||
setItem('user', JSON.stringify(authResult.idTokenPayload))
|
||||
if (window.location.href.includes(`callback`)) {
|
||||
window.location.href = '/dashboard'
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async signinOrCreateAccount ({ accessToken, idToken, expiresAt }) {
|
||||
console.log(
|
||||
'signinOrCreateAccount: ',
|
||||
client,
|
||||
this.apolloClient,
|
||||
accessToken,
|
||||
idToken,
|
||||
expiresAt
|
||||
)
|
||||
try {
|
||||
const data = await client.mutate({
|
||||
mutation: AUTHENTICATE,
|
||||
variables: { idToken }
|
||||
})
|
||||
|
||||
console.log('signinOrCreateAccount auth: ', await data)
|
||||
setItem('user', data.data.authenticate.auth0id)
|
||||
if (window.location.href.includes(`callback`)) {
|
||||
window.location.href = '/'
|
||||
} else {
|
||||
window.location.reload()
|
||||
}
|
||||
return data
|
||||
} catch (err) {
|
||||
return console.log('Sign in or create account error: ', err)
|
||||
}
|
||||
}
|
||||
|
||||
logout () {
|
||||
// Clear access token and ID token from local storage
|
||||
removeItem('access_token')
|
||||
removeItem('id_token')
|
||||
removeItem('expires_at')
|
||||
removeItem('user')
|
||||
removeItem('auth0.ssodata')
|
||||
window.localStorage.clear()
|
||||
deleteCookie('ajs_anonymous_id')
|
||||
deleteCookie('ajs_user_id')
|
||||
deleteCookie('current_tenant')
|
||||
actions.setLoggedOut()
|
||||
lock.logout({ returnTo: AUTH_CONFIG.logoutUrl })
|
||||
console.log('logout')
|
||||
}
|
||||
|
||||
async isAuthenticated () {
|
||||
// check session and run Auth0 SS0
|
||||
const getUser = await getItem('user')
|
||||
let user = JSON.parse(getUser)
|
||||
|
||||
if (validObject(user, 'exp') && new Date().getTime() < (user.exp * 1000)) {
|
||||
console.log('handleAuth.user exp: ', user, window.location.href)
|
||||
actions.setLoggedIn(user)
|
||||
return user
|
||||
}
|
||||
console.log('handleAuth.user: ', user, actions, window.location.href)
|
||||
const checkSSO = await this.checkSession()
|
||||
.then(result => {
|
||||
console.log('handleAuth.checksessions: ', result)
|
||||
setItem('user', result.idTokenPayload.sub)
|
||||
// user confirmed, log into client
|
||||
this.setSession(result)
|
||||
return result.idTokenPayload
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('handleAuth.checksessions err: ', err)
|
||||
if (err.error === 'login_required') {
|
||||
// return this.login()
|
||||
}
|
||||
return err
|
||||
})
|
||||
if (
|
||||
(window.location.href.includes(`manage`) ||
|
||||
window.location.href.includes(`profile`)) &&
|
||||
!user
|
||||
) {
|
||||
window.location.href = '/'
|
||||
}
|
||||
|
||||
if (/manage|profile|create|dashboard/.test(window.location.href) && !user) {
|
||||
this.login()
|
||||
}
|
||||
|
||||
console.log('handleAuth.isAuthenticated 2: ', checkSSO, user)
|
||||
if (validObject(checkSSO, 'error')) {
|
||||
return false
|
||||
} else {
|
||||
return checkSSO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Auth
|
||||
@@ -1,154 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { app } from 'hyperapp'
|
||||
import { location } from '@hyperapp/router'
|
||||
import { withFx } from '@hyperapp/fx'
|
||||
|
||||
import { ApolloClient } from 'apollo-client'
|
||||
import { HttpLink } from 'apollo-link-http'
|
||||
import { InMemoryCache } from 'apollo-cache-inmemory'
|
||||
import { ApolloLink, concat, split, Observable } from 'apollo-link'
|
||||
import { onError } from 'apollo-link-error'
|
||||
import { setContext } from 'apollo-link-context'
|
||||
import { WebSocketLink } from 'apollo-link-ws'
|
||||
import { SubscriptionClient } from 'subscriptions-transport-ws'
|
||||
import { getMainDefinition } from 'apollo-utilities'
|
||||
|
||||
import Auth from './auth'
|
||||
|
||||
import log from '../tools/log'
|
||||
import { getItem, setItem, removeItem } from './utils/local-storage'
|
||||
|
||||
import { actions } from './actions/'
|
||||
import { state } from './state/'
|
||||
import { view } from './views/'
|
||||
|
||||
import css from './styles/app.scss'
|
||||
|
||||
const graphqlEndpoint =
|
||||
process.env.NODE_ENV === 'production'
|
||||
? process.env.PROD_GRAPHQL
|
||||
: process.env.DEV_GRAPHQL
|
||||
|
||||
const wsUri = graphqlEndpoint.replace(/^http/, 'ws')
|
||||
|
||||
const globalVar =
|
||||
typeof global !== 'undefined'
|
||||
? global
|
||||
: typeof window !== 'undefined'
|
||||
? window
|
||||
: {}
|
||||
const webSocketImpl = globalVar.WebSocket || globalVar.MozWebSocket
|
||||
|
||||
const wsClient = new SubscriptionClient(
|
||||
wsUri,
|
||||
{
|
||||
reconnect: true
|
||||
},
|
||||
webSocketImpl
|
||||
)
|
||||
|
||||
wsClient.onDisconnected(() => {
|
||||
// console.log('onDisconnected')
|
||||
})
|
||||
|
||||
wsClient.onReconnected(() => {
|
||||
// console.log('onReconnected')
|
||||
})
|
||||
|
||||
const httpLink = new HttpLink({ uri: graphqlEndpoint })
|
||||
|
||||
const link = split(
|
||||
({ query }) => {
|
||||
const { kind, operation } = getMainDefinition(query)
|
||||
return kind === 'OperationDefinition' && operation === 'subscription'
|
||||
},
|
||||
new WebSocketLink(wsClient),
|
||||
httpLink
|
||||
)
|
||||
|
||||
const authRetryLink = onError(({ graphQLErrors, networkError, operation, forward }) => {
|
||||
if (graphQLErrors) {
|
||||
// User access token has expired
|
||||
// console.log('authLink: ', graphQLErrors) // check for error message to intercept and resend with Auth0 access token
|
||||
if (graphQLErrors[0].message === 'Not logged in') {
|
||||
// We assume we have auth0 access token needed to run the async request
|
||||
// Let's refresh token through async request
|
||||
return new Observable(observer => {
|
||||
getItem('access_token')
|
||||
.then(accessToken => {
|
||||
operation.setContext(({ headers = {} }) => ({
|
||||
headers: {
|
||||
// Re-add old headers
|
||||
...headers,
|
||||
// Switch out old access token for new one
|
||||
Authorization: `Bearer ${accessToken}` || null
|
||||
}
|
||||
}))
|
||||
})
|
||||
.then(() => {
|
||||
const subscriber = {
|
||||
next: observer.next.bind(observer),
|
||||
error: observer.error.bind(observer),
|
||||
complete: observer.complete.bind(observer)
|
||||
}
|
||||
|
||||
// Retry last failed request
|
||||
forward(operation).subscribe(subscriber)
|
||||
})
|
||||
.catch(error => {
|
||||
// No auth0 access token available, we force user to login
|
||||
observer.error(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const authLink = setContext((_, { headers }) => {
|
||||
// get the authentication token from local storage if it exists
|
||||
const token = window.localStorage.getItem('access_token')
|
||||
// return the headers to the context so httpLink can read them
|
||||
return {
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: token ? `Bearer ${token}` : ``
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let apolloClient
|
||||
|
||||
const cache = new InMemoryCache().restore(window.__APOLLO_STATE__)
|
||||
|
||||
export const client = new ApolloClient({
|
||||
link: ApolloLink.from([authRetryLink, authLink, link]),
|
||||
cache,
|
||||
connectToDevTools: true
|
||||
})
|
||||
|
||||
export const auth = new Auth(result => console.log('auth result', result), client)
|
||||
|
||||
let frontendReloadCount = 0
|
||||
|
||||
export const main = withFx(app)(
|
||||
state,
|
||||
actions,
|
||||
view,
|
||||
document.getElementById('root')
|
||||
)
|
||||
location.subscribe(main.location)
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (module.hot) {
|
||||
module.hot.accept()
|
||||
|
||||
module.hot.accept('./index', () => {
|
||||
try {
|
||||
log.debug('Updating front-end')
|
||||
frontendReloadCount = (frontendReloadCount || 0) + 1
|
||||
} catch (err) {
|
||||
log(err.stack)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
import { action, throttle } from '@hyperapp/fx'
|
||||
|
||||
import { UniqueAlgobotnameQuery } from '../graphql/UniqueAlgobotnameQuery'
|
||||
import { AddAlgobotMutation } from '../graphql/AddAlgobotMutation'
|
||||
import { GetAlgobotQuery } from '../graphql/GetAlgobotQuery'
|
||||
import { DeleteAlgobotMutation } from '../graphql/DeleteAlgobotMutation'
|
||||
|
||||
import { client } from '../../../index'
|
||||
|
||||
import { mergeDeep } from '../../../utils/js-helpers'
|
||||
|
||||
export const algobot = {
|
||||
form: ({ form, message }) => state => {
|
||||
return {
|
||||
form: {
|
||||
form,
|
||||
message
|
||||
}
|
||||
}
|
||||
},
|
||||
clearForms: () => ({
|
||||
form: {
|
||||
form: 'login',
|
||||
message: ''
|
||||
},
|
||||
createAlgobot: {
|
||||
input: '',
|
||||
unique: false,
|
||||
placeholder: 'Your algobotname'
|
||||
}
|
||||
}),
|
||||
checkAlgobotname: value => [
|
||||
action('throttleAlgobotname', value),
|
||||
action('createAlgobot.input', value)
|
||||
],
|
||||
throttleAlgobotname: value => throttle(750, 'uniqueAlgobotname', value),
|
||||
uniqueAlgobotname: value => async (state, actions) => {
|
||||
const result = await client.query({
|
||||
query: UniqueAlgobotnameQuery,
|
||||
variables: { algobotname: value }
|
||||
})
|
||||
return actions.createAlgobot.unique(result.data.uniqueAlgobotname)
|
||||
},
|
||||
createAlgobot: {
|
||||
input: value => state => {
|
||||
const algobotnameInput = {
|
||||
input: value
|
||||
}
|
||||
return mergeDeep(algobotnameInput, state.createAlgobot)
|
||||
},
|
||||
unique: value => state => {
|
||||
const algobotnameInput = {
|
||||
unique: value
|
||||
}
|
||||
return mergeDeep(algobotnameInput, state.createAlgobot)
|
||||
},
|
||||
error: message => state => {
|
||||
const errorInput = {
|
||||
message: message
|
||||
}
|
||||
return mergeDeep(errorInput, state.createAlgobot)
|
||||
}
|
||||
},
|
||||
profile: profile => (state, actions) => {
|
||||
return {
|
||||
profile
|
||||
}
|
||||
},
|
||||
addAlgobot: result => [
|
||||
action('form', {
|
||||
form: 'addAlgobotNotice',
|
||||
message: `Adding your algobot...`
|
||||
}),
|
||||
action('addAlgobotSubmit', result)
|
||||
],
|
||||
addAlgobotSubmit: algobotForm => async (state, actions) => {
|
||||
algobotForm.preventDefault()
|
||||
algobotForm.target.blur()
|
||||
|
||||
const algobotname = algobotForm.target.form[0].value
|
||||
|
||||
const slug = await algobotname.replace(/[^a-zA-Z0-9]+/g, '-').toLowerCase()
|
||||
const validateSlug = new RegExp('^[a-z0-9-]+$')
|
||||
const valid = await validateSlug.test(slug)
|
||||
await console.log('actions.algobot.addAlgobot: ', valid, slug)
|
||||
if (!valid) {
|
||||
return actions.form({
|
||||
form: 'addAlgobotError',
|
||||
message:
|
||||
'Algobot name not valid. Please only letters, numbers and spaces'
|
||||
})
|
||||
}
|
||||
|
||||
const teamId = algobotForm.target.form[2].value
|
||||
|
||||
if (teamId === '') {
|
||||
return actions.form({
|
||||
form: 'addAlgobotError',
|
||||
message: 'Please create a team first!'
|
||||
})
|
||||
}
|
||||
|
||||
const algobot = {
|
||||
name: algobotname,
|
||||
slug: slug,
|
||||
owner: algobotForm.target.form[1].value,
|
||||
team_id: teamId
|
||||
}
|
||||
|
||||
const uniqueAlgobot = await client.query({
|
||||
query: UniqueAlgobotnameQuery,
|
||||
variables: { algobotname: algobotname }
|
||||
})
|
||||
await console.log(
|
||||
'actions.algobot.addAlgobot: ',
|
||||
algobot,
|
||||
valid,
|
||||
uniqueAlgobot,
|
||||
uniqueAlgobot.data.uniqueAlgobotname
|
||||
)
|
||||
if (!uniqueAlgobot.data.uniqueAlgobotname) {
|
||||
return actions.form({
|
||||
form: 'addAlgobotError',
|
||||
message: 'Sorry, this algobot name is taken. Please create a new one'
|
||||
})
|
||||
}
|
||||
|
||||
const result = await client.mutate({
|
||||
mutation: AddAlgobotMutation,
|
||||
variables: { input: algobot },
|
||||
options: {
|
||||
update: (proxy, { data: { getAlgobotByOwner } }) => {
|
||||
const data = proxy.readQuery({ GetAlgobotQuery })
|
||||
data.getAlgobotByOwner.push(getAlgobotByOwner)
|
||||
proxy.writeQuery({ GetAlgobotQuery, data })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (result.data.addAlgobot !== null) {
|
||||
return actions.addAlgobotSuccess(result.data.addAlgobot)
|
||||
} else {
|
||||
return actions.form({
|
||||
form: 'addAlgobotError',
|
||||
message: `Unable to add Algobot: ${
|
||||
result.data.addAlgobot.errors[0].message
|
||||
}`
|
||||
})
|
||||
}
|
||||
},
|
||||
addAlgobotSuccess: result => [
|
||||
action('form', { form: 'addAlgobotSuccess', message: 'Algobot Added!' }),
|
||||
action('profile', result),
|
||||
action('createAlgobot.input', ''),
|
||||
action('toggleModal', '')
|
||||
],
|
||||
toggleModal: form => (state, actions) => {
|
||||
return {
|
||||
modal: {
|
||||
content: form,
|
||||
isActive: !state.modal.isActive
|
||||
}
|
||||
}
|
||||
},
|
||||
getAlgobots: id => [
|
||||
action('form', {
|
||||
form: 'getAlgobotLoad',
|
||||
message: 'Getting your algobot...'
|
||||
}),
|
||||
action('getAlgobotbyOwner', id)
|
||||
],
|
||||
getAlgobotbyOwner: id => async (state, actions) => {
|
||||
/*
|
||||
const result = await client.query({
|
||||
query: GetAlgobotQuery,
|
||||
variables: { owner: id },
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
|
||||
if (result.data.getAlgobotByOwner !== null) {
|
||||
return actions.getAlgobotSuccess(result.data.getAlgobotByOwner)
|
||||
} else {
|
||||
return actions.getAlgobotNoAlgobot()
|
||||
} */
|
||||
return actions.getAlgobotNoAlgobot()
|
||||
},
|
||||
getAlgobotSuccess: profile => [
|
||||
action('form', { form: 'getAlgobotSuccess', message: 'Your algobot' }),
|
||||
action('profile', profile)
|
||||
],
|
||||
getAlgobotNoAlgobot: profile => [
|
||||
action('form', { form: 'getAlgobotNotice', message: 'No algobot found' }),
|
||||
action('profile', { name: '' })
|
||||
],
|
||||
deleteAlgobot: id => [
|
||||
action('form', {
|
||||
form: 'deleteAlgobotLoad',
|
||||
message: 'Removing your algobot...'
|
||||
}),
|
||||
action('deleteAlgobotSubmit', id)
|
||||
],
|
||||
deleteAlgobotSubmit: id => async (state, actions) => {
|
||||
const result = await client.mutate({
|
||||
mutation: DeleteAlgobotMutation,
|
||||
variables: { id: Number(id) },
|
||||
options: {
|
||||
update: (proxy, { data: { getAlgobotByOwner } }) => {
|
||||
const data = proxy.readQuery({ GetAlgobotQuery })
|
||||
data.getAlgobotByOwner.push(getAlgobotByOwner)
|
||||
proxy.writeQuery({ GetAlgobotQuery, data })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (result.data.deleteAlgobot.id !== null) {
|
||||
return actions.deleteAlgobotSuccess({ name: '' })
|
||||
} else {
|
||||
return actions.form({
|
||||
form: 'deleteAlgobotError',
|
||||
message: 'No algobot found'
|
||||
})
|
||||
}
|
||||
},
|
||||
deleteAlgobotSuccess: profile => [
|
||||
action('form', {
|
||||
form: 'deleteAlgobotSuccess',
|
||||
message: 'Your algobot has been deleted'
|
||||
}),
|
||||
action('profile', profile)
|
||||
]
|
||||
}
|
||||
|
||||
export default algobot
|
||||
@@ -1,17 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const AddAlgobotMutation = gql`
|
||||
mutation addAlgobot($input: AddAlgobotInput!) {
|
||||
addAlgobot(input: $input) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
avatar
|
||||
repo
|
||||
team_id
|
||||
type
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,10 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const DeleteAlgobotMutation = gql`
|
||||
mutation deleteAlgobot($id: Int!) {
|
||||
deleteAlgobot(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,19 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GetAlgobotQuery = gql`
|
||||
query getAlgobotByOwner($owner: Int!) {
|
||||
getAlgobotByOwner(owner: $owner) {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
avatar
|
||||
repo
|
||||
team_id
|
||||
type
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default GetAlgobotQuery
|
||||
@@ -1,10 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const UniqueAlgobotnameQuery = gql`
|
||||
query uniqueAlgobotname($algobotname: String!) {
|
||||
uniqueAlgobotname(algobotname: $algobotname)
|
||||
}
|
||||
`
|
||||
|
||||
export default UniqueAlgobotnameQuery
|
||||
@@ -1,28 +0,0 @@
|
||||
export const algobot = {
|
||||
form: {
|
||||
type: 'login',
|
||||
message: ''
|
||||
},
|
||||
createAlgobot: {
|
||||
input: '',
|
||||
unique: true,
|
||||
placeholder: 'Your algobot name',
|
||||
error: ''
|
||||
},
|
||||
profile: {
|
||||
id: '',
|
||||
name: '',
|
||||
slug: '',
|
||||
owner: '',
|
||||
avatar: '',
|
||||
repo: '',
|
||||
team_id: ''
|
||||
},
|
||||
members: [],
|
||||
modal: {
|
||||
content: '',
|
||||
isActive: false
|
||||
}
|
||||
}
|
||||
|
||||
export default algobot
|
||||
@@ -1,90 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
|
||||
export const AddAlgobotForm = props => (state, actions) => (
|
||||
<form name='addAlgobotForm' key='AddAlgobotForm'>
|
||||
<h1 class='title'>Add An Algobot</h1>
|
||||
<div class='field'>
|
||||
<label class='label'>Enter a algobot name</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
class='input'
|
||||
type='text'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.algobot.createAlgobot.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.algobot.checkAlgobotname(value)
|
||||
}
|
||||
onblur={({ target: { value } }) =>
|
||||
actions.algobot.checkAlgobotname(value)
|
||||
}
|
||||
value={state.algobot.createAlgobot.input}
|
||||
placeholder={state.algobot.createAlgobot.placeholder}
|
||||
/>
|
||||
<span
|
||||
class={`icon is-small is-right ${
|
||||
state.algobot.createAlgobot.unique.input !== '' &&
|
||||
state.algobot.createAlgobot.unique
|
||||
? 'has-text-success'
|
||||
: 'has-text-grey'
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
key='createAlgobotUniqueMessage'
|
||||
class={`has-text-centered ${
|
||||
!state.algobot.createAlgobot.unique ? 'has-text-danger' : 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
This algobotname is already taken. Please create a new one.
|
||||
</p>
|
||||
<p
|
||||
key='addAlgobotMessage'
|
||||
class={`help is-size-4 m-t-hlf m-b-hlf
|
||||
${
|
||||
state.algobot.form.form === 'addAlgobotSuccess'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'addAlgobotNotice'
|
||||
? 'has-text-primary'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'addAlgobotError' ? 'has-text-danger' : ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'addAlgobotSuccess' ||
|
||||
state.algobot.form.form === 'addAlgobotNotice' ||
|
||||
state.algobot.form.form === 'addAlgobotError'
|
||||
? ''
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.algobot.form.message}
|
||||
</p>
|
||||
<div class='control buttons is-centered'>
|
||||
<input type='hidden' name='owner' value={state.user.profile.id} />
|
||||
<input type='hidden' name='team_id' value={state.team.profile.id} />
|
||||
<button
|
||||
onclick={e => actions.algobot.addAlgobot(e)}
|
||||
class='button is-link'
|
||||
>
|
||||
Create Algobot
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
export default AddAlgobotForm
|
||||
@@ -1,164 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
import { Link } from '@hyperapp/router'
|
||||
import { validObject } from '../../../utils/js-helpers'
|
||||
|
||||
export const AlgobotList = props => (state, actions) => {
|
||||
const algobot = state.algobot.profile
|
||||
console.log(state.algobot.form.form, state.algobot.form.message, algobot.name)
|
||||
if (
|
||||
state.algobot.form.form === 'getAlgobotNotice' &&
|
||||
state.algobot.form.message === 'No algobot found' &&
|
||||
algobot.name === '' &&
|
||||
state.team.profile.name === ''
|
||||
) {
|
||||
return (
|
||||
<div class='section' key='algobotList1'>
|
||||
<p class='is-size-4'>
|
||||
You don't have a team yet! Please create one before adding an algobot.
|
||||
</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<Link to='/team' class='button navbar-item'>
|
||||
Go to Team Overview →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
state.algobot.form.form === 'getAlgobotNotice' &&
|
||||
state.algobot.form.message === 'No algobot found' &&
|
||||
algobot.name === ''
|
||||
) {
|
||||
return (
|
||||
<div class='section' key='algobotList2'>
|
||||
<p class='is-size-4'>Algobot creation currently disabled</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<a
|
||||
class='button is-primary is-medium'
|
||||
disabled={true}
|
||||
onclick={() => actions.algobot.toggleModal('AddAlgobot')}
|
||||
>
|
||||
Create an algobot
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
(algobot.name !== '' && algobot.name !== null) ||
|
||||
state.algobot.form.form === 'deleteAlgobotSuccess'
|
||||
) {
|
||||
const initial = algobot.name.substring(0, 1)
|
||||
return (
|
||||
<div class='section' key='algobotList3'>
|
||||
<p
|
||||
class={`is-size-5 card-footer-item
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotSuccess'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotNotice'
|
||||
? 'has-text-primary'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotError'
|
||||
? 'has-text-danger'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotSuccess' ||
|
||||
state.algobot.form.form === 'deleteAlgobotNotice' ||
|
||||
state.algobot.form.form === 'deleteAlgobotError'
|
||||
? ''
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.algobot.form.message}
|
||||
</p>
|
||||
<table class='table is-fullwidth is-striped'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Algobot Name</th>
|
||||
<th>Team</th>
|
||||
<th>Type</th>
|
||||
<th>Owner</th>
|
||||
<th>Repo</th>
|
||||
<th class='has-text-right'>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{algobot.name !== '' ? (
|
||||
<tr>
|
||||
<td>
|
||||
{algobot.avatar !== null && algobot.avatar !== '' ? (
|
||||
<div class='image is-16x16 avatar'>
|
||||
<img src={algobot.avatar} alt={algobot.name} />
|
||||
</div>
|
||||
) : (
|
||||
<div class='image is-16x16 avatar has-text-white is-uppercase is-green is-size-7 is-centered'>
|
||||
{initial}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{algobot.name}</strong>
|
||||
</td>
|
||||
<td>{state.team.profile.name}</td>
|
||||
<td />
|
||||
<td>{state.user.profile.username}</td>
|
||||
<td />
|
||||
<td class='has-text-right'>
|
||||
<button
|
||||
onclick={e =>
|
||||
actions.algobot.deleteAlgobot(
|
||||
state.algobot.profile.id,
|
||||
state.algobot.profile.owner
|
||||
)
|
||||
}
|
||||
class='button is-small is-red'
|
||||
disabled={
|
||||
state.algobot.form.form === 'getAlgobotNotice' &&
|
||||
state.algobot.form.message === 'Removing your algobot...'
|
||||
}
|
||||
>
|
||||
<span class='icon'>
|
||||
<i class='fas fa-trash-alt' />
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class='section' key='algobotList4'>
|
||||
<p class='is-size-4'>{state.algobot.form.message}</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<span class='icon has-text-primary is-large'>
|
||||
<i class='fas fa-spinner fa-pulse' />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AlgobotList
|
||||
@@ -1,121 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
import { Link } from '@hyperapp/router'
|
||||
import { validObject } from '../../../utils/js-helpers'
|
||||
|
||||
export const AlgobotPanel = props => (state, actions) => {
|
||||
const algobot = validObject(state.algobot.profile, 'edges') ? state.team.profile.edges : ''
|
||||
|
||||
if (algobot === '') {
|
||||
return (
|
||||
<div
|
||||
class='section'
|
||||
key='algobotList1'
|
||||
class='is-marginless is-paddingless'
|
||||
>
|
||||
<h2 class='title is-size-4 m-t-1'>Algobots</h2>
|
||||
<p class='is-size-5'>
|
||||
Create a team first,<br />then setup your algobot!
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
state.algobot.form.form === 'getAlgobotNotice' &&
|
||||
state.algobot.form.message === 'No algobot found' &&
|
||||
algobot.name === ''
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
class='section'
|
||||
key='algobotList2'
|
||||
class='is-marginless is-paddingless p-l-1 p-r-1'
|
||||
>
|
||||
<h2 class='title is-size-4 m-t-1'>Algobots</h2>
|
||||
<p class='is-size-5'>Algobot creation currently disabled</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<a
|
||||
class='button is-primary is-medium'
|
||||
disabled={true}
|
||||
onclick={() => actions.algobot.toggleModal('AddAlgobot')}
|
||||
>
|
||||
Create an algobot
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
(algobot.name !== '' && algobot.name !== null) ||
|
||||
state.algobot.form.form === 'deleteAlgobotSuccess'
|
||||
) {
|
||||
const initial = algobot.name.substring(0, 1)
|
||||
return (
|
||||
<div
|
||||
class='section'
|
||||
key='algobotList3'
|
||||
class='is-marginless is-paddingless'
|
||||
>
|
||||
<h2 class='title is-size-4 m-t-1'>Algobots</h2>
|
||||
<table class='table is-fullwidth is-striped is-narrow'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Name</th>
|
||||
<th>Team</th>
|
||||
<th>Owner</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{algobot.name !== '' ? (
|
||||
<tr>
|
||||
<td>
|
||||
{algobot.avatar !== null && algobot.avatar !== '' ? (
|
||||
<div class='image is-16x16 avatar'>
|
||||
<img src={algobot.avatar} alt={algobot.name} />
|
||||
</div>
|
||||
) : (
|
||||
<div class='image is-16x16 avatar has-text-white is-uppercase is-green is-size-7 is-centered'>
|
||||
{initial}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td class='is-size-6'>
|
||||
<strong>{algobot.name}</strong>
|
||||
</td>
|
||||
<td class='is-size-6'>{state.team.profile.name}</td>
|
||||
<td class='is-size-6'>{state.user.profile.username}</td>
|
||||
</tr>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class='section'
|
||||
key='algobotList4'
|
||||
class='is-marginless is-paddingless'
|
||||
>
|
||||
<h2 class='title is-size-4 m-t-1'>Algobots</h2>
|
||||
<p class='is-size-4'>{state.algobot.form.message}</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<span class='icon has-text-primary is-large'>
|
||||
<i class='fas fa-spinner fa-pulse' />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AlgobotPanel
|
||||
@@ -1,5 +0,0 @@
|
||||
import { AddAlgobotForm } from './add-algobot'
|
||||
import { AlgobotList } from './algobot-list'
|
||||
import { AlgobotPanel } from './algobot-panel'
|
||||
|
||||
export { AddAlgobotForm, AlgobotList, AlgobotPanel }
|
||||
@@ -1,569 +0,0 @@
|
||||
import { action, throttle, delay } from '@hyperapp/fx'
|
||||
|
||||
import { client } from '../../../index'
|
||||
import { UniqueUsernameQuery } from '../graphql/UniqueUsernameQuery'
|
||||
import { RegisterSubmitMutation } from '../graphql/RegisterSubmit'
|
||||
import { VerifyEmailMutation } from '../graphql/VerifyEmail'
|
||||
import { FinishRegisterMutation } from '../graphql/FinishRegisterMutation'
|
||||
import { ForgotPassSubmitMutation } from '../graphql/ForgotPass'
|
||||
import { ResetPassSubmitMutation } from '../graphql/ResetPass'
|
||||
|
||||
import { mergeDeep } from '../../../utils/js-helpers'
|
||||
|
||||
const emailRegex = new RegExp(
|
||||
"^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
|
||||
)
|
||||
|
||||
export const auth = {
|
||||
form: ({ form, message }) => state => {
|
||||
return {
|
||||
form: {
|
||||
form,
|
||||
message
|
||||
}
|
||||
}
|
||||
},
|
||||
clearForms: () => ({
|
||||
form: {
|
||||
form: 'login',
|
||||
message: ''
|
||||
},
|
||||
username: {
|
||||
input: '',
|
||||
placeholder: 'Your username or email',
|
||||
valid: false
|
||||
},
|
||||
createUsername: {
|
||||
input: '',
|
||||
placeholder: 'Create a username',
|
||||
valid: false,
|
||||
unique: false
|
||||
},
|
||||
email: {
|
||||
input: '',
|
||||
placeholder: 'Your email address',
|
||||
valid: false
|
||||
},
|
||||
registerEmail: {
|
||||
input: ''
|
||||
},
|
||||
password: {
|
||||
input: '',
|
||||
placeholder: 'Your password',
|
||||
valid: false
|
||||
},
|
||||
createPassword: {
|
||||
input: '',
|
||||
placeholder: 'Your new password',
|
||||
secure: false,
|
||||
valid: false
|
||||
},
|
||||
confirmPassword: {
|
||||
input: '',
|
||||
placeholder: 'Confirm your password',
|
||||
matches: false,
|
||||
valid: false
|
||||
},
|
||||
forgotEmail: {
|
||||
input: '',
|
||||
placeholder: 'Enter your email address',
|
||||
valid: false
|
||||
},
|
||||
resetPassword: {
|
||||
input: '',
|
||||
placeholder: 'Set a new password',
|
||||
strength: '',
|
||||
valid: false
|
||||
},
|
||||
confirmResetPassword: {
|
||||
input: '',
|
||||
placeholder: 'Confirm your new password',
|
||||
matches: false,
|
||||
valid: false
|
||||
},
|
||||
finishRegister: {
|
||||
load: false,
|
||||
error: false,
|
||||
message: '',
|
||||
success: false
|
||||
}
|
||||
}),
|
||||
username: {
|
||||
input: value => state => {
|
||||
const usernameInput = {
|
||||
input: value
|
||||
}
|
||||
return mergeDeep(usernameInput, state.username)
|
||||
},
|
||||
error: message => state => {
|
||||
const errorInput = {
|
||||
message: message
|
||||
}
|
||||
return mergeDeep(errorInput, state.username)
|
||||
}
|
||||
},
|
||||
email: {
|
||||
input: value => state => {
|
||||
const emailInput = {
|
||||
input: value,
|
||||
valid: emailRegex.test(value)
|
||||
}
|
||||
return mergeDeep(emailInput, state.email)
|
||||
},
|
||||
sent: value => state => {
|
||||
const emailInput = {
|
||||
input: value
|
||||
}
|
||||
return mergeDeep(emailInput, state.email)
|
||||
}
|
||||
},
|
||||
registerEmail: {
|
||||
input: value => state => {
|
||||
const emailInput = {
|
||||
input: value
|
||||
}
|
||||
return mergeDeep(emailInput, state.registerEmail)
|
||||
}
|
||||
},
|
||||
password: {
|
||||
input: value => state => {
|
||||
const passwordInput = {
|
||||
input: value,
|
||||
valid: value !== ''
|
||||
}
|
||||
return mergeDeep(passwordInput, state.password)
|
||||
},
|
||||
error: message => state => {
|
||||
const errorInput = {
|
||||
message: message
|
||||
}
|
||||
return mergeDeep(errorInput, state.password)
|
||||
}
|
||||
},
|
||||
checkUsername: value => [
|
||||
action('throttleUsername', value),
|
||||
action('createUsername.input', value)
|
||||
],
|
||||
throttleUsername: value => throttle(750, 'uniqueUsername', value),
|
||||
uniqueUsername: value => async (state, actions) => {
|
||||
const result = await client.query({
|
||||
query: UniqueUsernameQuery,
|
||||
variables: { username: value }
|
||||
})
|
||||
|
||||
const unique = result.data.uniqueUsername
|
||||
if (unique) {
|
||||
return actions.setUsername(unique)
|
||||
} else {
|
||||
return actions.setUsernameError(unique)
|
||||
}
|
||||
},
|
||||
setUsername: value => [
|
||||
action('createUsername.setUnique', value),
|
||||
action('createUsername.error', '')
|
||||
],
|
||||
setUsernameError: value => [
|
||||
action('createUsername.setUnique', value),
|
||||
action('createUsername.error', 'This username already exists')
|
||||
],
|
||||
createUsername: {
|
||||
input: value => state => {
|
||||
const usernameInput = {
|
||||
input: value
|
||||
}
|
||||
return mergeDeep(usernameInput, state.createUsername)
|
||||
},
|
||||
setUnique: value => state => {
|
||||
const usernameInput = {
|
||||
unique: value
|
||||
}
|
||||
return mergeDeep(usernameInput, state.createUsername)
|
||||
},
|
||||
error: message => state => {
|
||||
const errorInput = {
|
||||
message: message
|
||||
}
|
||||
return mergeDeep(errorInput, state.createUsername)
|
||||
}
|
||||
},
|
||||
createPassword: {
|
||||
input: value => state => {
|
||||
let passStrength = 'weak'
|
||||
const strongRegex = new RegExp(
|
||||
'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{10,})'
|
||||
)
|
||||
const mediumRegex = new RegExp(
|
||||
'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})'
|
||||
)
|
||||
if (mediumRegex.test(value)) {
|
||||
passStrength = 'medium'
|
||||
}
|
||||
if (strongRegex.test(value)) {
|
||||
passStrength = 'strong'
|
||||
}
|
||||
const passwordInput = {
|
||||
input: value,
|
||||
strength: passStrength
|
||||
}
|
||||
return mergeDeep(passwordInput, state.createPassword)
|
||||
},
|
||||
error: message => state => {
|
||||
const errorInput = {
|
||||
message: message
|
||||
}
|
||||
return mergeDeep(errorInput, state.createPassword)
|
||||
}
|
||||
},
|
||||
confirmPassword: {
|
||||
input: ({ value, confirm }) => state => {
|
||||
const passwordInput = {
|
||||
input: value,
|
||||
matches: value === confirm
|
||||
}
|
||||
return mergeDeep(passwordInput, state.confirmPassword)
|
||||
}
|
||||
},
|
||||
register: registerForm => [
|
||||
action('form', {
|
||||
form: 'registerLoad',
|
||||
message: 'Submitting registration...'
|
||||
}),
|
||||
action('registerSubmit', registerForm)
|
||||
],
|
||||
registerSubmit: registerForm => async (state, actions) => {
|
||||
registerForm.preventDefault()
|
||||
registerForm.target.blur()
|
||||
|
||||
const email = registerForm.target.form[0].value
|
||||
|
||||
if (!state.email.valid) {
|
||||
return action('form', {
|
||||
form: 'registerSignup',
|
||||
message: 'Please submit a valid email address'
|
||||
})
|
||||
}
|
||||
const result = await client.mutate({
|
||||
mutation: RegisterSubmitMutation,
|
||||
variables: { input: { email } }
|
||||
})
|
||||
|
||||
if (
|
||||
result.data.registerSubmit.user !== '' &&
|
||||
result.data.registerSubmit.user !== null
|
||||
) {
|
||||
return actions.registerSubmitSuccess(result)
|
||||
} else {
|
||||
return actions.form({
|
||||
form: 'registerError',
|
||||
message: result.data.registerSubmit.errors[0].message
|
||||
})
|
||||
}
|
||||
},
|
||||
registerSubmitSuccess: result => [
|
||||
action('form', {
|
||||
form: 'registerSuccess',
|
||||
message:
|
||||
'To continue registration, please check your email for further instructions'
|
||||
}),
|
||||
action('email.input', '')
|
||||
],
|
||||
verifyRegister: token => [
|
||||
action('form', { form: 'loading', message: 'Verifying registration...' }),
|
||||
action('verifyEmail', token)
|
||||
],
|
||||
verifyEmail: token => async (state, actions) => {
|
||||
const result = await client.mutate({
|
||||
mutation: VerifyEmailMutation,
|
||||
variables: { token: token }
|
||||
})
|
||||
|
||||
const email = await result.data.verifyEmail.user.email
|
||||
console.log('actions.auth.verifyEmail: ', email)
|
||||
if (email !== '' && email !== null) {
|
||||
return actions.verifySuccess(email)
|
||||
} else {
|
||||
return actions.form({
|
||||
form: 'error',
|
||||
message: `Could not verify token: ${
|
||||
result.data.verifyEmail.errors[0].message
|
||||
}`
|
||||
})
|
||||
}
|
||||
},
|
||||
verifySuccess: email => [
|
||||
action('registerEmail.input', email),
|
||||
action('form', {
|
||||
form: 'verifySuccess',
|
||||
message: 'Continue registration...'
|
||||
})
|
||||
],
|
||||
finishRegister: {
|
||||
load: value => state => {
|
||||
const newState = {
|
||||
load: value
|
||||
}
|
||||
return mergeDeep(newState, state.finishRegister)
|
||||
},
|
||||
error: value => state => {
|
||||
const newState = {
|
||||
error: value
|
||||
}
|
||||
return mergeDeep(newState, state.finishRegister)
|
||||
},
|
||||
message: value => state => {
|
||||
const newState = {
|
||||
message: value
|
||||
}
|
||||
return mergeDeep(newState, state.finishRegister)
|
||||
},
|
||||
success: value => state => {
|
||||
const newState = {
|
||||
success: value
|
||||
}
|
||||
return mergeDeep(newState, state.finishRegister)
|
||||
}
|
||||
},
|
||||
finish: registerForm => [
|
||||
action('finishRegister.load', true),
|
||||
action('finishRegister.message', 'Completing registration...'),
|
||||
action('finishSubmit', registerForm)
|
||||
],
|
||||
finishSubmit: registerForm => async (state, actions) => {
|
||||
registerForm.preventDefault()
|
||||
registerForm.target.blur()
|
||||
|
||||
const username = registerForm.target.form[0].value
|
||||
const password = registerForm.target.form[1].value
|
||||
const confirm = registerForm.target.form[2].value
|
||||
const email = registerForm.target.form[3].value
|
||||
console.log(
|
||||
'actions.auth.finishSubmit: ',
|
||||
username,
|
||||
password,
|
||||
confirm,
|
||||
email
|
||||
)
|
||||
|
||||
var checkUsername = await client.query({
|
||||
query: UniqueUsernameQuery,
|
||||
variables: { username: username }
|
||||
})
|
||||
|
||||
if (!checkUsername) {
|
||||
actions.finishRegister.load(false)
|
||||
actions.finishRegister.error(true)
|
||||
actions.finishRegister.message('Username already exist.')
|
||||
return false
|
||||
}
|
||||
|
||||
if (password !== confirm) {
|
||||
actions.finishRegister.load(false)
|
||||
actions.finishRegister.error(true)
|
||||
actions.finishRegister.message('Passwords do not match')
|
||||
actions.confirmPassword.input({ value: confirm, confirm: password })
|
||||
return false
|
||||
}
|
||||
|
||||
if (checkUsername && password === confirm) {
|
||||
const result = await client.mutate({
|
||||
mutation: FinishRegisterMutation,
|
||||
variables: { input: { username, email, password } }
|
||||
})
|
||||
if (result.data.finishRegister.user !== null) {
|
||||
actions.finishRegister.load(false)
|
||||
actions.finishRegister.error(false)
|
||||
actions.finishRegister.success(true)
|
||||
actions.finishRegister.message(
|
||||
'Registration complete. Logging you in...'
|
||||
)
|
||||
return actions.form({
|
||||
form: 'finishSuccess',
|
||||
message: 'Registration complete. Logging you in...'
|
||||
})
|
||||
} else {
|
||||
actions.finishRegister.load(false)
|
||||
actions.finishRegister.error(true)
|
||||
actions.finishRegister.message(
|
||||
`${result.data.finishRegister.errors[0].field}: ${
|
||||
result.data.finishRegister.errors[0].message
|
||||
}`
|
||||
)
|
||||
actions.confirmPassword.input({ value: confirm, confirm: password })
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return state
|
||||
},
|
||||
finishSubmitError: result => [
|
||||
action('form', { form: 'finishError', message: `Error: ${result}` })
|
||||
],
|
||||
checkEmailUsername: value => [
|
||||
action('throttleUsername', value),
|
||||
action('createUsername.input', value)
|
||||
],
|
||||
throttleEmailUsername: value => throttle(750, 'uniqueUsername', value),
|
||||
forgotEmail: {
|
||||
input: value => state => {
|
||||
const usernameInput = {
|
||||
input: value
|
||||
}
|
||||
return mergeDeep(usernameInput, state.forgotEmail)
|
||||
},
|
||||
error: message => state => {
|
||||
const errorInput = {
|
||||
message: message
|
||||
}
|
||||
return mergeDeep(errorInput, state.forgotEmail)
|
||||
},
|
||||
valid: value => state => {
|
||||
const emailInput = {
|
||||
valid: emailRegex.test(value)
|
||||
}
|
||||
return mergeDeep(emailInput, state.forgotEmail)
|
||||
}
|
||||
},
|
||||
forgotPass: forgotPassForm => [
|
||||
action('form', {
|
||||
form: 'forgotPassLoad',
|
||||
message: 'Submitting password reset...'
|
||||
}),
|
||||
action('forgotPassSubmit', forgotPassForm)
|
||||
],
|
||||
forgotPassSubmit: forgotPassForm => async (state, actions) => {
|
||||
forgotPassForm.preventDefault()
|
||||
forgotPassForm.target.blur()
|
||||
|
||||
const email = forgotPassForm.target.form[0].value
|
||||
console.log('actions.auth.forgotPassSubmit: ', email)
|
||||
if (!emailRegex.test(email)) {
|
||||
return action('form', {
|
||||
form: 'registerSignup',
|
||||
message: 'Please submit a valid email address'
|
||||
})
|
||||
}
|
||||
const result = await client.mutate({
|
||||
mutation: ForgotPassSubmitMutation,
|
||||
variables: { input: { email: email } }
|
||||
})
|
||||
console.log('actions.auth.forgotPassSubmit 2: ', result)
|
||||
if (
|
||||
result.data.forgotPassword !== '' &&
|
||||
result.data.forgotPassword !== null
|
||||
) {
|
||||
if (result.data.forgotPassword === 'Success') {
|
||||
return actions.form({
|
||||
form: 'forgotPassSuccess',
|
||||
message: 'Please check your email for further instructions'
|
||||
})
|
||||
}
|
||||
return actions.form({
|
||||
form: 'forgotPassError',
|
||||
message: `Failed to reset password: ${result.data.forgotPassword}`
|
||||
})
|
||||
}
|
||||
},
|
||||
resetPassword: {
|
||||
input: value => state => {
|
||||
let passStrength = 'weak'
|
||||
const strongRegex = new RegExp(
|
||||
'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{10,})'
|
||||
)
|
||||
const mediumRegex = new RegExp(
|
||||
'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})'
|
||||
)
|
||||
if (mediumRegex.test(value)) {
|
||||
passStrength = 'medium'
|
||||
}
|
||||
if (strongRegex.test(value)) {
|
||||
passStrength = 'strong'
|
||||
}
|
||||
const passwordInput = {
|
||||
input: value,
|
||||
strength: passStrength
|
||||
}
|
||||
return mergeDeep(passwordInput, state.resetPassword)
|
||||
},
|
||||
error: message => state => {
|
||||
const errorInput = {
|
||||
message: message
|
||||
}
|
||||
return mergeDeep(errorInput, state.resetPassword)
|
||||
}
|
||||
},
|
||||
confirmResetPassword: {
|
||||
input: ({ value, confirm }) => state => {
|
||||
const passwordInput = {
|
||||
input: value,
|
||||
matches: value === confirm
|
||||
}
|
||||
return mergeDeep(passwordInput, state.confirmResetPassword)
|
||||
}
|
||||
},
|
||||
resetPass: resetPassForm => [
|
||||
action('form', {
|
||||
form: 'resetPassLoad',
|
||||
message: 'Updating your password...'
|
||||
}),
|
||||
action('resetPassSubmit', resetPassForm)
|
||||
],
|
||||
resetPassSubmit: resetPassForm => async (state, actions) => {
|
||||
resetPassForm.preventDefault()
|
||||
resetPassForm.target.blur()
|
||||
|
||||
const password = resetPassForm.target.form[0].value
|
||||
const confirm = resetPassForm.target.form[1].value
|
||||
const token = resetPassForm.target.form[2].value
|
||||
console.log(
|
||||
'actions.auth.resetPassSubmit: ',
|
||||
password,
|
||||
confirm,
|
||||
token,
|
||||
resetPassForm
|
||||
)
|
||||
if (password !== confirm) {
|
||||
return action('form', {
|
||||
form: 'resetPassError',
|
||||
message: 'Passwords do not match'
|
||||
})
|
||||
}
|
||||
const result = await client.mutate({
|
||||
mutation: ResetPassSubmitMutation,
|
||||
variables: {
|
||||
input: {
|
||||
password: password,
|
||||
passwordConfirmation: confirm,
|
||||
token: token
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log('actions.auth.resetPassSubmit 2: ', result)
|
||||
if (
|
||||
result.data.resetPassword !== '' &&
|
||||
result.data.resetPassword !== null
|
||||
) {
|
||||
if (result.data.resetPassword.errors === null) {
|
||||
actions.form({
|
||||
form: 'resetPassDone',
|
||||
message: 'Redirecting to Login...'
|
||||
})
|
||||
return actions.delayResetRedirect()
|
||||
}
|
||||
return actions.form({
|
||||
form: 'resetPassError',
|
||||
message: `Failed to reset password: ${
|
||||
result.data.resetPassword.errors[0].message
|
||||
}`
|
||||
})
|
||||
}
|
||||
},
|
||||
delayResetRedirect: () => delay(3000, 'resetPassSuccess'),
|
||||
resetPassSuccess: resetPassForm => [
|
||||
action('form', {
|
||||
form: 'resetPassSuccess',
|
||||
message: 'Redirecting to Login...'
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
export default auth
|
||||
@@ -1,18 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const FinishRegisterMutation = gql`
|
||||
mutation finishRegister($input: RegisterUserInput!) {
|
||||
finishRegister(input: $input) {
|
||||
user {
|
||||
id
|
||||
email
|
||||
username
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,8 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const ForgotPassSubmitMutation = gql`
|
||||
mutation forgotPassword($input: ForgotPasswordInput!) {
|
||||
forgotPassword(input: $input)
|
||||
}
|
||||
`
|
||||
@@ -1,53 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const RegisterSubmitMutation = gql`
|
||||
mutation registerSubmit($input: RegisterSubmitInput!) {
|
||||
registerSubmit(input: $input) {
|
||||
user {
|
||||
id
|
||||
email
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const UserInfo = gql`
|
||||
fragment UserInfo on User {
|
||||
id
|
||||
username
|
||||
role
|
||||
isActive
|
||||
email
|
||||
profile {
|
||||
firstName
|
||||
lastName
|
||||
fullName
|
||||
}
|
||||
auth {
|
||||
certificate {
|
||||
serial
|
||||
}
|
||||
facebook {
|
||||
fbId
|
||||
displayName
|
||||
}
|
||||
google {
|
||||
googleId
|
||||
displayName
|
||||
}
|
||||
github {
|
||||
ghId
|
||||
displayName
|
||||
}
|
||||
linkedin {
|
||||
lnId
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,13 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const ResetPassSubmitMutation = gql`
|
||||
mutation resetPassword($input: ResetPasswordInput!) {
|
||||
resetPassword(input: $input) {
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,10 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const UniqueUsernameQuery = gql`
|
||||
query uniqueUsername($username: String!) {
|
||||
uniqueUsername(username: $username)
|
||||
}
|
||||
`
|
||||
|
||||
export default UniqueUsernameQuery
|
||||
@@ -1,16 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const VerifyEmailMutation = gql`
|
||||
mutation verifyEmail($token: String!) {
|
||||
verifyEmail(token: $token) {
|
||||
user {
|
||||
email
|
||||
}
|
||||
errors {
|
||||
field
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,67 +0,0 @@
|
||||
export const auth = {
|
||||
form: {
|
||||
type: 'login',
|
||||
message: ''
|
||||
},
|
||||
username: {
|
||||
input: '',
|
||||
placeholder: 'Your username or email',
|
||||
valid: false
|
||||
},
|
||||
createUsername: {
|
||||
input: '',
|
||||
placeholder: 'Create a username',
|
||||
valid: false,
|
||||
unique: true
|
||||
},
|
||||
email: {
|
||||
input: '',
|
||||
placeholder: 'Your email address',
|
||||
valid: false
|
||||
},
|
||||
registerEmail: {
|
||||
input: ''
|
||||
},
|
||||
password: {
|
||||
input: '',
|
||||
placeholder: 'Your password',
|
||||
valid: false
|
||||
},
|
||||
createPassword: {
|
||||
input: '',
|
||||
placeholder: 'Your new password',
|
||||
strength: '',
|
||||
valid: false
|
||||
},
|
||||
confirmPassword: {
|
||||
input: '',
|
||||
placeholder: 'Confirm your password',
|
||||
matches: false,
|
||||
valid: false
|
||||
},
|
||||
forgotEmail: {
|
||||
input: '',
|
||||
placeholder: 'Enter your email address or username',
|
||||
valid: false
|
||||
},
|
||||
resetPassword: {
|
||||
input: '',
|
||||
placeholder: 'Set a new password',
|
||||
strength: '',
|
||||
valid: false
|
||||
},
|
||||
confirmResetPassword: {
|
||||
input: '',
|
||||
placeholder: 'Confirm your new password',
|
||||
matches: false,
|
||||
valid: false
|
||||
},
|
||||
finishRegister: {
|
||||
load: false,
|
||||
error: false,
|
||||
message: '',
|
||||
success: false
|
||||
}
|
||||
}
|
||||
|
||||
export default auth
|
||||
@@ -1,261 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
|
||||
export const FinishRegisterForm = props => (state, actions) => (
|
||||
<form name='finishRegisterForm' key='finishRegisterForm'>
|
||||
<div class='field'>
|
||||
<label class='label'>New Username:</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
name='new-username'
|
||||
class='input'
|
||||
type='text'
|
||||
autocomplete='username'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.createUsername.placeholder}
|
||||
oninput={({ target: { value } }) => actions.auth.checkUsername(value)}
|
||||
onblur={({ target: { value } }) => actions.auth.checkUsername(value)}
|
||||
value={state.auth.createUsername.input}
|
||||
placeholder={state.auth.createUsername.placeholder}
|
||||
/>
|
||||
<span
|
||||
key='createUsernameUserIcon'
|
||||
class={`icon is-small is-left ${
|
||||
state.auth.createUsername.unique ? 'has-text-success' : ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-user' />
|
||||
</span>
|
||||
<span
|
||||
key='createUsernameCheckIcon'
|
||||
class={`icon is-small is-right ${
|
||||
state.auth.createUsername.unique ? 'has-text-success' : ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
<p
|
||||
key='createUsernameUniqueMessage'
|
||||
class={`m-t-1 has-text-centered ${
|
||||
state.auth.createUsername.input !== '' &&
|
||||
state.auth.createUsername.error !== null &&
|
||||
state.auth.createUsername.error !== '' &&
|
||||
!state.auth.createUsername.unique
|
||||
? 'has-text-danger'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
This username is already taken. Please create a new one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='field'>
|
||||
<label class='label'>New Password:</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
name='new-password'
|
||||
class='input'
|
||||
type='password'
|
||||
autocomplete='new-password'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.createPassword.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.auth.createPassword.input(value)
|
||||
}
|
||||
onblur={({ target: { value } }) =>
|
||||
actions.auth.createPassword.input(value)
|
||||
}
|
||||
value={state.auth.createPassword.input}
|
||||
placeholder={state.auth.createPassword.placeholder}
|
||||
/>
|
||||
<span
|
||||
key='createPasswordLockIcon'
|
||||
class={`icon is-small is-left
|
||||
${
|
||||
state.auth.createPassword.strength === 'weak'
|
||||
? 'has-text-danger'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.createPassword.strength === 'medium'
|
||||
? 'has-text-warning'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.createPassword.strength === 'strong'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
>
|
||||
<i class='fas fa-lock' />
|
||||
</span>
|
||||
<span
|
||||
key='createPasswordCheckIcon'
|
||||
class={`icon is-small is-right ${
|
||||
state.auth.createPassword.strength === 'strong'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
<p
|
||||
key='createPasswordStrengthMessage'
|
||||
class={`m-t-1 has-text-centered ${
|
||||
state.auth.createPassword.strength !== null &&
|
||||
state.auth.createPassword.input !== ''
|
||||
? 'has-text-primary'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
Password strength is {state.auth.createPassword.strength}
|
||||
</p>
|
||||
<p
|
||||
key='createPasswordStrengthMessage2'
|
||||
class={`m-t-1 has-text-centered ${
|
||||
state.auth.createPassword.strength === 'weak' ||
|
||||
state.auth.createPassword.strength === 'medium'
|
||||
? 'has-text-primary'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
<span class='is-italic is-size-6'>
|
||||
At least 10 characters, upper & lower case letters<br />+ a
|
||||
number + a special char [! @ # $ % ^ & *]
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='field'>
|
||||
<label class='label'>Confirm Password:</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
name='confirm-password'
|
||||
class='input'
|
||||
type='password'
|
||||
autocomplete='new-confirm-password'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.confirmPassword.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.auth.confirmPassword.input({
|
||||
value: value,
|
||||
confirm: state.auth.createPassword.input
|
||||
})
|
||||
}
|
||||
onblur={({ target: { value } }) =>
|
||||
actions.auth.confirmPassword.input({
|
||||
value: value,
|
||||
confirm: state.auth.createPassword.input
|
||||
})
|
||||
}
|
||||
value={state.auth.confirmPassword.input}
|
||||
placeholder={state.auth.confirmPassword.placeholder}
|
||||
/>
|
||||
<span
|
||||
key='confirmPasswordLockIcon'
|
||||
class={`icon is-small is-left
|
||||
${
|
||||
state.auth.confirmPassword.strength === 'weak'
|
||||
? 'has-text-danger'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.confirmPassword.strength === 'medium'
|
||||
? 'has-text-warning'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.confirmPassword.strength === 'strong'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
>
|
||||
<i class='fas fa-lock' />
|
||||
</span>
|
||||
<span
|
||||
key='confirmPasswordCheckIcon'
|
||||
class={`icon is-small is-right ${
|
||||
state.auth.confirmPassword.matches ? 'has-text-success' : ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
<p
|
||||
key='confirmPasswordMatchesMessage'
|
||||
class={`m-t-1 has-text-centered ${
|
||||
state.auth.confirmPassword.matches
|
||||
? 'has-text-primary'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
Passwords match!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
key='finishRegisterMessage'
|
||||
class={`help
|
||||
${state.auth.finishRegister.success ? 'has-text-success' : ''}
|
||||
${state.auth.finishRegister.error ? 'has-text-danger' : ''}
|
||||
${state.auth.finishRegister.load ? 'has-text-primary' : ''}
|
||||
${
|
||||
(state.auth.finishRegister.load ||
|
||||
state.auth.finishRegister.error ||
|
||||
state.auth.finishRegister.success) &&
|
||||
state.auth.finishRegister.success !== ''
|
||||
? 'is-size-4 m-t-hlf m-b-hlf'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.auth.finishRegister.message}
|
||||
</p>
|
||||
<div class='control buttons is-centered'>
|
||||
<input
|
||||
type='hidden'
|
||||
name='email'
|
||||
value={state.auth.registerEmail.input}
|
||||
/>
|
||||
<button
|
||||
onclick={e => actions.auth.finish(e)}
|
||||
class={`button is-primary ${
|
||||
state.auth.finishRegister.load ? 'is-loading' : ''
|
||||
}`}
|
||||
aria-label='Finish Account Creation'
|
||||
disabled={
|
||||
!(
|
||||
state.auth.createUsername.unique &&
|
||||
state.auth.createPassword.strength === 'strong' &&
|
||||
state.auth.confirmPassword.matches
|
||||
)
|
||||
}
|
||||
>
|
||||
Finish Account Creation
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
export default FinishRegisterForm
|
||||
@@ -1,79 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
import { Link } from '@hyperapp/router'
|
||||
|
||||
export const ForgotPassForm = props => (state, actions) => (
|
||||
<form name='forgotPassForm' key='forgotPassForm'>
|
||||
<div class='field'>
|
||||
<label class='label'>Enter your Email Address</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
class='input'
|
||||
type='text'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.forgotEmail.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.auth.forgotEmail.input(value)
|
||||
}
|
||||
value={state.auth.forgotEmail.input}
|
||||
placeholder={state.auth.forgotEmail.placeholder}
|
||||
/>
|
||||
<span
|
||||
class={`icon is-small is-left ${
|
||||
state.auth.forgotEmail.valid ? 'has-text-success' : ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-user' />
|
||||
</span>
|
||||
<span
|
||||
class={`icon is-small is-right ${
|
||||
state.auth.forgotEmail.valid
|
||||
? 'has-text-success'
|
||||
: 'has-text-light-grey'
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
class={`help is-size-4 m-t-hlf m-b-hlf
|
||||
${state.auth.form.form === 'forgotPassLoad' ? 'is-primary' : ''}
|
||||
${state.auth.form.form === 'forgotPassSuccess' ? 'is-success' : ''}
|
||||
${state.auth.form.form === 'forgotPassError' ? 'is-danger' : ''}
|
||||
${
|
||||
state.auth.form.form === 'forgotPassLoad' ||
|
||||
state.auth.form.form === 'forgotPassSuccess' ||
|
||||
state.auth.form.form === 'forgotPassError'
|
||||
? ''
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.auth.form.message}
|
||||
</p>
|
||||
</div>
|
||||
<div class='control'>
|
||||
<button
|
||||
onclick={e => actions.auth.forgotPass(e)}
|
||||
class={`button is-primary ${
|
||||
state.auth.form.form === 'forgotPassLoad' ? 'is-loading' : ''
|
||||
}`}
|
||||
aria-label='Send New Password'
|
||||
disabled={state.auth.form.form === 'forgotPassLoad'}
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<Link to='/login' class='is-link is-size-6 is-pulled-right'>
|
||||
Back to Login →
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
export default ForgotPassForm
|
||||
@@ -1,13 +0,0 @@
|
||||
import LoginForm from './login'
|
||||
import RegisterForm from './register'
|
||||
import FinishRegisterForm from './finish-register'
|
||||
import ForgotPassForm from './forgot-pass-form'
|
||||
import ResetPassForm from './reset-pass-form'
|
||||
|
||||
export {
|
||||
LoginForm,
|
||||
RegisterForm,
|
||||
FinishRegisterForm,
|
||||
ForgotPassForm,
|
||||
ResetPassForm
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
import { Link } from '@hyperapp/router'
|
||||
|
||||
export const LoginForm = props => (state, actions) => (
|
||||
<form name='loginForm' key='LoginForm'>
|
||||
<div class='field'>
|
||||
<label class='label'>Username or Email</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
class='input'
|
||||
type='text'
|
||||
autocomplete='username'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.username.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.auth.username.input(value)
|
||||
}
|
||||
value={state.auth.username.input}
|
||||
placeholder={state.auth.username.placeholder}
|
||||
/>
|
||||
<span class='icon is-small is-left'>
|
||||
<i class='fas fa-user' />
|
||||
</span>
|
||||
<span class='icon is-small is-right is-invisible'>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='field'>
|
||||
<label class='label'>Password</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
class='input'
|
||||
type='password'
|
||||
autocomplete='current-password'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.password.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.auth.password.input(value)
|
||||
}
|
||||
value={state.auth.password.input}
|
||||
placeholder={state.auth.password.placeholder}
|
||||
/>
|
||||
<span class='icon is-small is-left'>
|
||||
<i class='fas fa-lock' />
|
||||
</span>
|
||||
<span
|
||||
class={`icon is-small is-right ${
|
||||
state.user.form.form === 'loginError' ? '' : 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-exclamation-triangle' />
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
key='loginMessage'
|
||||
class={`help is-size-4 m-t-hlf m-b-hlf
|
||||
${state.user.form.form === 'loginSuccess' ? 'has-text-success' : ''}
|
||||
${state.user.form.form === 'loginNotice' ? 'has-text-primary' : ''}
|
||||
${state.user.form.form === 'loginError' ? 'has-text-danger' : ''}
|
||||
${
|
||||
state.user.form.form === 'loginSuccess' ||
|
||||
state.user.form.form === 'loginNotice' ||
|
||||
state.user.form.form === 'loginError'
|
||||
? ''
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.user.form.message}
|
||||
</p>
|
||||
</div>
|
||||
<div class='control'>
|
||||
<button onclick={e => actions.user.loginSubmit(e)} class='button is-link'>
|
||||
Submit
|
||||
</button>
|
||||
<Link to='/forgot-pass' class='is-link is-size-6 is-pulled-right'>
|
||||
Forgot password?
|
||||
</Link>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
export default LoginForm
|
||||
@@ -1,71 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
|
||||
export const RegisterForm = props => (state, actions) => (
|
||||
<form name='registerForm' key='registerForm'>
|
||||
<div class='field'>
|
||||
<label class='label'>Email Address</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
class='input'
|
||||
type='text'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.email.placeholder}
|
||||
oninput={({ target: { value } }) => actions.auth.email.input(value)}
|
||||
value={state.auth.email.input}
|
||||
placeholder={state.auth.email.placeholder}
|
||||
/>
|
||||
<span
|
||||
class={`icon is-small is-left ${
|
||||
state.auth.email.valid ? 'has-text-success' : ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-user' />
|
||||
</span>
|
||||
<span
|
||||
class={`icon is-small is-right ${
|
||||
state.auth.email.valid ? 'has-text-success' : 'has-text-light-grey'
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
</div>
|
||||
<p
|
||||
class={`help is-size-4 m-t-hlf m-b-hlf
|
||||
${state.auth.form.form === 'registerLoad' ? 'is-primary' : ''}
|
||||
${state.auth.form.form === 'registerSuccess' ? 'is-success' : ''}
|
||||
${state.auth.form.form === 'registerError' ? 'is-danger' : ''}
|
||||
${
|
||||
state.auth.form.form === 'registerLoad' ||
|
||||
state.auth.form.form === 'registerSuccess' ||
|
||||
state.auth.form.form === 'registerError'
|
||||
? ''
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.auth.form.message}
|
||||
</p>
|
||||
</div>
|
||||
<div class='control buttons is-centered'>
|
||||
<button
|
||||
onclick={e => actions.auth.register(e)}
|
||||
class={`button is-primary ${
|
||||
state.auth.form.form === 'registerLoad' ? 'is-loading' : ''
|
||||
}`}
|
||||
aria-label='Register'
|
||||
disabled={state.auth.form.form === 'registerLoad'}
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
export default RegisterForm
|
||||
@@ -1,200 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
|
||||
export const ResetPassForm = ({ token }) => (state, actions) => (
|
||||
<form name='resetPassForm' key='resetPassForm'>
|
||||
<div class='field'>
|
||||
<label class='label'>New Password:</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
name='new-password'
|
||||
class='input'
|
||||
type='password'
|
||||
autocomplete='new-password'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.resetPassword.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.auth.resetPassword.input(value)
|
||||
}
|
||||
onblur={({ target: { value } }) =>
|
||||
actions.auth.resetPassword.input(value)
|
||||
}
|
||||
value={state.auth.resetPassword.input}
|
||||
placeholder={state.auth.resetPassword.placeholder}
|
||||
/>
|
||||
<span
|
||||
key='resetPasswordLockIcon'
|
||||
class={`icon is-small is-left
|
||||
${
|
||||
state.auth.resetPassword.strength === 'weak'
|
||||
? 'has-text-danger'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.resetPassword.strength === 'medium'
|
||||
? 'has-text-warning'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.resetPassword.strength === 'strong'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
>
|
||||
<i class='fas fa-lock' />
|
||||
</span>
|
||||
<span
|
||||
key='resetPasswordCheckIcon'
|
||||
class={`icon is-small is-right ${
|
||||
state.auth.resetPassword.strength === 'strong'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
<p
|
||||
key='resetPasswordStrengthMessage'
|
||||
class={`${
|
||||
state.auth.resetPassword.strength !== null &&
|
||||
state.auth.resetPassword.input !== ''
|
||||
? 'm-t-1 has-text-centered has-text-primary'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
Password strength is {state.auth.resetPassword.strength}
|
||||
</p>
|
||||
<p
|
||||
key='resetPasswordStrengthMessage2'
|
||||
class={`${
|
||||
state.auth.resetPassword.strength === 'weak' ||
|
||||
state.auth.resetPassword.strength === 'medium'
|
||||
? 'm-t-1 has-text-centered has-text-primary'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
<span class='is-italic is-size-6'>
|
||||
At least 10 characters, upper & lower case letters<br />+ a
|
||||
number + a special char [! @ # $ % ^ & *]
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class='field'>
|
||||
<label class='label'>Confirm Password:</label>
|
||||
<div class='control has-icons-left has-icons-right'>
|
||||
<input
|
||||
name='confirm-password'
|
||||
class='input'
|
||||
type='password'
|
||||
autocomplete='new-confirm-password'
|
||||
onupdate={(element, oldAttributes) => {
|
||||
if (
|
||||
oldAttributes.value !== element.value ||
|
||||
(oldAttributes.value !== '' && element.value === '')
|
||||
) {
|
||||
element.focus()
|
||||
}
|
||||
}}
|
||||
aria-label={state.auth.confirmResetPassword.placeholder}
|
||||
oninput={({ target: { value } }) =>
|
||||
actions.auth.confirmResetPassword.input({
|
||||
value: value,
|
||||
confirm: state.auth.resetPassword.input
|
||||
})
|
||||
}
|
||||
onblur={({ target: { value } }) =>
|
||||
actions.auth.confirmResetPassword.input({
|
||||
value: value,
|
||||
confirm: state.auth.resetPassword.input
|
||||
})
|
||||
}
|
||||
value={state.auth.confirmResetPassword.input}
|
||||
placeholder={state.auth.confirmResetPassword.placeholder}
|
||||
/>
|
||||
<span
|
||||
key='confirmResetPasswordLockIcon'
|
||||
class={`icon is-small is-left
|
||||
${
|
||||
state.auth.confirmResetPassword.strength === 'weak'
|
||||
? 'has-text-danger'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.confirmResetPassword.strength === 'medium'
|
||||
? 'has-text-warning'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.auth.confirmResetPassword.strength === 'strong'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}
|
||||
`}
|
||||
>
|
||||
<i class='fas fa-lock' />
|
||||
</span>
|
||||
<span
|
||||
key='confirmResetPasswordCheckIcon'
|
||||
class={`icon is-small is-right ${
|
||||
state.auth.confirmResetPassword.matches ? 'has-text-success' : ''
|
||||
}`}
|
||||
>
|
||||
<i class='fas fa-check' />
|
||||
</span>
|
||||
<p
|
||||
key='confirmResetPasswordMatchesMessage'
|
||||
class={`${
|
||||
state.auth.confirmResetPassword.matches
|
||||
? 'm-t-1 has-text-centered has-text-primary'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
Passwords match!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
key='resetPassMessage'
|
||||
class={`help
|
||||
${state.auth.form.form === 'resetPassDone' ? 'has-text-success' : ''}
|
||||
${state.auth.form.form === 'resetPassError' ? 'has-text-danger' : ''}
|
||||
${
|
||||
state.auth.form.form === 'resetPassDone' ||
|
||||
state.auth.form.form === 'resetPassError' ||
|
||||
state.auth.form.form === 'resetPassLoad'
|
||||
? ' is-size-4 m-t-hlf m-b-hlf has-text-centered'
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.auth.form.message}
|
||||
</p>
|
||||
<div class='control buttons is-centered'>
|
||||
<input type='hidden' name='token' value={token} />
|
||||
<button
|
||||
onclick={e => actions.auth.resetPass(e)}
|
||||
class={`button is-primary ${
|
||||
state.auth.form.form === 'resetPassLoad' ? 'is-loading' : ''
|
||||
}`}
|
||||
disabled={
|
||||
!(
|
||||
state.auth.resetPassword.strength === 'strong' &&
|
||||
state.auth.confirmResetPassword.matches
|
||||
)
|
||||
}
|
||||
>
|
||||
Reset Password
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
export default ResetPassForm
|
||||
@@ -1,94 +0,0 @@
|
||||
import { action } from '@hyperapp/fx'
|
||||
|
||||
import { GetAllTeamsQuery } from '../graphql/GetAllTeamsQuery'
|
||||
import { GetAllAlgobotsQuery } from '../graphql/GetAllAlgobotsQuery'
|
||||
|
||||
import { client } from '../../../index'
|
||||
|
||||
export const community = {
|
||||
form: ({ form, message }) => state => {
|
||||
return {
|
||||
form: {
|
||||
form,
|
||||
message
|
||||
}
|
||||
}
|
||||
},
|
||||
clearForms: () => ({
|
||||
form: {
|
||||
form: 'login',
|
||||
message: ''
|
||||
}
|
||||
}),
|
||||
toggleModal: form => (state, actions) => {
|
||||
return {
|
||||
modal: {
|
||||
content: form,
|
||||
isActive: !state.modal.isActive
|
||||
}
|
||||
}
|
||||
},
|
||||
teams: teams => ({ teams }),
|
||||
algobots: algobots => ({ algobots }),
|
||||
getTeams: id => [
|
||||
action('form', {
|
||||
form: 'getTeamsLoad',
|
||||
message: 'Getting community teams...'
|
||||
}),
|
||||
action('getAllTeams')
|
||||
],
|
||||
getAllTeams: () => async (state, actions) => {
|
||||
const result = await client.query({
|
||||
query: GetAllTeamsQuery,
|
||||
variables: { limit: 10, after: 0 },
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
console.log(result.data.teams)
|
||||
if (result.data.teams.edges.length > 0) {
|
||||
return actions.getAllTeamsSuccess(result.data.teams)
|
||||
} else {
|
||||
return actions.noTeams()
|
||||
}
|
||||
},
|
||||
getAllTeamsSuccess: teams => [
|
||||
action('form', { form: 'getAllTeamsSuccess', message: 'Community Teams' }),
|
||||
action('teams', teams)
|
||||
],
|
||||
noTeams: profile => [
|
||||
action('form', { form: 'getAlgobotsNotice', message: 'No teams found' }),
|
||||
action('teams', [])
|
||||
],
|
||||
getAlgobots: id => [
|
||||
action('form', {
|
||||
form: 'getAlgobotsLoad',
|
||||
message: 'Getting community algobots...'
|
||||
}),
|
||||
action('getAllAlgobots', id)
|
||||
],
|
||||
getAllAlgobots: () => async (state, actions) => {
|
||||
const result = await client.query({
|
||||
query: GetAllAlgobotsQuery,
|
||||
variables: { limit: 10, after: 0 },
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
console.log(result.data.getAlgobots)
|
||||
if (result.data.getAlgobots !== null) {
|
||||
return actions.getAllAlgobotsSuccess(result.data.getAlgobots)
|
||||
} else {
|
||||
return actions.noAlgobots()
|
||||
}
|
||||
},
|
||||
getAllAlgobotsSuccess: algobots => [
|
||||
action('form', {
|
||||
form: 'getAlgobotSuccess',
|
||||
message: 'Community Algobots'
|
||||
}),
|
||||
action('algobots', algobots)
|
||||
],
|
||||
noAlgobots: profile => [
|
||||
action('form', { form: 'getAlgobotsNotice', message: 'No algobots found' }),
|
||||
action('algobots', [])
|
||||
]
|
||||
}
|
||||
|
||||
export default community
|
||||
@@ -1,29 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GetAllAlgobotsQuery = gql`
|
||||
query algobots($limit: Int!, $after: Int) {
|
||||
algobots(limit: $limit, after: $after) {
|
||||
totalCount
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
owner
|
||||
avatar
|
||||
repo
|
||||
team_id
|
||||
type
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default GetAllAlgobotsQuery
|
||||
@@ -1,40 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export const GetAllTeamsQuery = gql`
|
||||
query teamsQuery {
|
||||
teams {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
slug
|
||||
profile {
|
||||
avatar
|
||||
description
|
||||
motto
|
||||
}
|
||||
owner {
|
||||
nickname
|
||||
visible
|
||||
profile {
|
||||
avatar
|
||||
}
|
||||
}
|
||||
members {
|
||||
role
|
||||
member {
|
||||
nickname
|
||||
visible
|
||||
profile {
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default GetAllTeamsQuery
|
||||
@@ -1,15 +0,0 @@
|
||||
export const community = {
|
||||
form: {
|
||||
form: 'login',
|
||||
message: ''
|
||||
},
|
||||
filter: {},
|
||||
teams: [],
|
||||
algobots: [],
|
||||
modal: {
|
||||
content: 'AddAlgobot',
|
||||
isActive: false
|
||||
}
|
||||
}
|
||||
|
||||
export default community
|
||||
@@ -1,141 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
import { Link } from '@hyperapp/router'
|
||||
|
||||
export const AlgobotsList = props => (state, actions) => {
|
||||
const algobot = state.algobot.profile
|
||||
console.log(state.algobot.form.form, state.algobot.form.message, algobot.name)
|
||||
|
||||
if (
|
||||
state.algobot.form.form === 'getAlgobotNotice' &&
|
||||
state.algobot.form.message === 'No algobot found' &&
|
||||
algobot.name === ''
|
||||
) {
|
||||
return (
|
||||
<div class='section' key='algobotsList2'>
|
||||
<p class='is-size-4'>You don't have an algobot yet!</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<a
|
||||
class='button is-primary is-medium'
|
||||
onclick={() => actions.algobot.toggleModal('AddAlgobot')}
|
||||
>
|
||||
Create an algobot
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
(algobot.name !== '' && algobot.name !== null) ||
|
||||
state.algobot.form.form === 'deleteAlgobotSuccess'
|
||||
) {
|
||||
const initial = algobot.name.substring(0, 1)
|
||||
return (
|
||||
<div class='section' key='algobotsList3'>
|
||||
<p
|
||||
class={`is-size-5 card-footer-item
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotSuccess'
|
||||
? 'has-text-success'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotNotice'
|
||||
? 'has-text-primary'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotError'
|
||||
? 'has-text-danger'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.algobot.form.form === 'deleteAlgobotSuccess' ||
|
||||
state.algobot.form.form === 'deleteAlgobotNotice' ||
|
||||
state.algobot.form.form === 'deleteAlgobotError'
|
||||
? ''
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.algobot.form.message}
|
||||
</p>
|
||||
<table class='table is-fullwidth is-striped'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Algobot Name</th>
|
||||
<th>Team</th>
|
||||
<th>Type</th>
|
||||
<th>Owner</th>
|
||||
<th>Repo</th>
|
||||
<th class='has-text-right'>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{algobot.name !== '' ? (
|
||||
<tr>
|
||||
<td>
|
||||
{algobot.avatar !== null && algobot.avatar !== '' ? (
|
||||
<div class='image is-16x16 avatar'>
|
||||
<img src={algobot.avatar} alt={algobot.name} />
|
||||
</div>
|
||||
) : (
|
||||
<div class='image is-16x16 avatar has-text-white is-uppercase is-green is-size-7 is-centered'>
|
||||
{initial}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{algobot.name}</strong>
|
||||
</td>
|
||||
<td>{state.team.profile.name}</td>
|
||||
<td />
|
||||
<td>{state.user.profile.username}</td>
|
||||
<td />
|
||||
<td class='has-text-right'>
|
||||
<button
|
||||
onclick={e =>
|
||||
actions.algobot.deleteAlgobot(
|
||||
state.algobot.profile.id,
|
||||
state.algobot.profile.owner
|
||||
)
|
||||
}
|
||||
class='button is-small is-red'
|
||||
disabled={
|
||||
state.algobot.form.form === 'getAlgobotNotice' &&
|
||||
state.algobot.form.message === 'Removing your algobot...'
|
||||
}
|
||||
>
|
||||
<span class='icon'>
|
||||
<i class='fas fa-trash-alt' />
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class='section' key='algobotsList4'>
|
||||
<p class='is-size-4'>{state.algobot.form.message}</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<span class='icon has-text-primary is-large'>
|
||||
<i class='fas fa-spinner fa-pulse' />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AlgobotsList
|
||||
@@ -1,5 +0,0 @@
|
||||
import { AlgobotsList } from './algobots-list'
|
||||
import { TeamsList } from './teams-list'
|
||||
import { TeamsListWrapper } from './teams-list-wrapper'
|
||||
|
||||
export { AlgobotsList, TeamsList, TeamsListWrapper }
|
||||
@@ -1,29 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
|
||||
export const TeamsItem = ({ team }) => (
|
||||
<tr>
|
||||
<td>
|
||||
{team.profile !== null && team.profile.avatar !== '' ? (
|
||||
<div class='image is-16x16 avatar'>
|
||||
<img src={team.profile.avatar} alt={team.name} />
|
||||
</div>
|
||||
) : (
|
||||
<div class='image is-16x16 avatar has-text-white is-uppercase is-green is-size-7 is-centered'>
|
||||
{team.name.substring(0, 1)}
|
||||
</div>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<strong>{team.name}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{team.owner.nickname}</strong>
|
||||
</td>
|
||||
<td>
|
||||
<strong>{team.members.length}</strong>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
export default TeamsItem
|
||||
@@ -1,25 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
|
||||
import { TeamsList } from './teams-list'
|
||||
|
||||
export const TeamsListWrapper = () => (state, actions) => (
|
||||
<section
|
||||
class='section has-text-centered p-l-3 p-r-3'
|
||||
key='DashboardContainer'
|
||||
oncreate={() => actions.community.getTeams()}
|
||||
>
|
||||
<div class='section m-b-0'>
|
||||
<div class='card'>
|
||||
<div class='card-content has-text-centered'>
|
||||
<h1 class='title has-text-centered is-uppercase is-condensed is-size-3'>
|
||||
Community Teams
|
||||
</h1>
|
||||
<TeamsList />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
export default TeamsListWrapper
|
||||
@@ -1,73 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
import { Link } from '@hyperapp/router'
|
||||
|
||||
import { TeamsItem } from './teams-item'
|
||||
|
||||
import { validObject } from '../../../utils/js-helpers'
|
||||
|
||||
export const TeamsList = props => (state, actions) => {
|
||||
const teams = validObject(state.community.teams, 'edges') ? state.community.teams.edges : ''
|
||||
console.log(state.community.form.form, state.community.form.message)
|
||||
|
||||
if (
|
||||
state.community.teams.length > 0 ||
|
||||
state.community.form.form === 'getAllTeamsSuccess'
|
||||
) {
|
||||
return (
|
||||
<div class='section' key='teamsList3'>
|
||||
<p
|
||||
class={`is-size-5 card-footer-item
|
||||
${
|
||||
state.community.form.form === 'getAllTeamsNotice'
|
||||
? 'has-text-primary'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.community.form.form === 'getAllTeamsError'
|
||||
? 'has-text-danger'
|
||||
: ''
|
||||
}
|
||||
${
|
||||
state.community.form.form === 'getAllTeamsNotice' ||
|
||||
state.community.form.form === 'getAllTeamsError'
|
||||
? ''
|
||||
: 'is-invisible'
|
||||
}`}
|
||||
>
|
||||
{state.community.form.message}
|
||||
</p>
|
||||
<table class='table is-fullwidth is-striped'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th>Team Name</th>
|
||||
<th>Team Owner</th>
|
||||
<th># of Members</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{teams.map((team, i) => (
|
||||
<TeamsItem team={team.node} key={`team-item-${team.node.id}`} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class='section' key='teamsList4'>
|
||||
<p class='is-size-4'>{state.community.form.message}</p>
|
||||
<div class='level m-t-2'>
|
||||
<div class='level-item'>
|
||||
<span class='icon has-text-primary is-large'>
|
||||
<i class='fas fa-spinner fa-pulse' />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TeamsList
|
||||
@@ -1,16 +0,0 @@
|
||||
import { mergeDeep } from '../../../utils/js-helpers'
|
||||
|
||||
export const nav = {
|
||||
top: {
|
||||
toggleMobileMenu: () => state => {
|
||||
const menu = { mobileMenu: !state.mobileMenu }
|
||||
return mergeDeep(menu, state.top)
|
||||
},
|
||||
more: () => state => {
|
||||
const more = { more: !state.more }
|
||||
return mergeDeep(more, state.top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default nav
|
||||
@@ -1,8 +0,0 @@
|
||||
export const navState = {
|
||||
top: {
|
||||
mobileMenu: false,
|
||||
more: false
|
||||
}
|
||||
}
|
||||
|
||||
export default navState
|
||||
@@ -1,41 +0,0 @@
|
||||
import { action } from '@hyperapp/fx'
|
||||
|
||||
import { mergeDeep } from '../../../utils/js-helpers'
|
||||
|
||||
export const onboard = {
|
||||
form: ({ form, message }) => state => {
|
||||
return {
|
||||
form: {
|
||||
form,
|
||||
message
|
||||
}
|
||||
}
|
||||
},
|
||||
clearForms: () => ({
|
||||
form: {
|
||||
form: 'login',
|
||||
message: ''
|
||||
},
|
||||
dashboard: {
|
||||
team: false,
|
||||
algobot: false,
|
||||
develop: false,
|
||||
compete: false,
|
||||
active: true
|
||||
}
|
||||
}),
|
||||
registerEmail: {
|
||||
input: value => state => {
|
||||
const emailInput = {
|
||||
input: value
|
||||
}
|
||||
return mergeDeep(emailInput, state.registerEmail)
|
||||
}
|
||||
},
|
||||
checkUsername: value => [
|
||||
action('throttleUsername', value),
|
||||
action('createUsername.input', value)
|
||||
]
|
||||
}
|
||||
|
||||
export default onboard
|
||||
@@ -1,19 +0,0 @@
|
||||
export const onboard = {
|
||||
form: {
|
||||
type: 'login',
|
||||
message: ''
|
||||
},
|
||||
dashboard: {
|
||||
team: false,
|
||||
algobot: false,
|
||||
develop: false,
|
||||
compete: false,
|
||||
active: true
|
||||
},
|
||||
modal: {
|
||||
content: 'AddTeam',
|
||||
isActive: false
|
||||
}
|
||||
}
|
||||
|
||||
export default onboard
|
||||
@@ -1,57 +0,0 @@
|
||||
/* eslint-disable no-unused-vars */
|
||||
import { h } from 'hyperapp'
|
||||
|
||||
export const DashboardSteps = props => (state, actions) => (
|
||||
<ul class='steps is-narrow is-medium is-centered has-content-centered'>
|
||||
<li class='steps-segment'>
|
||||
<a href='#' class='has-text-dark'>
|
||||
<span class='steps-marker has-text-white'>
|
||||
<span class='icon'>
|
||||
<i class='fa fa-users' />
|
||||
</span>
|
||||
</span>
|
||||
<div class='steps-content'>
|
||||
<p class='heading'>Create A Team</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class='steps-segment'>
|
||||
<a hef='#' class='has-text-dark'>
|
||||
<span class='steps-marker has-text-white'>
|
||||
<span class='icon'>
|
||||
<i class='fa fa-robot' />
|
||||
</span>
|
||||
</span>
|
||||
<div class='steps-content'>
|
||||
<p class='heading'>Name Your Algobot</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class='steps-segment is-active has-gaps'>
|
||||
<a hef='#' class='has-text-dark has-text-white'>
|
||||
<span class='steps-marker has-text-dark'>
|
||||
<span class='icon'>
|
||||
<i class='fa fa-code' />
|
||||
</span>
|
||||
</span>
|
||||
<div class='steps-content'>
|
||||
<p class='heading'>Develop</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
<li class='steps-segment'>
|
||||
<a hef='#' class='has-text-dark'>
|
||||
<span class='steps-marker is-hollow'>
|
||||
<span class='icon'>
|
||||
<i class='fa fa-trophy' />
|
||||
</span>
|
||||
</span>
|
||||
<div class='steps-content'>
|
||||
<p class='heading'>Compete</p>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
|
||||
export default DashboardSteps
|
||||