Remove packages dir

This commit is contained in:
Barry Low committed 2018-10-12 13:56:43 -07:00
1 parent a0fb1b133c
commit 01d04f0d36
195 files changed
-38594

No files matched your search

-14
View File
@@ -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
-9
View File
@@ -1,9 +0,0 @@
/.env
/database/.env
/database/docker-compose.prisma.prod.yml
/docker-compose.prisma.prod.yml
dist
node_modules
.idea
.vscode
*.log
-12
View File
@@ -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
-21
View File
@@ -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
-114
View File
@@ -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!
}
-8
View File
@@ -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
-50
View File
@@ -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:
File diff suppressed because it is too large. Load diff
-26
View File
@@ -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"
}
}
-117
View File
@@ -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 }
File diff suppressed because it is too large. Load diff
@@ -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
-247
View File
@@ -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 }
-24
View File
@@ -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 }
-35
View File
@@ -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
}
-11
View File
@@ -1,11 +0,0 @@
{
"presets": [
["@babel/env", {
"modules": false
}]
],
"plugins": [
"syntax-dynamic-import",
["transform-react-jsx", { "pragma": "h" }]
]
}
-7
View File
@@ -1,7 +0,0 @@
.git
.cache
dist
build
.DS_Store
node_modules
npm-debug.log
-11
View File
@@ -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>
-3
View File
@@ -1,3 +0,0 @@
{
"extends": "standard"
}
-71
View File
@@ -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
-29
View File
@@ -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
-108
View File
@@ -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...
-19
View File
@@ -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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

-26
View File
@@ -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&amp;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"
}
Binary file not shown.

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

File diff suppressed because it is too large. Load diff
-95
View File
@@ -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
}
}
-5
View File
@@ -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
}
Binary file not shown.

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

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">
Binary file not shown.

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"
}
-218
View File
@@ -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
-154
View File
@@ -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
Whitespace-only changes.
@@ -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 &rarr;
</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>&nbsp;</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>&nbsp;</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
}
}
}
`
Whitespace-only changes.
@@ -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 &amp; lower case letters<br />+ a
number + a special char [! @ # $ % ^ &amp; *]
</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 &rarr;
</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 &amp; lower case letters<br />+ a
number + a special char [! @ # $ % ^ &amp; *]
</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
Whitespace-only changes.
@@ -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>&nbsp;</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>&nbsp;</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
Loaded 100 of 195 files, more files were not shown because too many files have changed in this diff. Show more