master 29f3717b3b8e cached
160 files
443.2 KB
125.8k tokens
63 symbols
1 requests
Download .txt
Showing preview only (483K chars total). Download the full file or copy to clipboard to get everything.
Repository: therewillbecode/haskell-poker
Branch: master
Commit: 29f3717b3b8e
Files: 160
Total size: 443.2 KB

Directory structure:
gitextract_6a575bnu/

├── README.md
├── client/
│   ├── .babelrc
│   ├── .dockerignore
│   ├── .editorconfig
│   ├── .eslintignore
│   ├── .eslintrc
│   ├── .gitattributes
│   ├── .gitignore
│   ├── .prettierrc
│   ├── .travis.yml
│   ├── Dockerfile
│   ├── LICENSE.md
│   ├── README.md
│   ├── app/
│   │   ├── actions/
│   │   │   ├── auth.js
│   │   │   ├── games.js
│   │   │   ├── lobby.js
│   │   │   ├── profile.js
│   │   │   ├── socket.js
│   │   │   ├── tests/
│   │   │   │   └── auth.test.js
│   │   │   └── types.js
│   │   ├── app.js
│   │   ├── components/
│   │   │   ├── ActionPanel.js
│   │   │   ├── App.js
│   │   │   ├── Board.js
│   │   │   ├── Card.js
│   │   │   ├── Footer.js
│   │   │   ├── Game.js
│   │   │   ├── Home.js
│   │   │   ├── Lobby.js
│   │   │   ├── NavBar.js
│   │   │   ├── NotFoundPage.js
│   │   │   ├── Profile.js
│   │   │   ├── Seat.js
│   │   │   ├── SignInForm.js
│   │   │   ├── SignUpForm.js
│   │   │   └── Signout.js
│   │   ├── configureStore.js
│   │   ├── containers/
│   │   │   ├── AppContainer.js
│   │   │   ├── GameContainer.js
│   │   │   ├── HomeContainer.js
│   │   │   ├── LobbyContainer.js
│   │   │   ├── NavBarContainer.js
│   │   │   ├── ProfileContainer.js
│   │   │   ├── SignInFormContainer.js
│   │   │   └── SignUpFormContainer.js
│   │   ├── index.html
│   │   ├── middleware/
│   │   │   └── socket.js
│   │   ├── reducers/
│   │   │   ├── auth.js
│   │   │   ├── games.js
│   │   │   ├── lobby.js
│   │   │   ├── profile.js
│   │   │   ├── rootReducer.js
│   │   │   ├── socket.js
│   │   │   └── tests/
│   │   │       └── auth.test.js
│   │   ├── reducers.js
│   │   ├── selectors/
│   │   │   ├── auth.js
│   │   │   ├── games.js
│   │   │   ├── lobby.js
│   │   │   ├── profile.js
│   │   │   ├── route.js
│   │   │   ├── socket.js
│   │   │   └── tests/
│   │   │       └── games.test.js
│   │   ├── styles/
│   │   │   ├── _common.scss
│   │   │   ├── common/
│   │   │   │   ├── _colours.scss
│   │   │   │   ├── _mixins.scss
│   │   │   │   ├── _typography.scss
│   │   │   │   └── _variables.scss
│   │   │   ├── components/
│   │   │   │   ├── _buttons.scss
│   │   │   │   ├── _footer.scss
│   │   │   │   ├── _forms.scss
│   │   │   │   ├── _game.scss
│   │   │   │   ├── _lobby.scss
│   │   │   │   ├── _navbar.scss
│   │   │   │   └── game/
│   │   │   │       ├── _actionPanel.scss
│   │   │   │       ├── _boardCards.scss
│   │   │   │       ├── _cards.scss
│   │   │   │       ├── _seat.scss
│   │   │   │       ├── _slider.scss
│   │   │   │       └── _table.scss
│   │   │   ├── layout/
│   │   │   │   └── _app.scss
│   │   │   └── main.scss
│   │   └── utils/
│   │       └── request.js
│   ├── config/
│   │   ├── jest-mocks/
│   │   │   ├── cssModule.js
│   │   │   └── image.js
│   │   ├── jest.config.js
│   │   ├── test-setup.js
│   │   ├── webpack.base.babel.js
│   │   ├── webpack.dev.babel.js
│   │   └── webpack.prod.babel.js
│   ├── jest.config.js
│   ├── netlify.toml
│   ├── package.json
│   ├── server/
│   │   ├── index.js
│   │   ├── middlewares/
│   │   │   ├── addDevMiddlewares.js
│   │   │   ├── addProdMiddlewares.js
│   │   │   └── frontendMiddleware.js
│   │   └── util/
│   │       ├── argv.js
│   │       ├── logger.js
│   │       └── port.js
│   ├── shell.nix
│   └── static/
│       └── fonts/
│           └── GothamPro/
│               └── GothamHTF-BookCondensed.otf
├── docker-compose.yml
└── server/
    ├── .dev.env
    ├── .dockerignore
    ├── .gitignore
    ├── .projectile
    ├── ChangeLog.md
    ├── Dockerfile
    ├── README.md
    ├── Setup.hs
    ├── UNLICENSE.txt
    ├── app/
    │   └── Main.hs
    ├── bootstrap.sh
    ├── deploy-server.sh
    ├── deploy.sh
    ├── docs/
    │   ├── lobbyAPI.md
    │   ├── socket.md
    │   └── userAPI.md
    ├── package.yaml
    ├── ping.sh
    ├── provision.sh
    ├── server.service
    ├── shell.nix
    ├── src/
    │   ├── API.hs
    │   ├── Bots.hs
    │   ├── Database.hs
    │   ├── Env.hs
    │   ├── Poker/
    │   │   ├── ActionValidation.hs
    │   │   ├── Game/
    │   │   │   ├── Actions.hs
    │   │   │   ├── Blinds.hs
    │   │   │   ├── Game.hs
    │   │   │   ├── Hands.hs
    │   │   │   ├── Privacy.hs
    │   │   │   └── Utils.hs
    │   │   ├── Poker.hs
    │   │   └── Types.hs
    │   ├── Schema.hs
    │   ├── Socket/
    │   │   ├── Auth.hs
    │   │   ├── Clients.hs
    │   │   ├── Lobby.hs
    │   │   ├── Msg.hs
    │   │   ├── Setup.hs
    │   │   ├── Subscriptions.hs
    │   │   ├── Table.hs
    │   │   ├── Types.hs
    │   │   ├── Utils.hs
    │   │   └── Workers.hs
    │   ├── Socket.hs
    │   ├── Types.hs
    │   └── Users.hs
    ├── stack.yaml
    └── test/
        ├── Poker/
        │   ├── ActionSpec.hs
        │   ├── ActionValidationSpec.hs
        │   ├── BlindSpec.hs
        │   ├── GameSpec.hs
        │   ├── Generators.hs
        │   ├── HandSpec.hs
        │   └── UtilsSpec.hs
        ├── PokerSpec.hs
        └── Spec.hs

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# Poker Maison

## A poker app crafted with Haskell and React

Supports games across multiple tables in realtime.

Player moves are timed in order to ensure that games keep running if players disconnect.

The UI and backend are all implemented in less than seven thousand lines of code.

![screenshot](https://s11.gifyu.com/images/SgQfh.gif)


[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](http://unlicense.org/)

![alt text](https://s5.gifyu.com/images/ezgif.com-optimize-1e35dcba1eceb51f5.gif "Demo")

## How to run in docker

Skip this section if you would rather avoid docker.

### Docker Prerequisites

In order to use Docker have the following installed.

- [Docker](https://docs.docker.com/compose/install/) (17.12.0+)
- [Docker Compose](https://docs.docker.com/v17.09/engine/installation/)
- [Docker Machine](https://docs.docker.com/machine/install-machine/)

Firstly start Docker Machine

```bash
docker-machine start
```

Then set the correct variables in your terminal so you can connect to Docker Machine

```bash
eval $(docker-machine env)
```

Now build the images. This will take a while.

```
docker-compose up
```

Now go navigate to http://192.168.99.100:3000 in your browser and the app should be running.

The above ip address is the one for your docker-machine VM if you are on the default settings. By default docker-machine doesn't serve localhost but instead uses 192.168.99.100 as the host.

You can simulate multiple players in the same game on on your machine if you navigate to the above url in a few different browser tabs. Eac time you open up a new tab just remember to log out after you have signed in as the browser will cache the access_token for the last logged in user for each new tab as URL is the same.

## Common Docker Problems

### Docker has the wrong TLS setting

If you get the error below then Docker Compose is not using the correct TLS version.

```
Building web
ERROR: SSL error: HTTPSConnectionPool(host='192.168.99.100', port=2376): Max retries exceeded with url: /v1.30/build?q=False&pull=False&t=server_web&nocache=False&forcerm=False&rm=True (Caused by SSLError(SSLError(1, u'[SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:727)'),))
```

You can fix this by setting the following environment variable with the correct TLS version.

```bash
export COMPOSE_TLS_VERSION=TLSv1_2
```

### Container runs out of memory

If the server docker container runs out of memory whilst building. Whis would look like this.

```
--  While building package Cabal-2.4.1.0 using:
      /root/.stack/setup-exe-cache/x86_64-linux/Cabal-simple_mPHDZzAJ_2.4.0.1_ghc-8.6.5 --builddir=.stack-work/dist/x86_64-linux/Cabal-2.4.0.1 build --ghc-options ""
    Process exited with code: ExitFailure (-9) (THIS MAY INDICATE OUT OF MEMORY)
```

Then set increase the memory available to the VM you are using for docker-machine.
Assuming your VM is named "default", run:

```bash
docker-machine stop default
VBoxManage modifyvm default --memory 4096
docker-machine start default
```

### Slow builds

If you want to speed up builds then replace `n` in the command below
with the number of cores your machine has and run the command.
The command below assumes that "default" is the name of the VM Docker Machine is using.

```bash
docker-machine stop default
VBoxManage modifyvm default --cpus n
docker-machine start default
```

# Building locally from scratch.

The following steps are based on an Ubuntu distribution.

## Back End

Firstly make sure you have ghc and stack installed in order to compile the back end written in Haskell.
If you need to install the Haskell platform then run

```bash
curl -sSL https://get.haskellstack.org/ | sh
```

Secondly install libpq (c bindings to postgres)

```bash
sudo apt-get install libpq-dev
```

Next install redis.

```bash
sudo apt-get install redis
```

Navigate to the server/ directory.

```bash
cd server
```

Compile the back end poker server.

```bash
stack build
```

## Now we need to set some config.

Ensure postgresql 10 is installed and running.

Set the env var so that the server has the postgresql connection string.
Of course you will need to change the db connection parameters below to match your local database.

```bash
export dbConnStr='host=0.0.0.0 port=5432 user=postgres dbname=pokerdbtest password=postgres
```

Set env variable with the secret key for generating auth tokens.
Note that this secret must be 32 characters long or it won't work.

```bash
export secret="changeme077cf4e7441c32d2d0a86b4c"
```

Lastly ensure redis-server is running in the background on default port

```bash
redis-server
```

Now run the server locally. The default user API port is 8000 and websocket port is 5000.

```bash
stack run
```

## Front End

Install node version 10.16.3 and then install yarn globally

```bash
npm i -g yarn@1.17.3
```

Install a required system dependency for node-sass .

```bash
sudo apt-get install libpng-dev
```

Navigate to the client/ directory with

```bash
cd client
```

Then just run.

```bash
yarn start
```

Now you are ready to play poker!

### Simulating a multiplayer game locally

You may want to play against yourself when you are developing locally so just
run the clients on two separate ports.

In your first terminal run

```
PORT=8001 yarn start
```

Then open another terminal and run

```
PORT=8002 yarn start
```

Now just open two tabs in your browser navigating to

```
localhost:8001
```

and

```
localhost:8002
```

## Running Tests

To run the test suite on the backend which has over a hundred tests

```bash
cd server && stack test
```

## Contributions Welcome

Have a look at the issues if you want some starting ideas on how to get involved.

Feel free to open any issues with potential enhancements or bugs you have found.

## License

This is free and unencumbered software released into the public domain.  
For more information, please refer to the `UNLICENSE` file or [unlicense.org](http://unlicense.org).


================================================
FILE: client/.babelrc
================================================
{
  "presets": [
    [
      "env",
      {
        "modules": false
      }
    ],
    "react",
    "stage-0"
  ],
  "env": {
    "production": {
      "only": ["app"],
      "plugins": [
        "transform-react-remove-prop-types",
        "transform-react-constant-elements",
        "transform-react-inline-elements"
      ]
    },
    "test": {
      "plugins": ["transform-es2015-modules-commonjs", "dynamic-import-node"]
    }
  }
}


================================================
FILE: client/.dockerignore
================================================
node_modules

================================================
FILE: client/.editorconfig
================================================
# editorconfig.org

root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false


================================================
FILE: client/.eslintignore
================================================
/build/**
/coverage/**
/docs/**
/jsdoc/**
/templates/**
/tests/bench/**
/tests/fixtures/**
/tests/performance/**
/tmp/**
/lib/util/unicode/is-combining-character.js
/sass/
/node/modules
test.js
!.eslintrc.js
.gitignore

================================================
FILE: client/.eslintrc
================================================
{
  "parser": "babel-eslint",
  "extends": [
    "airbnb",
    "plugin:react/recommended",
    "prettier/react",
    "prettier"
  ],
  "env": {
    "browser": true,
    "node": true,
    "jest": true,
    "es6": true
  },
  "plugins": [
    "react",
    "jsx-a11y",
    "prettier"
  ],
  "parserOptions": {
    "ecmaVersion": 6,
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  },
  "rules": {
    "no-param-reassign": "off",
    "arrow-parens": "off",
    "function-paren-newline": "off",
    "comma-dangle": [
      "error",
      "only-multiline"
    ],
    "import/no-extraneous-dependencies": 0,
    "import/prefer-default-export": 0,
    "indent": [
      2,
      2,
      {
        "SwitchCase": 1
      }
    ],
    "max-len": 0,
    "no-console": 1,
    "react/forbid-prop-types": 0,
    "react/jsx-curly-brace-presence": "off",
    "react/jsx-first-prop-new-line": [
      2,
      "multiline"
    ],
    "react/jsx-filename-extension": 0,
    "react/self-closing-comp": 0,
    "jsx-a11y/anchor-is-valid": 0
  },
  "settings": {
    "import/resolver": {
      "webpack": {
        "config": "./config/webpack.prod.babel.js"
      }
    }
  }
}

================================================
FILE: client/.gitattributes
================================================
# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes

# Handle line endings automatically for files detected as text
# and leave all files detected as binary untouched.
* text=auto

#
# The above will handle all files NOT found below
#

#
## These files are text and should be normalized (Convert crlf => lf)
#

# source code
*.php text
*.css text
*.sass text
*.scss text
*.less text
*.styl text
*.js text eol=lf
*.coffee text
*.json text
*.htm text
*.html text
*.xml text
*.svg text
*.txt text
*.ini text
*.inc text
*.pl text
*.rb text
*.py text
*.scm text
*.sql text
*.sh text
*.bat text

# templates
*.ejs text
*.hbt text
*.jade text
*.haml text
*.hbs text
*.dot text
*.tmpl text
*.phtml text

# server config
.htaccess text
.nginx.conf text

# git config
.gitattributes text
.gitignore text
.gitconfig text

# code analysis config
.jshintrc text
.jscsrc text
.jshintignore text
.csslintrc text

# misc config
*.yaml text
*.yml text
.editorconfig text

# build config
*.npmignore text
*.bowerrc text

# Heroku
Procfile text
.slugignore text

# Documentation
*.md text
LICENSE text
AUTHORS text


#
## These files are binary and should be left untouched
#

# (binary is a macro for -text -diff)
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.mov binary
*.mp4 binary
*.mp3 binary
*.flv binary
*.fla binary
*.swf binary
*.gz binary
*.zip binary
*.7z binary
*.ttf binary
*.eot binary
*.woff binary
*.pyc binary
*.pdf binary


================================================
FILE: client/.gitignore
================================================
# Don't check auto-generated stuff into git
coverage
build
node_modules
stats.json

# Cruft
.DS_Store
npm-debug.log
.idea

# Logs
yarn-error.log



================================================
FILE: client/.prettierrc
================================================
{
  "singleQuote": true,
  "semi": false
}


================================================
FILE: client/.travis.yml
================================================
language: node_js

os: osx

node_js:
  - 8
  - 6
  
script:
  - npm run test
  - npm run build

notifications:
  email:
    on_failure: change

cache:
  yarn: true
  directories:
    - node_modules


================================================
FILE: client/Dockerfile
================================================
# base image
FROM node:10.16.3-alpine

RUN apk add --no-cache \
    autoconf \
    automake \
    bash \
    g++ \
    libc6-compat \
    libjpeg-turbo-dev \
    libpng-dev \
    make \
    nasm

WORKDIR /app

RUN npm install yarn@1.17.3 -g

# install and cache app dependencies
COPY package.json .

RUN yarn

COPY . .

CMD yarn run start:docker


================================================
FILE: client/LICENSE.md
================================================
The MIT License (MIT)

Copyright (c) 2018 Dinesh Pandiyan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: client/README.md
================================================
<img src="https://raw.githubusercontent.com/flexdinesh/react-redux-boilerplate/master/app/components/Header/images/banner.jpg" alt="react redux boilerplate banner" align="center" />

<br />

<div align="center">A minimal, beginner friendly React-Redux boilerplate with all the industry best practices</div>

<br />

<div align="center">
  <!-- Dependency Status -->
  <a href="https://david-dm.org/flexdinesh/react-redux-boilerplate">
    <img src="https://david-dm.org/flexdinesh/react-redux-boilerplate.svg" alt="Dependency Status" />
  </a>
  <!-- devDependency Status -->
  <a href="https://david-dm.org/flexdinesh/react-redux-boilerplate#info=devDependencies">
    <img src="https://david-dm.org/flexdinesh/react-redux-boilerplate/dev-status.svg" alt="devDependency Status" />
  </a>
  <!-- Build Status -->
  <a href="https://travis-ci.org/flexdinesh/react-redux-boilerplate">
    <img src="https://travis-ci.org/flexdinesh/react-redux-boilerplate.svg" alt="Build Status" />
  </a>
  <!-- Gitter -->
  <a href="https://gitter.im/flexdinesh/react-redux-boilerplate">
    <img src="https://camo.githubusercontent.com/54dc79dc7da6b76b17bc8013342da9b4266d993c/68747470733a2f2f6261646765732e6769747465722e696d2f6d78737462722f72656163742d626f696c6572706c6174652e737667" alt="Gitter Chat" />
  </a>
</div>

<br />

<div align="center">
  <sub>Created by <a href="https://twitter.com/flexdinesh">Dinesh Pandiyan</a></sub>
</div>


## Why? [![start with why](https://img.shields.io/badge/start%20with-why%3F-brightgreen.svg?style=flat)](http://www.ted.com/talks/simon_sinek_how_great_leaders_inspire_action)

The whole React community knows and will unanimously agree that [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate) is the ultimate starter template for kickstarting a React project. It's setup with all the industry best practices and standards. But it also has a lot more than what you just need to start a react-redux app. It took me quite some time to get my head around what was happening in the codebase and it's clearly not for starters. They quote this right in their readme,

> Please note that this boilerplate is **production-ready and not meant for beginners**! If you're just starting out with react or redux, please refer to https://github.com/petehunt/react-howto instead. If you want a solid, battle-tested base to build your next product upon and have some experience with react, this is the perfect start for you.

So it involves a lot of additional learning curve to get started with [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate). That's why I forked it, stripped it down and made this _leaner, **beginner friendly**_ boilerplate without all the additional complexity.


## Features

This boilerplate features all the latest tools and practices in the industry.

- _React.js_ - **React 16**✨, React Router 5
- _Redux.js_ - Redux saga, Redux immutable and Reselect
- _Babel_ - ES6, ESNext, Airbnb and React/Recommended config
- _Webpack_ - **Webpack 4**✨, Hot Reloading, Code Splitting, Optimized Prod Build and more
- _Test_ - Jest with Enzyme
- _Lint_ - ESlint
- _Styles_ - SCSS Styling

Here are a few highlights to look out for in this boilerplate 

<dl>
  <dt>Instant feedback</dt>
  <dd>Enjoy the best DX (Developer eXperience) and code your app at the speed of thought! Your saved changes to the CSS and JS are reflected instantaneously without refreshing the page. Preserve application state even when you update something in the underlying code!</dd>

  <dt>Next generation JavaScript</dt>
  <dd>Use template strings, object destructuring, arrow functions, JSX syntax and more, today.</dd>

  <dt>Component Specific Styles</dt>
  <dd>Separate styles for each component. Style in the good old scss way but still keep it abstracted for each component.</dd>

  <dt>Industry-standard routing</dt>
  <dd>It's natural to want to add pages (e.g. `/about`) to your application, and routing makes this possible.</dd>

  <dt>Predictable state management</dt>
  <dd>Unidirectional data flow allows for change logging and time travel debugging.</dd>

  <dt>SEO</dt>
  <dd>We support SEO (document head tags management) for search engines that support indexing of JavaScript content. (eg. Google)</dd>
</dl>

But wait... there's more!

  - *The best test setup:* Automatically guarantee code quality and non-breaking
    changes. (Seen a react app with 99% test coverage before?)
  - *The fastest fonts:* Say goodbye to vacant text.
  - *Stay fast*: Profile your app's performance from the comfort of your command
    line!
  - *Catch problems:* TravisCI setup included by default, so your
    tests get run automatically on each code push.


## Quick start

1. Clone this repo using `git clone https://github.com/flexdinesh/react-redux-boilerplate.git`
2. Move to the appropriate directory: `cd react-redux-boilerplate`.<br />
3. Run `yarn` or `npm install` to install dependencies.<br />
4. Run `npm start` to see the example app at `http://localhost:3000`.

Now you're ready build your beautiful React Application!


## Info

These are the things I stripped out from [react-boilerplate](https://github.com/react-boilerplate/react-boilerplate) - _github project rules, ngrok tunneling, shjs, service worker, webpack dll plugin, i18n, styled-components, code generators and a few more._


## License

MIT license, Copyright (c) 2018 Dinesh Pandiyan.


================================================
FILE: client/app/actions/auth.js
================================================
import axios from 'axios'

import * as types from './types'
import { checkStatus } from '../utils/request'

/* Action Creators for Socket API authentication */

// Redux Socket Middleware intercepts this action and handles connection logic
export const connectSocket = token => ({
  type: types.CONNECT_SOCKET,
  token
})

export const disconnectSocket = () => ({ type: types.DISCONNECT_SOCKET })

export const logoutUser = history => dispatch => {
  localStorage.removeItem('token')
  dispatch(logout())
  dispatch(disconnectSocket())
  history.push('/')
}

console.log('env var', process.env)

const AUTH_API_URL =
  process.env.NODE_ENV === 'docker'
    ? 'http://192.168.99.100:8000'
    : process.env.NODE_ENV === 'production'
    ? 'https://tenpoker.co.uk'
    : 'http://localhost:8000'

export const authRequested = () => ({ type: types.AUTH_REQUESTED })

export const authSuccess = username => ({ type: types.AUTHENTICATED, username })

export const authError = error => ({ type: types.AUTHENTICATION_ERROR, error })

export const logout = () => ({ type: types.UNAUTHENTICATED })

export function login(username, password, history) {
  return async dispatch => {
    dispatch(authRequested())
    axios
      .post(
        `${AUTH_API_URL}/login`,
        {
          loginUsername: username,
          loginPassword: password
        },
        {
          headers: {
            'Access-Control-Allow-Origin': '*'
          }
        }
      )
      .then(({ data }) => {
        const { access_token } = data
        dispatch(authSuccess(username))
        dispatch(connectSocket(access_token))
        localStorage.setItem('token', JSON.stringify({ ...data, username }))
        history.push('/profile')
      })
      .catch(err => dispatch(authError(err)))
  }
}

export function register(username, email, password, history) {
  return async dispatch => {
    dispatch(authRequested())
    axios
      .post(`${AUTH_API_URL}/register`, {
        newUsername: username,
        newUserEmail: email,
        newUserPassword: password
      })
      .then(({ data }) => {
        const { access_token } = data
        dispatch(authSuccess(username))
        dispatch(connectSocket(access_token))
        localStorage.setItem('token', JSON.stringify({ ...data, username }))
        history.push('/profile')
      })
      .catch(err => dispatch(authError(err)))
  }
}


================================================
FILE: client/app/actions/games.js
================================================
import * as types from './types'

export const newGameState = (tableName, gameState) => ({
  type: types.NEW_GAME_STATE,
  tableName,
  gameState
})

export const postBigBlind = tableName => ({
  type: types.POST_BIG_BLIND,
  data: {
    tag: 'GameMsgIn',
    contents: {
      tag: 'GameMove',
      contents: [tableName, { tag: 'PostBlind', contents: 'Big' }]
    }
  }
})

export const postSmallBlind = tableName => ({
  type: types.POST_SMALL_BLIND,
  data: {
    tag: 'GameMsgIn',
    contents: {
      tag: 'GameMove',
      contents: [tableName, { tag: 'PostBlind', contents: 'Small' }]
    }
  }
})

export const bet = (tableName, amount) => ({
  type: types.BET,
  data: {
    tag: 'GameMsgIn',
    contents: {
      tag: 'GameMove',
      contents: [tableName, { tag: 'Bet', contents: Number(amount) }]
    }
  }
})

export const raise = (tableName, amount) => ({
  type: types.RAISE,
  data: {
    tag: 'GameMsgIn',
    contents: {
      tag: 'GameMove',
      contents: [tableName, { tag: 'Raise', contents: Number(amount) }]
    }
  }
})

export const call = tableName => ({
  type: types.CALL,
  data: {
    tag: 'GameMsgIn',
    contents: { tag: 'GameMove', contents: [tableName, { tag: 'Call' }] }
  }
})

export const check = tableName => ({
  type: types.CHECK,
  data: {
    tag: 'GameMsgIn',
    contents: { tag: 'GameMove', contents: [tableName, { tag: 'Check' }] }
  }
})

export const fold = tableName => ({
  type: types.FOLD,
  data: {
    tag: 'GameMsgIn',
    contents: { tag: 'GameMove', contents: [tableName, { tag: 'Fold' }] }
  }
})

export const leaveSeat = tableName => ({
  type: types.LEAVE_SEAT,
  data: {
    tag: 'GameMsgIn',
    contents: {
      tag: 'LeaveSeat',
      contents: tableName
    }
  }
})

export const sitIn = tableName => ({
  type: types.SIT_IN,
  data: { tag: 'GameMove', contents: [tableName, { tag: 'SitIn' }] }
})


================================================
FILE: client/app/actions/lobby.js
================================================
/* 
   The data value of the action forms the websocket msg payload.
*/
import * as types from './types'

export const getLobby = () => ({
  type: types.GET_LOBBY,
  data: { tag: 'GetTables' }
})

export const newLobby = lobby => ({ type: types.NEW_LOBBY, lobby })

// should be moved as this is game action
export const takeSeat = (tableName, chips) => ({
  type: types.TAKE_SEAT,
  data: {
    tag: 'GameMsgIn',
    contents: {
      tag: 'TakeSeat',
      contents: [tableName, Number(chips)]
    }
  }
})

export const subscribeToTable = tableName => ({
  type: types.SUBSCRIBE_TO_TABLE,
  data: { tag: 'SubscribeToTable', contents: tableName }
})


================================================
FILE: client/app/actions/profile.js
================================================
import axios from 'axios'

import * as types from './types'

/* Action Creators for User API authentication */
const AUTH_API_URL = 'https://tenpoker.co.uk'
//process.env.NODE_ENV === 'production' ? 'https://tenpoker.co.uk' : 'http://localhost:8000'

export const getProfileRequest = () => ({ type: types.GET_PROFILE_REQUEST })

export const getProfileSuccess = profile => ({
  type: types.GET_PROFILE_SUCCESS,
  profile
})

export const getProfileErr = error => ({ type: types.GET_PROFILE_ERR, error })

export const getProfile = username => {
  return dispatch => {
    const token = localStorage.getItem('token')
    if (token) {
      try {
        const { access_token } = JSON.parse(token)
        dispatch(getProfileRequest())
        console.log('access token', access_token)
        axios
          .get(`${AUTH_API_URL}/profile`, {
            headers: {
              Authorization: access_token,
              'Content-Type': 'application/json'
            }
          })
          .then(({ data }) => {
            const profile = {
              chipsInPlay: data.proChipsInPlay,
              availableChips: data.proAvailableChips,
              userCreatedAt: data.proUserCreatedAt,
              username: data.proUsername,
              email: data.proEmail
            }

            dispatch(getProfileSuccess(profile))
          })
          .catch(err => dispatch(getProfileErr(err)))
      } catch (e) {
        console.log(e)
        dispatch(getProfileErr(e))
      }
    } else {
      dispatch(getProfileErr('No JWT token for profile request'))
    }
  }
}


================================================
FILE: client/app/actions/socket.js
================================================
import * as types from './types'

export const socketConnErr = err => ({ type: types.SOCKET_CONN_ERR, err })

export const socketConnected = socket => ({ type: types.SOCKET_CONNECTED, socket })

export const socketAuthSuccess = () => ({ type: types.SOCKET_AUTH_SUCCESS })

export const socketAuthErr = err => ({ type: types.SOCKET_AUTH_ERR, err })

export const socketReconnecting = () => ({ type: "SOCKET_RECONNECTING" })

export const socketReconnectFail = () => ({ type: "SOCKET_RECONNECT_FAIL" })

================================================
FILE: client/app/actions/tests/auth.test.js
================================================
/* eslint-disable */
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import axios from "axios";

import { authRequested, authError, authSuccess, login, logout, register } from "../auth";
import * as types from "../types";

const localStorageMock = {
  getItem: jest.fn(),
  setItem: jest.fn(),
  clear: jest.fn()
};

global.localStorage = localStorageMock;

const jestMock = response => jest
  .fn()
  .mockImplementation(
    () =>
      new Promise(
        (resolve, reject) =>
          response.status !== 200 ? reject(response) : resolve(response)
      )
  );

const stubAxios = response => {
  axios.get = jestMock(response)
  axios.post = jestMock(response)
};

describe("auth actions", () => {
  describe("action creators", () => {
    describe("authRequested", () => {
      it("should return correct action an authSuccess action for received asset", () => {
        expect(authRequested()).toEqual({ type: types.AUTH_REQUESTED })
      });
    })

    describe("authSuccess", () => {
      it("should return correct action an authSuccess action for received asset", () => {
        const username = 'Argo'
        expect(authSuccess(username)).toEqual({ type: types.AUTHENTICATED, username })
      });
    })

    describe("authError", () => {
      it("should return correct action an authSuccess action for received asset", () => {
        const error = '404'
        expect(authError(error)).toEqual({ type: types.AUTHENTICATION_ERROR, error })
      });
    })

    describe("logout", () => {
      it("should return correct action an authSuccess action for received asset", () => {
        expect(logout()).toEqual({ type: types.UNAUTHENTICATED })
      });
    })
  })

  describe("thunk actions", () => {
    let mockStore;
    let historyMock = { push: jest.fn() } // mocks react router history

    describe("signIn", () => {
      beforeEach(() => {
        const middlewares = [thunk];
        mockStore = configureMockStore(middlewares);
      });

      afterEach(() => {
        axios.get.mockReset();
        historyMock.push.mockReset()
        localStorage.clear()
      });

      afterAll(() => {
        axios.get.mockRestore();
      });

      const username = 'Argo'
      const email = 'email@email.com'
      const password = 'password'

      it("should dispatch correct actions when authentication succeeds", () => {
        const store = mockStore({});
        const expectedActions = [
          { type: types.AUTH_REQUESTED },
          { type: types.AUTHENTICATED }
        ];

        stubAxios({ status: 200, data: { token: 'JWT' } });
        return store.dispatch(login({ email, password }, historyMock)).then(() => {
          expect(store.getActions()).toEqual(expectedActions);
        });
      });


      it("should dispatch correction actions when error occurs while fetching user profile", () => {
        const store = mockStore({});
        const error = {
          "response": { "data": "Unauthorized" }, "status": 401
        }
        const expectedActions = [
          { type: types.AUTH_REQUESTED },
          { type: types.AUTHENTICATION_ERROR, error }
        ];

        stubAxios({ status: 401, response: { data: "Unauthorized" } });
        return store.dispatch(login({ email, password }, historyMock)).then(() => {
          expect(store.getActions()).toEqual(expectedActions);
        });
      });

      it("should redirect to correct route on auth success", () => {
        const store = mockStore({});
        const expectedRoute = '/lobby'
        stubAxios({ status: 200, data: { token: 'JWT' } });

        return store.dispatch(login({ email, password }, historyMock)).then(() => {
          expect(historyMock.push).toBeCalledWith(expectedRoute)
        });
      });

      it("should store JWT token in localStorage on auth success", () => {
        const store = mockStore({});
        const token = 'JWT'
        stubAxios({ status: 200, data: { token } });

        return store.dispatch(login({ email, password }, historyMock)).then(() => {
          expect(localStorage.setItem).toBeCalledWith('token', token)
        });
      });
    });
  });
});

================================================
FILE: client/app/actions/types.js
================================================
/* Actions prefixed with /server denote actions which trigger the sending of a websocket msg to server*/

/* User API Types */
export const AUTH_REQUESTED = 'AUTH_REQUESTED'
export const AUTHENTICATED = 'AUTHENTICATED'
export const UNAUTHENTICATED = 'UNAUTHENTICATED'
export const AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR'

/* Retrieve User Profile */
export const GET_PROFILE_REQUEST = 'GET_PROFILE_REQUEST'
export const GET_PROFILE_SUCCESS = 'GET_PROFILE_SUCCESS'
export const GET_PROFILE_ERR = 'GET_PROFILE_ERR'

/* Websocket Action Types */
export const CONNECT_SOCKET = 'CONNECT_SOCKET'
export const SOCKET_CONNECTED = 'SOCKET_CONNECTED'
export const DISCONNECT_SOCKET = 'DISCONNECT_SOCKET'
export const SOCKET_AUTH_SUCCESS = 'SOCKET_AUTH_SUCCESS'
export const SOCKET_AUTH_ERR = 'SOCKET_AUTH_ERR'
export const SOCKET_CONN_ERR = 'SOCKET_CONN_ERR'

/* Lobby Action Types */
export const GET_LOBBY = 'server/GET_LOBBY'
export const NEW_LOBBY = 'NEW_LOBBY'
export const TAKE_SEAT = 'server/TAKE_SEAT'
export const SUBSCRIBE_TO_TABLE = 'server/SUBSCRIBE_TO_TABLE'

/* Game Action Types */
export const NEW_GAME_STATE = 'NEW_GAME_STATE'
export const SUCCESSFULLY_SAT_DOWN = 'SUCCESSFULLY_SAT_DOWN'
export const POST_BIG_BLIND = 'server/POST_BIG_BLIND'
export const POST_SMALL_BLIND = 'server/POST_SMALL_BLIND'
export const BET = 'server/BET'
export const RAISE = 'server/RAISE'
export const CHECK = 'server/CHECK'
export const FOLD = 'server/FOLD'
export const CALL = 'server/CALL'
export const SIT_IN = 'server/SIT_IN'
export const LEAVE_SEAT = 'server/LEAVE_SEAT'


================================================
FILE: client/app/app.js
================================================
/**
 * app.js
 *
 * This is the entry file for the application, only setup and boilerplate
 * code.
 */

// Needed for redux-saga es6 generator support
import 'babel-polyfill'

// Import all the third party stuff
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { ConnectedRouter } from 'react-router-redux'
import createHistory from 'history/createBrowserHistory'

import 'sanitize.css/sanitize.css'

import AppContainer from 'containers/AppContainer'

import { authSuccess } from './actions/auth'

import 'styles/main.scss'

import configureStore from './configureStore'

// Create redux store with history
const initialState = {}
const history = createHistory()
const store = configureStore(initialState, history)
const MOUNT_NODE = document.getElementById('app')

const render = () => {
  ReactDOM.render(
    <Provider store={store}>
      <ConnectedRouter history={history}>
        <AppContainer />
      </ConnectedRouter>
    </Provider>,
    MOUNT_NODE
  )
}

if (module.hot) {
  // Hot reloadable React components and translation json files
  // modules.hot.accept does not accept dynamic dependencies,
  // have to be constants at compile-time
  module.hot.accept(['containers/AppContainer'], () => {
    ReactDOM.unmountComponentAtNode(MOUNT_NODE)
    render()
  })
}

// If we have a JWT token in localStorage then treat user as authenticated
const token = localStorage.getItem('token')

if (token) {
  try {
    const { username } = JSON.parse(localStorage.getItem('token'))
    store.dispatch(authSuccess(username))
  } catch (e) {
    console.log(e)
  }
}

render()


================================================
FILE: client/app/components/ActionPanel.js
================================================
import React from 'react'

// TODO move to own component called pocket cards
import Card from './Card'

const getPocketCards = cards =>
  cards !== undefined && cards !== null ? cards.map(card => {
    const rank = card.get('rank')
    const suit = card.get('suit')

    return (<Card
      key={rank + suit}
      rank={rank}
      suit={suit}
    />)
  }) : ''




const ActionPanel = ({
  updateBetValue,
  betValue,
  bet,
  raise,
  call,
  fold,
  check,
  postSmallBlind,
  postBigBlind,
  sitDown,
  leaveGameSeat,
  userPocketCards,
  gameStage,
  sitIn,
  bigBlind,
  maxCurrBet,
  isTurnToAct,
  availableActions,
  userPlayer
}) => {
  console.log('available actions', availableActions)
  console.log(gameStage)
  const preDealActions =
    gameStage === "PreDeal" ? <React.Fragment>

      {availableActions.includes("PostBigBlind") ?
        <button
          type="button"
          onClick={() => postBigBlind()} className="button">
          Post Big Blind
      </button> : ''}

      {availableActions.includes("PostSmallBlind") ?
        <button
          type="button" onClick={() => postSmallBlind()} className="button">
          post Small Blind
      </button> : ' '}

      {userPlayer ? '' : <button
        type="button"
        onClick={() => sitDown(betValue)}
        className="button">
        Sit Down Bet <span className='monospaced-font-bold'>
          {betValue}</span>
      </button>}

      {userPlayer && (userPlayer.get("_playerState") === "SatOut") ? <button
        type="button"
        onClick={() => sitIn()}
        className="button">
        Sit In
          </button> : ''}

      {userPlayer ? <button
        type="button"
        onClick={() => leaveGameSeat()}
        className="button">
        Leave Seat
    </button> : ' '}

    </React.Fragment> : '';


  let minBet = maxCurrBet >= bigBlind ? 2 * maxCurrBet : bigBlind

  return (
    <div className='action-panel'>


      <div className='user-actions-container'>
        {(availableActions.includes("Bet") || !userPlayer || availableActions.includes("Raise")) ?
          <div className="slidecontainer">
            <input type="range"

              max={userPlayer ? userPlayer.get("_chips") : 2000}
              min={userPlayer ? minBet : 1500}
              step={5}
              value={betValue}
              className="slider"
              id="myRange"

              onChange={updateBetValue} />
          </div> : ''}
        {preDealActions}
        {true ?   // gameStage !== 'Showdown' && gameStage !== 'PreDeal' && isTurnToAct ?
          <React.Fragment>

            {availableActions.includes("Check") ?
              <button type="button" onClick={() => check()} className="button">
                Check
       </button> : ''}

            {availableActions.includes("Call") ?
              <button type="button" onClick={() => call()} className="button">
                Call</button> : ''}

            {availableActions.includes("Bet") ? <button
              type="button"
              onClick={() => bet(betValue)} className="button">Bet <span className='monospaced-font-bold'>
                {betValue}</span></button> : ''}
            {availableActions.includes("Raise") ?
              <button
                type="button"
                onClick={() => raise(betValue)}
                className="button">
                Raise {betValue}</button> : ''}
            {availableActions.includes("Fold") ? <button
              type="button"
              onClick={() => fold()}
              className="button">
              Fold
          </button> : ''}
          </React.Fragment>
          : ''}

      </div>
    </div>)
}


export default ActionPanel


================================================
FILE: client/app/components/App.js
================================================
/**
 *
 * App
 *
 * This component is the skeleton around the actual pages, and should only
 * contain code that should be seen on all pages. (e.g. navigation bar)
 */

import React from 'react'
import { Helmet } from 'react-helmet'
import { Switch, Route } from 'react-router-dom'

import HomeContainer from '../containers/HomeContainer'
import NavBarContainer from '../containers/NavBarContainer'
import SignUpFormContainer from '../containers/SignUpFormContainer'
import SignInFormContainer from '../containers/SignInFormContainer'
import LobbyContainer from '../containers/LobbyContainer'
import GameContainer from '../containers/GameContainer'
import ProfileContainer from '../containers/ProfileContainer'

import Footer from './Footer'
import NotFoundPage from './NotFoundPage'
import Signout from './Signout'

const App = ({ username }) => (
  <div className="app-wrapper">
    <Helmet
      titleTemplate="%s Ten Poker"
      defaultTitle="Ten Poker"
    >
      <meta name="description" content="An open source poker app." />
    </Helmet>
    <NavBarContainer />
    <Switch>
      <Route exact path="/" component={HomeContainer} />
      <Route path="/signup" component={SignUpFormContainer} />
      <Route path="/signin" component={SignInFormContainer} />
      <Route path="/signout" component={Signout} />
      <Route path="/lobby" component={LobbyContainer} />
      <Route path="/profile" component={ProfileContainer} />
      <Route
        path="/game/:tableName"
        render={props => <GameContainer {...props} username={username} />}
      />
      <Route path="" component={NotFoundPage} />
    </Switch>
  </div>
)

export default App


================================================
FILE: client/app/components/Board.js
================================================
import React from 'react';

import Card from './Card';

const Board = ({ cards }) => (
  <div className="board-cards">
    <div className='board-cards-container'>
      {cards.map(card => {
        const rank = card.get('rank')
        const suit = card.get('suit')

        return (<Card
          key={rank + suit}
          rank={rank}
          suit={suit}
        />)
      })}
    </div>
  </div>);

export default Board;

================================================
FILE: client/app/components/Card.js
================================================
import React from 'react';

import clubs from '../../static/Clubs.svg'
import hearts from '../../static/Hearts.svg'
import spades from '../../static/Spades.svg'
import diamonds from '../../static/Diamonds.svg'

const showRank = rank => {
  switch (rank) {
    case 'Ace':
      return 'A'
    case 'King':
      return 'K'
    case 'Queen':
      return 'Q'
    case 'Jack':
      return 'J'
    case 'Ten':
      return '10'
    case 'Nine':
      return '9'
    case 'Eight':
      return '8'
    case 'Seven':
      return '7'
    case 'Six':
      return '6'
    case 'Five':
      return '5'
    case 'Four':
      return '4'
    case 'Three':
      return '3'
    case 'Two':
      return '2'
  }
}

const suitSVG = suit => {
  switch (suit) {
    case 'Spades':
      return spades
    case 'Diamonds':
      return diamonds
    case 'Hearts':
      return hearts
    case 'Clubs':
      return clubs
  }
}

const Card = ({ rank, suit }) => (
  <div className='card'>
    <div className="rank">
      <span className="monospace-font-bold">{showRank(rank)}</span>
    </div>
    <div className="suit">
      <img alt={suit} src={suitSVG(suit)} />
    </div>
  </div>);

export default Card;


================================================
FILE: client/app/components/Footer.js
================================================
import React from 'react';


const Footer = () => (
  <div className='footer'> Footer</div>
);

export default Footer;


================================================
FILE: client/app/components/Game.js
================================================
import React from 'react'

import ActionPanel from './ActionPanel'
import Board from './Board'
import Seat from './Seat'
import Card from './Card'

import { fromJS, toJS, List } from 'immutable'

let isEveryoneAllIn = players => {
  let activesCount = players.filter(p =>
    p.get("_playerState") == "In").size
  let allInCount = players.filter(p =>
    (p.get("_playerState") == "In") && (p.get("_chips") === 0)).size

  console.log('actives count', activesCount)
  console.log('all in count', allInCount)
  if (activesCount < 2) {
    return false;
  }
  else {
    let notAllInCount = activesCount - allInCount
    return notAllInCount <= 1
  }
}



const getSeatedPlayer = (
  username,
  player,
  gameStage,
  position,
  isTurnToAct,
  isEveryoneAllIn,
  activePlayerCount,

) => (
    <Seat
      key={position}
      position={position}
      isEveryoneAllIn={isEveryoneAllIn}
      playerName={player.get('_playerName')}
      chips={player.get('_chips')}
      hasPocketCards={
        player.get('_playerName') !== username &&
        player.get('_playerState') === 'In' &&
        gameStage !== 'PreDeal' &&
        gameStage !== 'Showdown'
      }
      playerState={player.get('_playerState')}
      activePlayerCount={activePlayerCount}
      isTurnToAct={isTurnToAct && (player.get('_actedThisTurn') === false) && gameStage !== 'Showdown'}
    />
  )

const getSeats = (username, maxPlayers, players, gameStage, currentPosToAct, isEveryoneAllIn, activePlayerCount) =>
  Array(maxPlayers)
    .fill(null)
    .map((_, i) => {
      const player = players.get(i)
      const isTurnToAct = i === currentPosToAct

      return player ? (
        getSeatedPlayer(username, player, gameStage, i, isTurnToAct, isEveryoneAllIn, activePlayerCount)
      ) : (
          <Seat
            key={i}
            position={i} />
        )
    })

const getPocketCards = players =>
  players.map((p, i) => {
    if (p.get('_pockets') === null) {
      return ''
    }

    return p.get('_pockets').length !== 0 ? (
      <div className={`showdown-pocket-cards-${i}`} key={p.get('_playerName')}>
        <div className="showdown-pocket-cards-container">
          {p.get('_pockets').map(card => {
            const rank = card.get('rank')
            const suit = card.get('suit')

            return <Card key={rank + suit} rank={rank} suit={suit} />
          })}
        </div>
      </div>
    ) : (
        ''
      )
  })

const getPlayerBets = players =>
  players.map(
    (p, i) =>
      p.get('_bet') > 0 ? (
        <div className={`player-bet-container-pos-${i}`}>
          <div className={`player-bet-pos-${i}`}>
            <div className="player-bet-chip" />
            <div className="player-bet-label">
              <h3><span className="monospaced-font-bold">{`$${p.get('_bet')}`}</span></h3>
            </div>
          </div>
        </div>
      ) : (
          ''
        )
  )

const parseAvailableActions = actions => {
  console.log(actions)


  if (List.size === 0) {
    return actions
  } else {
    const actionsJS = actions.toJS()
    return actionsJS.map(a => {
      console.log(a)


      if (a.tag === "PostBlind") {
        if (a.contents === "Big") {
          return "PostBigBlind"
        } else {
          return "PostSmallBlind"
        }
      } else {
        return a.tag
      }
    })
  }
}

const Game = props => {
  const { game, username, isTurnToAct } = props




  if (game) {
    const jsgame = game.toJS()
    console.log(jsgame)
    console.log('everyone all in ', isEveryoneAllIn(game.get("_players")))
    const everyoneAllIn = isEveryoneAllIn(game.get("_players"))
    const players = game.get('_players')
    const dealerPos = game.get('_dealer')
    const maxPlayers = 6
    const gameStage = game.get('_street')
    const potSize = game.get('_pot')

    let activePlayerCount = players.filter(p =>
      p.get("_playerState") == "In").size

    const userPlayer = game
      .get('_players')
      .find(p => p.get('_playerName') === username)


    const userPocketCards = userPlayer ? userPlayer.get('_pockets') : null
    const userAvailableActions = userPlayer ? userPlayer.get('_possibleActions') : fromJS([])
    console.log(((parseAvailableActions(userAvailableActions))).toJS)

    const currentPosToAct = game.get('_currentPosToAct')
    const isMultiplayerShowdown =
      game.get('_winners').get('tag') == 'MultiPlayerShowdown'
    const showdownPots = game.get('_winners').get('contents')
    const mainShowdownPot = showdownPots
      ? showdownPots.get
        ? showdownPots.get(0)
        : null
      : null
    const mainShowdownPotHandRanking = mainShowdownPot
      ? mainShowdownPot.get(0).get(0)
      : null
    const mainShowdownPotHandCards = mainShowdownPot
      ? mainShowdownPot.get(0).get(1)
      : null
    const mainShowdownPotHandPlayers = mainShowdownPot
      ? mainShowdownPot.get(1)
      : null
    const playerNamesWinnersOfMainShowdownPot = mainShowdownPot
      ? mainShowdownPot.get(1)
      : null


    return (
      <div className="game-view-grid">
        <div className="game-container">
          <div className="table-container">
            {getPocketCards(players)}
            {getSeats(
              username,
              maxPlayers,
              players,
              gameStage,
              currentPosToAct,
              everyoneAllIn,
              activePlayerCount,
              gameStage
            )}
            <div className="game-grid">
              {players ? (
                players.count() > 1 ? (
                  <div className={`dealer-btn-pos-${dealerPos}`}>D</div>
                ) : (
                    ''
                  )
              ) : (
                  ''
                )}
              <Board cards={game.get('_board')} />
              {getPlayerBets(players)}
              <h3 className="pot-label">
                <span className="monospaced-font-bold">{`$${potSize}`}</span>
              </h3>
              {mainShowdownPot ? (
                <h3 className="winners-label">
                  {`${mainShowdownPotHandPlayers} wins with ${mainShowdownPotHandRanking}`}
                </h3>
              ) : (
                  ''
                )}
            </div>
            <div className="game-table" />
          </div>
        </div>
        <ActionPanel
          {...props}

          gameStage={gameStage}
          userPocketCards={userPocketCards}
          availableActions={parseAvailableActions(userAvailableActions)}
          userPlayer={userPlayer}
          maxCurrBet={game.get("_maxBet")}
          bigBlind={game.get("_bigBlind")}
        />
      </div>
    )
  }
  return <h2>Loading Game...</h2>
}

export default Game


================================================
FILE: client/app/components/Home.js
================================================
import React from 'react';

const Home = () => (
  <div> Home
  </div>
);

export default Home;


================================================
FILE: client/app/components/Lobby.js
================================================
import React from 'react';

const Lobby = ({ lobby, history, subscribeToATable }) =>
  < table className="table game-table-list" >
    <thead>
      <tr>
        <th><h4>Name</h4></th>
        <th><h4>Players</h4></th>
        <th><h4>Waitlist</h4></th>
        <th><h4>Min Buy In</h4></th>
        <th><h4>Max Buy In</h4></th>
        <th><h4>Big Blind</h4></th>
        <th></th>
      </tr>
    </thead>
    <tbody >
      {lobby.map((table) => {
        const tableName = table.get('_tableName')

        return <tr
          key={tableName}
        >
          <td>{tableName}</td>
          <td>{`${table.get('_playerCount')} / ${table.get('_maxPlayers')}`}</td>
          <td>{table.get('_waitlistCount')}</td>
          <td>{table.get('_minBuyInChips')}</td>
          <td>{table.get('_maxBuyInChips')}</td>
          <td>{table.get('_bigBlind')}</td>
          <td><button className="button" style={{ fontSize: "0.8em", paddingBottom: '0.2em', paddingTop: '0.2em', width: "5.7em", height: "3.8em" }} onClick={() => {
            subscribeToATable(tableName)
            history.push(`/game/${tableName}`)
          }}>Join</button></td>
        </tr>
      })}
    </tbody >
  </table >

export default Lobby;


================================================
FILE: client/app/components/NavBar.js
================================================
import React from 'react'

const NavBar = ({
  isAuthenticated,
  currRoute,
  username,
  history,
  logoutUser
}) => (
    <nav role="navigation" className="navbar">
      <div className="navbar-brand">
        <div>
          <a onClick={() => history.push('/')}>
            <h4 className="brand-title">Ten Poker</h4>
          </a>
        </div>
      </div>
      <div className="navbar-menu">
        <div className="navbar-start">
          {isAuthenticated ? (
            <React.Fragment>
              <div
                onClick={() => history.push('/lobby')}
                className={`navbar-item${
                  currRoute === '/lobby' ? '-active' : ''
                  }`}
              >
                <a >
                  <h4>Lobby</h4>
                </a>
              </div>

            </React.Fragment>
          ) : (
              ''
            )}
        </div>
        <div className="navbar-end">
          {isAuthenticated ? (
            <div
              className={`navbar-item${
                currRoute === '/profile' ? '-active' : ''
                }`}
            >
              {' '}
              <a onClick={() => history.push('/profile')}>
                <h4>
                  <span className="navbar-username">{username}</span>
                </h4>
              </a>
            </div>
          ) : (
              ''
            )}
          {isAuthenticated ? (
            <div className="navbar-item">
              <a onClick={() => logoutUser()}>
                <h4>Logout</h4>
              </a>
            </div>
          ) : (
              ''
            )}
          {!isAuthenticated ? (
            <div className="navbar-item">
              <a onClick={() => history.push('/signin')}>
                <h4>Login</h4>
              </a>
            </div>
          ) : (
              ''
            )}
          {!isAuthenticated ? (
            <div className="navbar-item">
              <a onClick={() => history.push('/signup')}>
                <h4>Register</h4>
              </a>
            </div>
          ) : (
              ''
            )}
        </div>
      </div>
    </nav>
  )

export default NavBar


================================================
FILE: client/app/components/NotFoundPage.js
================================================
import React from 'react';

const NotFoundPage = () => (
  <div> NotFoundPage
  </div>
);

export default NotFoundPage;


================================================
FILE: client/app/components/Profile.js
================================================
import React from 'react'

const Profile = username => <div className="profile">profile</div>

export default Profile


================================================
FILE: client/app/components/Seat.js
================================================
import React from 'react';

let isPlayerInactive = playerState =>
  playerState === 'Folded' ||
  playerState === 'SatOut'


const Seat = ({ activePlayerCount, gameStage, isEveryoneAllIn, playerName, chips, isTurnToAct, hasPocketCards, position, playerState }) => {

  console.log('player state', playerState)
  console.log('has pockets', hasPocketCards)
  console.log('is everyone all in ', isEveryoneAllIn)
  console.log(' hasPocketCards && playerState !== "Folded" && !isEveryoneAllIn', hasPocketCards && playerState !== "Folded" && !isEveryoneAllIn)

  return (
    <div className={`seat-${position}-container`}>
      {hasPocketCards && playerState == "In" && !isEveryoneAllIn && (gameStage !== 'Showdown') && (activePlayerCount > 1) ?
        <div className='hidden-pocket-cards' >
          <div className='hidden-pocket-cards-container' >
            <div className='card pocket-one' />
            <div className='card pocket-two' />
          </div>
        </div> : ''}
      <div
        className={`seat-${position}
        ${isTurnToAct ? 'active-player' : ''} 
        ${playerName ? '' : 'empty-seat'}
        ${isPlayerInactive(playerState) ? 'disabled' : ''}`
        }>
        <h4 className={`${playerName ? 'player-name' : ''}`}>{playerName || ''}</h4>
        <h4 className={playerState ? 'player-state' : ''}>
          {playerState == 'In' && chips == 0 ? 'All In' : ''}
          {playerState == 'Folded' ? 'Folded' : ''}
          {playerState == 'SatOut' ? 'Sat Out' : ''}</h4>
        {playerName ?
          <h4 className='player-chip-count'>
            <span className='monospaced-font-bold'>
              {playerState == 'In' && chips == 0 ? '' : `$${chips}`}</span></h4> : ''}
      </div>
    </div>)

}

export default Seat;

================================================
FILE: client/app/components/SignInForm.js
================================================
import React from 'react';

const SignInForm = ({ handleChange, handleSubmit }) => (
  <div className="form-container">
    <div className="form" >
      <div className="form-container">

        <h2> Log In</h2>

        <form onSubmit={handleSubmit}>
          <div className="field">
            <p className="control has-icons-left has-icons-right">
              <input className="input" type="text" placeholder="Username" name="username" onChange={e => handleChange(e)} />
              <span className="icon is-small is-left">
                <i className="fas fa-envelope"></i>
              </span>
              <span className="icon is-small is-right">
                <i className="fas fa-check"></i>
              </span>
            </p>
          </div>
          <div className="field">
            <p className="control has-icons-left">
              <input className="input" type="password" placeholder="Password" name="password" onChange={e => handleChange(e)} />
              <span className="icon is-small is-left">
                <i className="fas fa-lock"></i>
              </span>
            </p>
          </div>
          <div className="field">
            <p className="control">
              <button className="button is-success">
                Login
        </button>
            </p>
          </div>
        </form>
      </div>
    </div>
  </div>
);

export default SignInForm;


================================================
FILE: client/app/components/SignUpForm.js
================================================
import React from 'react'

const SignUpForm = ({ handleChange, handleSubmit }) => (
  <div className="form-container">
    <div className="form">
      <h2> Sign Up</h2>

      <form onSubmit={handleSubmit}>
        <div className="field">
          <p className="control has-icons-left has-icons-right">
            <input
              className="input"
              type="email"
              placeholder="Email"
              name="email"
              onChange={e => handleChange(e)}
            />
            <span className="icon is-small is-left">
              <i className="fas fa-envelope" />
            </span>
            <span className="icon is-small is-right">
              <i className="fas fa-check" />
            </span>
          </p>
        </div>
        <div className="field">
          <p className="control has-icons-left">
            <input
              className="input"
              type="text"
              placeholder="Username"
              name="username"
              onChange={e => handleChange(e)}
            />
            <span className="icon is-small is-left">
              <i className="fas fa-lock" />
            </span>
          </p>
        </div>
        <div className="field">
          <p className="control has-icons-left">
            <input
              className="input"
              type="password"
              placeholder="Password"
              name="password"
              onChange={e => handleChange(e)}
            />
            <span className="icon is-small is-left">
              <i className="fas fa-lock" />
            </span>
          </p>
        </div>
        <div className="field">
          <p className="control has-icons-left">
            <input
              className="input"
              type="password"
              placeholder="Repeat Password"
              name="repeatPassword"
              onChange={e => handleChange(e)}
            />
            <span className="icon is-small is-left">
              <i className="fas fa-lock" />
            </span>
          </p>
        </div>
        <div className="field">
          <p className="control">
            <button className="button is-success">Register</button>
          </p>
        </div>
      </form>
    </div>
  </div>
)

export default SignUpForm


================================================
FILE: client/app/components/Signout.js
================================================
import React from 'react';

const Signout = () => (
  <div> Signout </div>
);

export default Signout;


================================================
FILE: client/app/configureStore.js
================================================
/**
 * Create the store with dynamic reducers
 */

import { createStore, applyMiddleware, compose } from 'redux'
import { fromJS } from 'immutable'
import { routerMiddleware } from 'react-router-redux'
import reduxThunk from 'redux-thunk'

import createReducer from './reducers'
import socketMiddleware from "./middleware/socket";

export default function configureStore(initialState = {}, history) {
  // Create the store with two middlewares
  // 1. sagaMiddleware: Makes redux-sagas work
  // 2. routerMiddleware: Syncs the location/URL path to the state
  const middlewares = [reduxThunk, socketMiddleware, routerMiddleware(history)]

  const enhancers = [applyMiddleware(...middlewares)]

  // If Redux DevTools Extension is installed use it, otherwise use Redux compose
  /* eslint-disable no-underscore-dangle */
  const composeEnhancers =
    process.env.NODE_ENV !== 'production' &&
      typeof window === 'object' &&
      window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
      ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
        // TODO Try to remove when `react-router-redux` is out of beta, LOCATION_CHANGE should not be fired more than once after hot reloading
        // Prevent recomputing reducers for `replaceReducer`
        shouldHotReload: false
      })
      : compose
  /* eslint-enable */

  const store = createStore(
    createReducer(),
    fromJS(initialState),
    composeEnhancers(...enhancers)
  )

  // Extensions
  store.injectedReducers = {} // Reducer registry

  // Make reducers hot reloadable, see http://mxs.is/googmo
  /* istanbul ignore next */
  if (module.hot) {
    module.hot.accept('./reducers', () => {
      store.replaceReducer(createReducer(store.injectedReducers))
      store.dispatch({ type: '@@REDUCER_INJECTED' })
    })
  }

  return store
}


================================================
FILE: client/app/containers/AppContainer.js
================================================
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'

import { isAuthenticated, getUsername } from '../selectors/auth'
import { isSocketAuthenticated } from '../selectors/socket'

import { connectSocket } from '../actions/auth'

import App from '../components/App'

class AppContainer extends Component {
  componentDidMount() {
    /* If we have a token and socket not connected then try and connect */
    const { connectSocket, isSocketAuthenticated } = this.props
    const token = localStorage.getItem('token')
    if (token && !isSocketAuthenticated) {
      try {
        const { access_token } = JSON.parse(token)
        connectSocket(access_token)
      } catch (e) {
        console.log(e)
      }
    }
  }

  render() {
    const { username } = this.props

    return <App username={username} />
  }
}

const mapStateToProps = state => ({
  isAuthenticated: isAuthenticated(state),
  isSocketAuthenticated: isSocketAuthenticated(state),
  username: getUsername(state)
})

const mapDispatchToProps = dispatch => ({
  connectSocket: (url, token) => dispatch(connectSocket(url, token))
})

export default withRouter(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )(AppContainer)
)


================================================
FILE: client/app/containers/GameContainer.js
================================================
import React from 'react'
import { connect } from "react-redux";

import Game from '../components/Game'
import { getGame, getPlayerPosition, isTurnToAct } from '../selectors/games'
import { call, bet, fold, raise, check, postBigBlind, postSmallBlind, sitIn, leaveSeat } from '../actions/games'
import { takeSeat } from '../actions/lobby'

class GameContainer extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      betValue: 0
    }
  }
  handleChange = event => {
    this.setState({ betValue: event.target.value });
  }

  render() {
    return (<Game updateBetValue={this.handleChange} betValue={this.state.betValue} {...this.props} />)
  }
}

const mapStateToProps = (state, { username, match: { params: { tableName } } }) => ({
  game: getGame(tableName)(state),
  isTurnToAct: isTurnToAct(username, tableName)(state),
  playerPosition: getPlayerPosition(tableName)(state)
});

const mapDispatchToProps = (dispatch, { match: { params: { tableName } } }) => ({
  bet: amount => dispatch(bet(tableName, amount)),
  raise: amount => dispatch(raise(tableName, amount)),
  call: () => dispatch(call(tableName)),
  fold: () => dispatch(fold(tableName)),
  check: () => dispatch(check(tableName)),
  postSmallBlind: () => dispatch(postSmallBlind(tableName)),
  postBigBlind: () => dispatch(postBigBlind(tableName)),
  sitDown: chips => dispatch(takeSeat(tableName, chips)),
  sitIn: () => dispatch(sitIn(tableName)),
  leaveGameSeat: () => dispatch(leaveSeat(tableName))
});


export default connect(mapStateToProps, mapDispatchToProps)(GameContainer)


================================================
FILE: client/app/containers/HomeContainer.js
================================================
import Home from '../components/Home'

export default Home

================================================
FILE: client/app/containers/LobbyContainer.js
================================================
import React from 'react'
import { connect } from "react-redux";
import { withRouter } from 'react-router-dom'

import { getLobby, subscribeToTable } from '../actions/lobby'
import { getLobbyState } from '../selectors/lobby'
import Lobby from '../components/Lobby'

class LobbyContainer extends React.Component {
  componentDidMount() {
    this.props.getLobby()
  }

  render() {
    const { lobby } = this.props
    return (<Lobby {...this.props} />)
  }
}

const mapStateToProps = state => ({
  lobby: getLobbyState(state)
});

const mapDispatchToProps = (dispatch) => ({
  getLobby: () => dispatch(getLobby()),
  subscribeToATable: tableName => dispatch(subscribeToTable(tableName))
});

export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LobbyContainer));


================================================
FILE: client/app/containers/NavBarContainer.js
================================================
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'

import { logoutUser } from '../actions/auth'
import { isAuthenticated, getUsername } from '../selectors/auth'
import { getPathname } from '../selectors/route'

import NavBar from '../components/NavBar'

const mapStateToProps = state => ({
  isAuthenticated: isAuthenticated(state),
  username: getUsername(state),
  currRoute: getPathname(state)
})

const mapDispatchToProps = (dispatch, { history }) => ({
  logoutUser: () => dispatch(logoutUser(history))
})

export default withRouter(
  connect(
    mapStateToProps,
    mapDispatchToProps
  )(NavBar)
)


================================================
FILE: client/app/containers/ProfileContainer.js
================================================
import React from 'react'
import { connect } from 'react-redux'

import { getUsername } from '../selectors/auth'
import { getProfileSelector } from '../selectors/profile'

import { getProfile } from '../actions/profile'
import Profile from '../components/Profile'

class ProfileContainer extends React.Component {
  componentDidMount() {
    this.props.getProfile()
  }

  render() {
    return <Profile {...this.props} />
  }
}

const mapStateToProps = state => ({
  username: getUsername(state),
  profile: getProfileSelector(state)
})

const mapDispatchToProps = (dispatch, { username }) => ({
  getProfile: () => dispatch(getProfile(username))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ProfileContainer)


================================================
FILE: client/app/containers/SignInFormContainer.js
================================================
import React from 'react'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'

import { login } from '../actions/auth'
import SignInForm from '../components/SignInForm'

class SignInFormContainer extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      username: '',
      password: ''
    }
  }

  handleChange = event =>
    this.setState({
      [event.target.name]: event.target.value
    })

  handleSubmit = event => {
    const { username, password } = this.state
    this.props.login(
      username,
      password
    )
    event.preventDefault()
  }

  validateForm = () => {
    return this.state.username.length > 0 && this.state.password.length > 0
  }

  render() {
    return (
      <SignInForm
        handleChange={this.handleChange}
        handleSubmit={this.handleSubmit}
      />
    )
  }
}

const mapDispatchToProps = (dispatch, { history }) => ({
  login: (username, password) => dispatch(login(username, password, history))
})

export default connect(
  undefined,
  mapDispatchToProps
)(withRouter(SignInFormContainer))


================================================
FILE: client/app/containers/SignUpFormContainer.js
================================================
import React from 'react'
import { connect } from 'react-redux'
import { withRouter } from 'react-router'

import { register } from '../actions/auth'
import SignUpForm from '../components/SignUpForm'

class SignUpFormContainer extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      email: '',
      username: '',
      password: '',
      repeatPassword: ''
    }
  }

  handleChange = event =>
    this.setState({
      [event.target.name]: event.target.value
    })

  handleSubmit = event => {
    const { username, email, password } = this.state
    this.props.register(
      username,
      email,
      password
    )
    event.preventDefault()
  }

  validateForm = () => {
    return this.state.email.length > 0 && this.state.password.length > 0
  }

  render() {
    return (
      <SignUpForm
        handleChange={this.handleChange}
        handleSubmit={this.handleSubmit}
      />
    )
  }
}

const mapDispatchToProps = (dispatch, { history }) => ({
  register: (username, email, password) => dispatch(register(username, email, password, history))
})

export default connect(
  undefined,
  mapDispatchToProps
)(withRouter(SignUpFormContainer))


================================================
FILE: client/app/index.html
================================================
<!doctype html>
<html lang="en">

<head>
  <!-- The first thing in any HTML file should be the charset -->
  <meta charset="utf-8">

  <!-- Make the page mobile compatible -->
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>Poker</title>
</head>

<body>
  <!-- Display a message if JS has been disabled on the browser. -->
  <noscript>If you're seeing this message, that means
    <strong>JavaScript has been disabled on your browser</strong>, please
    <strong>enable JS</strong> to make this app work.</noscript>

  <!-- The app hooks into this div -->
  <div id="app"></div>
  <!-- Open Sans Font -->
  <!-- A lot of magic happens in this file. HtmlWebpackPlugin automatically includes all assets (e.g. bundle.js, main.css) with the correct HTML tags, which is why they are missing in this HTML file. Don't add any assets here! (Check out the webpack config files in config/webpack for details) -->
</body>

</html>

================================================
FILE: client/app/middleware/socket.js
================================================
import { fromJS } from 'immutable'

import { disconnectSocket } from '../actions/auth'
import {
  socketConnErr,
  socketConnected,
  socketAuthErr,
  socketAuthSuccess
} from '../actions/socket'
import { newLobby, getLobby } from '../actions/lobby'
import { newGameState } from '../actions/games'

import * as types from '../actions/types'

//const SOCKET_API_URL = 'ws://localhost:5000'
// 'wss://tengame.co.uk'

const SOCKET_API_URL =
  process.env.NODE_ENV === 'docker'
    ? 'ws://192.168.99.100:5000'
    : process.env.NODE_ENV === 'production'
    ? 'wss://tengame.co.uk'
    : 'ws://localhost:5000'

import ReconnectingWebSocket from 'reconnecting-websocket'

//process.env.NODE_ENV === 'production' ? 'wss://tengame.co.uk' : 'ws://localhost:5000'

function addHandlers(socket, authToken, dispatch) {
  socket.onopen = event => {
    // connected to server but not authenticated
    dispatch(socketConnected(socket)) // pass ref to socket so dispatcher - socket middleware has access to new connected socket instance
    socket.send(authToken)
  }

  socket.onclose = event => {
    dispatch(disconnectSocket())
    // try and reconnect nearly instantly which is
    // useful when the client has refreshed their web browser
    setTimeout(() => {
      if (socket.connect) {
        socket.connect()
      } else {
      }
    }, 750)
  }

  socket.onmessage = msg => {
    console.log(msg)

    const parsedMsg = JSON.parse(JSON.parse(msg.data))
    if (parsedMsg.tag === 'AuthSuccess') {
      dispatch(socketAuthSuccess())
    }
    if (parsedMsg.tag === 'TableList') {
      const tableList = parsedMsg.contents
      dispatch(newLobby(fromJS(tableList)))
    }
    if (
      parsedMsg.tag === 'SuccessfullySatDown' ||
      parsedMsg.tag === 'NewGameState' ||
      parsedMsg.tag === 'SuccessfullySubscribedToTable'
    ) {
      const tableName = parsedMsg.contents[0]
      const gameState = parsedMsg.contents[1]
      dispatch(newGameState(tableName, fromJS(gameState)))
    }
  }

  socket.onerror = err => {
    console.log(err)
    dispatch(socketAuthErr(err))
    console.log(err)
  }
}

let connectedSocket = null

function connHandler(dispatch, action) {
  if (action.type === types.CONNECT_SOCKET) {
    const { token } = action
    connectedSocket = new ReconnectingWebSocket(SOCKET_API_URL)
    // new WebSocket(SOCKET_API_URL)

    addHandlers(connectedSocket, token, dispatch)
  }

  if (action.type === types.DISCONNECT_SOCKET && connectedSocket) {
    if (connectedSocket.readyState === 1) {
      connectedSocket.close()
    }
  }

  if (action.data && connectedSocket) {
    //if (connectedSocket.readyState === 1)
    // connectedSocket.send(action.payload)
  }
}

/**
 * Allows you to register actions that when dispatched, send the action to the
 * server via a socket.
 * `criteria` may be a function (type, action) that returns true if you wish to send the
 *  action to the server, array of action types, or a string prefix.
 * the third parameter is an options object with the following properties:
 * {
 *   eventName,// a string name to use to send and receive actions from the server.
 *   execute, // a function (action, emit, next, dispatch) that is responsible for
 *            // sending the message to the server.
 * }
 */
const reduxSocketMiddleware = ({ dispatch, getState }) => next => action => {
  connHandler(dispatch, action)
  console.log(action)

  const criteria = 'server/'

  if (connectedSocket) {
    if (evaluate(action, criteria)) {
      return defaultExecute(dispatch, next, action)
    }
  }
  return next(action)
}

function evaluate(action, option) {
  if (!action || !action.type) {
    return false
  }

  const { type } = action
  let matched = false
  if (typeof option === 'function') {
    // Test function
    matched = option(type, action)
  } else if (typeof option === 'string') {
    // String prefix
    matched = type.indexOf(option) === 0
  } else if (Array.isArray(option)) {
    // Array of types
    matched = option.some(item => type.indexOf(item) === 0)
  }
  return matched
}

function defaultExecute(dispatch, next, action) {
  if (connectedSocket) {
    if (connectedSocket.readyState === 1)
      connectedSocket.send(JSON.stringify(action.data))
  }
}

export default reduxSocketMiddleware


================================================
FILE: client/app/reducers/auth.js
================================================
import Immutable from 'immutable';

import * as types from '../actions/types';

const initialState = Immutable.Map({ authenticated: false, username: null, error: null, isLoading: false });

export default function (state = initialState, action) {
  switch (action.type) {
    case types.AUTH_REQUESTED:
      return state.set('isLoading', true);
    case types.AUTHENTICATED:
      return state.set('authenticated', true).set('username', action.username).set('isLoading', false).set('error', null);
    case types.UNAUTHENTICATED:
      return state.set('authenticated', false).set('username', null).set('isLoading', false).set('error', null);
    case types.AUTHENTICATION_ERROR:
      return state.set('error', action.error).set('isLoading', false)
    default:
      return state
  }
}

================================================
FILE: client/app/reducers/games.js
================================================
import Immutable from 'immutable';

import * as types from '../actions/types';

const initialState = Immutable.Map({});

export default function (state = initialState, action) {
  switch (action.type) {
    case types.NEW_GAME_STATE:
      return state.set(action.tableName, action.gameState);
    default:
      return state
  }
}

================================================
FILE: client/app/reducers/lobby.js
================================================
import Immutable, { fromJS } from 'immutable';

import * as types from '../actions/types';

const initialState = Immutable.fromJS([]);

export default function (state = initialState, action) {
  switch (action.type) {
    case types.NEW_LOBBY:
      return fromJS(action.lobby)
    default:
      return state
  }
}

================================================
FILE: client/app/reducers/profile.js
================================================
import Immutable from 'immutable'

import * as types from '../actions/types'

const initialState = Immutable.Map({
  profile: null,
  isLoading: false,
  error: null
})

export default function(state = initialState, action) {
  switch (action.type) {
    case types.GET_PROFILE_REQUEST:
      return state.set('isLoading', true)
    case types.GET_PROFILE_SUCCESS:
      return state
        .set('profile', action.profile)
        .set('isLoading', false)
        .set('error', null)
    case types.GET_PROFILE_ERR:
      return state.set('error', action.error).set('isLoading', false)
    default:
      return state
  }
}


================================================
FILE: client/app/reducers/rootReducer.js
================================================
import { combineReducers } from 'redux-immutable'

import auth from './auth'
import socket from './socket'
import lobby from './lobby'
import games from './games'
import profile from './profile'

const rootReducer = combineReducers({
  auth,
  socket,
  lobby,
  profile,
  games
})

export default rootReducer


================================================
FILE: client/app/reducers/socket.js
================================================

import Immutable, { fromJS } from 'immutable';

import {
  SOCKET_AUTH_SUCCESS,
  SOCKET_AUTH_ERR,
  SOCKET_CONN_ERR,
  SOCKET_CONNECTED,
  DISCONNECT_SOCKET
} from '../actions/types'

const initialState = fromJS({
  socketAuth: false,
  socketAuthErr: null,
  socketConnErr: null,
  socketConnected: false
})

export default function socket(state = initialState, action) {
  switch (action.type) {
    case SOCKET_CONNECTED:
      return state.set('socketConnected', true).set('socketConnErr', null).set('socketAuthErr', null)
    case DISCONNECT_SOCKET:
      return state.set('socketConnected', false).set('socketConnErr', null).set('socketAuthErr', null)
    case SOCKET_AUTH_SUCCESS:
      return state.set('socketAuth', true)
    case SOCKET_AUTH_ERR:
      return state.set('socketAuthError', action.err)
    case SOCKET_CONN_ERR:
      return state.set('socketConnError', action.err)
    default:
      return state
  }
}

================================================
FILE: client/app/reducers/tests/auth.test.js
================================================
/* eslint-disable */
import { Map } from "immutable";

import reducer from "../auth";
import * as types from "../../actions/types";

const initialState = Map({
  authenticated: false,
  username: null,
  error: null,
  isLoading: false
});

describe("auth reducer", () => {
  it("should return the initial state", () => {
    expect(reducer(undefined, {})).toEqual(initialState);
  });

  it("should handle authentication success", () => {
    const username = 'Argo'
    const expectedState = Map({
      authenticated: true,
      username,
      error: null,
      isLoading: false
    });

    expect(
      reducer(initialState,
        { type: types.AUTHENTICATED, username }
      ))
      .toEqual(expectedState)
  });


  it("should handle authentication errors", () => {
    const error = '404'
    const expectedState = Map({
      authenticated: false,
      username: null,
      error,
      isLoading: false
    });

    expect(reducer(
      initialState, { type: types.AUTHENTICATION_ERROR, error }
    )).toEqual(expectedState)
  });

  it("should handle logout", () => {
    const initialState = Map({
      authenticated: true,
      username: 'Argo',
      error: null,
      isLoading: false
    });
    const expectedState = Map({
      authenticated: false,
      username: null,
      error: null,
      isLoading: false
    });

    expect(reducer(initialState, { type: types.UNAUTHENTICATED })).toEqual(expectedState)
  });
});

================================================
FILE: client/app/reducers.js
================================================
/**
 * Combine all reducers in this file and export the combined reducers.
 */

import { fromJS } from 'immutable'
import { combineReducers } from 'redux-immutable'
import { LOCATION_CHANGE } from 'react-router-redux'

import rootReducer from 'reducers/rootReducer'

/*
 * routeReducer
 *
 * The reducer merges route location changes into our immutable state.
 * The change is necessitated by moving to react-router-redux@5
 *
 */

// Initial routing state
const routeInitialState = fromJS({
  location: null
})

/**
 * Merge route into the global application state
 */
function routeReducer(state = routeInitialState, action) {
  switch (action.type) {
    /* istanbul ignore next */
    case LOCATION_CHANGE:
      return state.merge({
        location: action.payload
      })
    default:
      return state
  }
}

/**
 * Creates the main reducer with the dynamically injected ones
 */
export default function createReducer(injectedReducers) {
  return combineReducers({
    route: routeReducer,
    global: rootReducer,
    ...injectedReducers
  })
}


================================================
FILE: client/app/selectors/auth.js
================================================
import Immutable from 'immutable'
import { createSelector } from 'reselect'

export const auth = state => state.get('global').get('auth')

export const isAuthenticated = createSelector(auth, state =>
  state.get('authenticated')
)

export const getUsername = createSelector(auth, state => state.get('username'))


================================================
FILE: client/app/selectors/games.js
================================================
import { createSelector } from 'reselect'

export const getGames = state => state.get('global').get('games')

export const getGame = tableName =>
  createSelector(getGames, games => games.get(tableName))

export const getCurrentPlayerToAct = tableName => (
  getGame(tableName),
  game => {
    if (!game) {
      return null
    }

    const currentPosToAct = game.get('_currentPosToAct')

    if (Number.isInteger(currentPosToAct)) {
      const player = game.get('_players').get(currentPosToAct)

      if (player.get("_hasActed") == true) {
        return null
      } else {
        return player.get('_playerName')
      }
    }

    return null
  }
)

export const isTurnToAct = (username, tableName) => createSelector(
  getCurrentPlayerToAct(tableName),
  playerName => playerName === username
)

export const getPlayerPosition = (tableName, username) => createSelector(
  getGame(tableName), game => {
    if (!game) return null
    return game.get('_players')
      .findIndex(
        plyr => plyr.get('_playerName') === username
      )
  }
)


================================================
FILE: client/app/selectors/lobby.js
================================================
import Immutable from 'immutable';
import { createSelector } from 'reselect'

export const getLobbyState = state => state.get('global').get('lobby')


================================================
FILE: client/app/selectors/profile.js
================================================
import Immutable from 'immutable'
import { createSelector } from 'reselect'

export const profile = state => ({})

export const getProfileSelector = createSelector(profile, profile => ({}))


================================================
FILE: client/app/selectors/route.js
================================================
import Immutable from 'immutable'
import { createSelector } from 'reselect'

export const getLocation = state => state.get('route').get('location')

export const getPathname = createSelector(
  getLocation,
  location => (location ? location.get('pathname') : null)
)


================================================
FILE: client/app/selectors/socket.js
================================================
import Immutable from 'immutable';
import { createSelector } from 'reselect'

export const socket = state => state.get('global').get('socket')

export const isSocketAuthenticated = createSelector(
    socket,
    state => state.get('isSocketAuth')
)


================================================
FILE: client/app/selectors/tests/games.test.js
================================================
/* eslint-disable */
import Immutable from 'immutable'

describe('Games Selectors', () => {
  describe('getGames', () => {
    it('should return correct action an authSuccess action for received asset', () => {
      expect(authRequested()).toEqual({ type: types.AUTH_REQUESTED })
    })
  })
})


================================================
FILE: client/app/styles/_common.scss
================================================
/* Re-export all common scss files */
@import 'common/mixins';
@import 'common/variables';
@import 'common/typography';
@import 'common/colours';

================================================
FILE: client/app/styles/common/_colours.scss
================================================
@import "variables";

.card {
  background-color: $neutral-colour-100;
  color: $neutral-colour-800;
}

.navbar {
  //background-color: $neutral-colour-700;
  background-color: $neutral-accent-700; // #e8e9f3; // #a18276; // #39a9db; // #39a9db;
  //  background-color: $neutral-accent-600;
  color: $neutral-colour-100;
}
.action-panel {
  background-color: transparent;
}

.hidden-pocket-cards .card {
  background-color: $neutral-colour-100; // $neutral-colour-100;
}

%seat {
  background-color: $primary-colour-700;
}


================================================
FILE: client/app/styles/common/_mixins.scss
================================================
@import "variables";

/*
---------------------------------
  Media queries
---------------------------------
*/

// Small devices
@mixin xs {
  @media (min-width: #{$screen-xs-min}) {
    @content;
  }
}

// Small devices
@mixin sm {
  @media (min-width: #{$screen-sm-min}) {
    @content;
  }
}

// Medium devices
@mixin md {
  @media (min-width: #{$screen-md-min}) {
    @content;
  }
}

// Large devices
@mixin lg {
  @media (min-width: #{$screen-lg-min}) {
    @content;
  }
}

// Extra large devices
@mixin xl {
  @media (min-width: #{$screen-xl-min}) {
    @content;
  }
}

// Extra large devices
@mixin xxl {
  @media (min-width: #{$screen-xxl-min}) {
    @content;
  }
}

// Game
%seat {
  margin: auto;
  justify-content: center;
  align-content: center;
  display: grid;
  width: 100%;
  height: 70%;
  border-radius: 7em;
  min-height: 5.3rem;
}

%showdown-pocket-cards {
  margin: 0;
  position: relative;
  top: 0.42em;
  justify-self: center;
  align-self: end;
  z-index: -1;
}

/* A poker cards dimensions are 63.5mm X 88.9mm */
%card {
  height: 2.5em;
  width: 1.7em;
  margin-left: 0.2em;
  text-align: center;
  font-weight: 800;
  background-color: $neutral-colour-100;
  border-radius: 0.33em;
  color: neutral-colour-900;
}

%dealer-btn {
  height: 1.9em;
  width: 1.9em;
  font-family: "Raleway", sans-serif;
  font-weight: 600;
  font-size: 1em;
  line-height: 2em;
  text-align: center;
  letter-spacing: -0.1em;
  border-radius: 1.7em;
  background-color: $neutral-accent-600;
  //box-shadow: 1px 1px 1px 0px rgba(18, 27, 33, 0.55);
}


================================================
FILE: client/app/styles/common/_typography.scss
================================================
// Import Modular Scale Plugin
@import "~modularscale-sass/stylesheets/modularscale";

@import "variables";

/*
---------------------------------
  Typography
---------------------------------
*/

// Monospaced

@font-face {
  font-family: "MonoP";
  src: url("../../static/fonts/MonoP-Medium.woff") format("woff"); /* Pretty Modern Browsers */
}

@font-face {
  font-family: "MonoP-Bold";
  src: url("../../static/fonts/MonoP-Bold.woff") format("woff"); /* Pretty Modern Browsers */
}

$monospaced-font: "MonoP", sans-serif;

$monospaced-font2: "MonoP-Bold", sans-serif;

.monospaced-font {
  font-family: $monospaced-font;
}

.monospaced-font-bold {
  font-family: $monospaced-font2;
}

/* cabin-500 - latin */
@font-face {
  font-family: "Cabin";
  font-style: normal;
  font-weight: 500;
  src: local("Cabin Medium"), local("Cabin-Medium"),
    /* IE6-IE8 */ url("../../static/fonts/cabin/cabin-v14-latin-500.woff2") format("woff2"),
    /* Super Modern Browsers */ url("../../static/fonts/cabin/cabin-v14-latin-500.woff") format("woff"),
    /* Modern Browsers */ url("../../static/fonts/cabin/cabin-v14-latin-500.ttf") format("truetype");
}

// Raleway

/* raleway-200 - latin */
@font-face {
  font-family: "Raleway";
  font-style: normal;
  font-weight: 200;
  src: local("Raleway ExtraLight"), local("Raleway-ExtraLight"),
    url("../../static/fonts/raleway/raleway-v14-latin-200.woff2") format("woff2"),
    /* Chrome 26+, Opera 23+, Firefox 39+ */ url("../../static/fonts/raleway/raleway-v14-latin-200.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* raleway-300 - latin */
@font-face {
  font-family: "Raleway";
  font-style: normal;
  font-weight: 300;
  src: local("Raleway Light"), local("Raleway-Light"),
    url("../../static/fonts/raleway/raleway-v14-latin-300.woff2") format("woff2"),
    /* Chrome 26+, Opera 23+, Firefox 39+ */ url("../../static/fonts/raleway/raleway-v14-latin-300.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* raleway-regular - latin */
@font-face {
  font-family: "Raleway";
  font-style: normal;
  font-weight: 400;
  src: local("Raleway"), local("Raleway-Regular"),
    url("../../static/fonts/raleway/raleway-v14-latin-regular.woff2") format("woff2"),
    /* Chrome 26+, Opera 23+, Firefox 39+ */ url("../../static/fonts/raleway/raleway-v14-latin-regular.woff")
      format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* raleway-500 - latin */
@font-face {
  font-family: "Raleway";
  font-style: normal;
  font-weight: 500;
  src: local("Raleway Medium"), local("Raleway-Medium"),
    url("../../static/fonts/raleway/raleway-v14-latin-500.woff2") format("woff2"),
    /* Chrome 26+, Opera 23+, Firefox 39+ */ url("../../static/fonts/raleway/raleway-v14-latin-500.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* raleway-700 - latin */
@font-face {
  font-family: "Raleway";
  font-style: normal;
  font-weight: 700;
  src: local("Raleway Bold"), local("Raleway-Bold"),
    url("../../static/fonts/raleway/raleway-v14-latin-700.woff2") format("woff2"),
    /* Chrome 26+, Opera 23+, Firefox 39+ */ url("../../static/fonts/raleway/raleway-v14-latin-700.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* raleway-800 - latin */
@font-face {
  font-family: "Raleway";
  font-style: normal;
  font-weight: 800;
  src: local("Raleway ExtraBold"), local("Raleway-ExtraBold"),
    url("../../static/fonts/raleway/raleway-v14-latin-800.woff2") format("woff2"),
    /* Chrome 26+, Opera 23+, Firefox 39+ */ url("../../static/fonts/raleway/raleway-v14-latin-800.woff") format("woff"); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}

$raleway-bold: "Raleway Bold", sans-serif;

$cabin: "Cabin", sans-serif;

// Configure modular-scale plugin
$modularscale: (
  base: 0.6em,
  ratio: 1.15,
  $screen-sm-min: (
    base: 0.8em,
    ratio: 1.25
  ),
  $screen-md-min: (
    base: 0.9em,
    ratio: 1.25
  ),
  $screen-lg-min: (
    base: 0.9em,
    ratio: 1.25
  ),
  $screen-xl-min: (
    base: 1em,
    ratio: 1.25
  ),
  $screen-xxl-min: (
    base: 1em,
    ratio: 1.333
  )
);

h1,
h2,
h3,
h4,
h5 {
  margin: 0;
}

h1,
.h1 {
  @include ms-respond(font-size, 5);
  font-family: "Raleway", sans-serif;
  font-weight: 800;
}

h2,
.h2 {
  @include ms-respond(font-size, 4);
  font-family: "Raleway", sans-serif;
  font-weight: 800;
}

h3,
.h3 {
  @include ms-respond(font-size, 3);
  font-family: "Raleway", sans-serif;
  letter-spacing: 1.3px;
  font-weight: 800;
}

h4,
.h4 {
  @include ms-respond(font-size, 1);
  letter-spacing: 0.8px;
  font-weight: 800;
  font-family: "Raleway", sans-serif;
}

h5,
.h5 {
  @include ms-respond(font-size, 1);
  letter-spacing: 1px;
  font-family: "Raleway", sans-serif;
  font-weight: 800;
}

body,
.body {
  @include ms-respond(font-size, 0);
  font-family: "Raleway", sans-serif;
  //letter-spacing: 1px;
}

.brand-title {
  letter-spacing: 1.4px;
  font-family: "Raleway", sans-serif;
  font-weight: 700;
  opacity: 0.85;
  color: $neutral-colour-100;
  text-transform: uppercase;
}

.card {
  font-size: 1.6em;
  @include md {
    font-size: 1.65em;
  }
  @include lg {
    font-size: 1.7em;
  }

  @include xxl {
    font-size: 2.2em;
  }
  font-family: "GothamPro-Black", sans-serif;
}

@include md {
  margin-left: 14vw;
  margin-right: 14vw;
}

/* Prevents table Aspect Ratio from being too wide on wider screens. */
@include xl {
  margin-left: 20vw;
  margin-right: 20vw;
}

.button {
  font-family: "Raleway", sans-serif;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  font-size: 1em;
  line-height: 1em;
}

.button > .monospaced-font-bold {
  font-size: 1.2em;
  //font-family: "Raleway", sans-serif;
  font-weight: 700;
  text-transform: uppercase;
}

//.monospaced-font
.player-name {
  //font-family: $monospaced-font;
  color: $neutral-colour-300;
  font-weight: 700;
  font-size: 1.3em;
  text-align: center;
  text-transform: uppercase;
}

.navbar-username {
}

.pot-label {
  font-weight: 800;
  align-self: center;
  justify-self: center;
}

td {
  font-weight: 700;
  @include ms-respond(font-size, 1);
  letter-spacing: 0.8px;
}


================================================
FILE: client/app/styles/common/_variables.scss
================================================
/*** Variables ***/

/*
---------------------------------
  Colours
---------------------------------
*/

$primary-colour-100: #edf6fc;
$primary-colour-200: #a0d7ff;
$primary-colour-300: #4bb3fd;
$primary-colour-400: #14a1ff;
$primary-colour-500: #027cce;
$primary-colour-600: #015c99;
$primary-colour-700: #014877;
$primary-colour-800: #39a9db;
$primary-colour-900: #003856;

$neutral-colour-100: #e5f1f9;
$neutral-colour-200: #dee2e2;
$neutral-colour-300: #cee2e2;
$neutral-colour-400: #5d8989;
$neutral-colour-500: #4f535b;
$neutral-colour-600: #244242;
$neutral-colour-700: #243942;
$neutral-colour-800: #131414;

$neutral-accent-100: #cfe8f9;
$neutral-accent-200: #a0c9e5;
$neutral-accent-300: #6698ba;
$neutral-accent-400: #56819e;
$neutral-accent-500: #495c68;
$neutral-accent-600: #213b4f;
$neutral-accent-700: #172630;
$neutral-accent-900: #121b21;

//2c3e50
//$gradient: radial-gradient($neutral-accent-600, $neutral-accent-900);
$gradient: radial-gradient($neutral-accent-600, $neutral-accent-900);

//$success-accent-100:
//$success-accent-200:
//$success-accent-300:
//$success-accent-400:
//$success-accent-500:
//$success-accent-600:
//$success-accent-700:
//
//$warning-accent-100:
//$warning-accent-200:
//$warning-accent-300:
//$warning-accent-400:
//$warning-accent-500:
//$warning-accent-600:
//$warning-accent-700:
//
//$danger-accent-100:
//$danger-accent-200:
//$danger-accent-300:
//$danger-accent-400:
//$danger-accent-500:
//$danger-accent-600:
//$danger-accent-700:

// table gradient should comprise of mixed palette colours
$dark-imperial-blue: #003e60;

$anti-flash-white: white; // #F0EFF4;

$prussian-blue: #003049;
$dark-cerulean: #064789;
$eerie-black: #161925;
$deep-space-purple: #415a77;

$outer-space-grey: #424b54;

$color-grey-400: $outer-space-grey;

// or
$orioles-orange: #f34213; // perhaps busies the palette too much

// App colours
$color-primary: $prussian-blue;
$color-secondary: $dark-imperial-blue;
$color-accent: $orioles-orange;
$color-light: $deep-space-purple;
$color-darkest: $eerie-black;

$color-brand: $prussian-blue;

// gradients
$gotham: radial-gradient(#240b36, $dark-imperial-blue);

$witching-hour: radial-gradient(#c31432, #240b36);
$night-hawk: radial-gradient(#2980b9, #2c3e50);
$red-mist: radial-gradient(#e74c3c, #000000);
$namn: linear-gradient(#a73737, #7a2828);

// Brand colours
$color-facebook: #3b5998;
$color-feedly: #2bb24c;
$color-github: #333;
$color-google: #dc4e41;
$color-instagram: #3f729b;
$color-linkedin: #0077b5;
$color-medium: #00ab6b;
$color-messenger: #0084ff;
$color-rss: #f26522;
$color-spotify: #2ebd59;
$color-twitter: #55acee;

// Borders
$border-light: solid 1px rgba(0, 0, 0, 0.05);

/*
---------------------------------
  Media queries
---------------------------------
*/

// Very small devices such as small phones
$screen-xs-min: 376px;

// Small tablets and large smartphones (landscape view)
$screen-sm-min: 576px;

// Small tablets (portrait view)
$screen-md-min: 798px;

// Tablets and small desktops
$screen-lg-min: 1222px;

// Large tablets and desktops
$screen-xl-min: 1250px;

// Large tablets and desktops
$screen-xxl-min: 1600px;

/*
---------------------------------
  Animation
---------------------------------
*/

// Animation
$anime-in: 0.4s;
$anime-out: 0.5s;


================================================
FILE: client/app/styles/components/_buttons.scss
================================================
@import "../common";

.button {
  border-radius: 1.5em;
  width: 8em;
  height: 4rem;
  margin: 0.5em;
  padding: 0.8em;
  border: 0;
  background-color: $primary-colour-700;
  color: $neutral-colour-100;
  border: none;
}

.button:hover,
.button:focus {
  background-color: $primary-colour-600;
  transition: background-color ease-in-out 0.25s;
  outline: 0;
}


================================================
FILE: client/app/styles/components/_footer.scss
================================================
.footer {
  background: green;
}

================================================
FILE: client/app/styles/components/_forms.scss
================================================
.form-container {
  width: 35vw;
  min-width: 200px;
  margin: auto;
  text-align: center;
}
.form {
  text-align: centre;
}

.form-container {
  margin: auto;
}


================================================
FILE: client/app/styles/components/_game.scss
================================================
@import "game/table";
@import "game/actionPanel";
@import "game/boardCards";
@import "game/cards";
@import "game/seat";
@import "game/slider";

.game-container {
  display: flex;
  justify-content: center;
  align-items: center;
  grid-column-start: 2;
  grid-row-start: 1;
  margin-top: 11vh;
}

.game-grid {
  grid-area: 2/2/5/5;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  grid-template-rows: repeat(6, 1fr);
}


================================================
FILE: client/app/styles/components/_lobby.scss
================================================
.game-table-list {
  margin: auto;
  text-align: center;
  margin-top: 10%;
  width: 60%;
}

.game-table-list tr {
  padding-top: 1em;
}


================================================
FILE: client/app/styles/components/_navbar.scss
================================================
.navbar {
  display: flex;
  text-align: center;
  min-height: 4em;
}

.navbar-brand {
  width: 16em;
  margin: auto;
  text-align: left;
  padding-left: 2.5em;
}

.navbar-menu {
  display: flex;
  justify-content: space-between;
  width: 100%;
}

.navbar-start {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.navbar-end {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-right: 2.5em;
}

.navbar-item {
  width: 9em;
  margin: 7%;
}

.navbar-item-active {
  color: $neutral-colour-500;
}

.navbar-item h4 {
  font-weight: 700;
}

.navbar-item:hover {
  opacity: 0.82;
  transition: all 0.08s linear;
}

.navbar-item-active {
  width: 7em;
  margin: 7%;
  color: $eerie-black;
}

.brand-title {
  letter-spacing: 0.1em;
}


================================================
FILE: client/app/styles/components/game/_actionPanel.scss
================================================
.action-panel {
  grid-column-start: 2;
  grid-row-start: 2;
  width: 70%;
  margin: auto;
  grid-row-end: 4;
  display: flex;

  @include md {
    border-radius: 3em 3em 0 0;
  }
}

.user-actions-container {
  margin: auto;
}


================================================
FILE: client/app/styles/components/game/_boardCards.scss
================================================
.board-cards {
  justify-self: center;
  align-self: center;
  grid-area: 3/1/5/6;
}

.board-cards-container {
  display: flex;
  height: 100%;
  width: 100%;
  justify-self: center;
  align-self: center;
}


================================================
FILE: client/app/styles/components/game/_cards.scss
================================================
@import "../../common";

.rank {
  font-family: "MonoP-Bold", sans-serif;
  font-weight: 800;
  height: 1.32em;
}

.suit > img {
  height: 0.75em;
  width: 0.75em;
  margin-bottom: 1.3em;
}

.suit-icon {
  width: 100%;
}

.card {
  @extend %card;
}

/* cards behind player oval representing opponent pockets outwith showdown */
.hidden-pocket-cards .card {
  position: absolute;
  z-index: -1;
  margin: 0;
  height: 3em;
  top: -1em;
}

.hidden-pocket-cards-container {
  width: 70%;
  margin: auto;
}

.pocket-one {
  right: 65%;
  margin-left: 0;
}

/* Pocket cards held by opponents that are face down */
.hidden-pocket-cards {
  position: relative;
  margin: auto;
  width: 0;
}

/* Cards displayed at showdown  */
.showdown-pocket-cards-container {
  width: 100%;

  display: flex;
}

.showdown-pocket-cards-container:first-child {
  margin-right: 0.15em;
}

.showdown-pocket-cards-container:last-child {
  margin-right: 0.15em;
}

.showdown-pocket-cards-0 {
  @extend %showdown-pocket-cards;
  grid-area: 4/3;
}

.showdown-pocket-cards-1 {
  @extend %showdown-pocket-cards;
  grid-area: 3/1;
}

.showdown-pocket-cards-2 {
  @extend %showdown-pocket-cards;
  grid-area: 1/1;
}

.showdown-pocket-cards-3 {
  @extend %showdown-pocket-cards;
  grid-area: 1/3;
}

.showdown-pocket-cards-4 {
  @extend %showdown-pocket-cards;
  grid-area: 1/5;
}

.showdown-pocket-cards-5 {
  @extend %showdown-pocket-cards;
  grid-area: 3/5;
}

.showdown-pocket-cards-6 {
  @extend %showdown-pocket-cards;
  grid-area: 1/3;
}


================================================
FILE: client/app/styles/components/game/_seat.scss
================================================
/*

The real power of mixins comes when you pass them arguments. 
Arguments are declared as a parenthesized, comma-separated list of variables. 

Each of those variables is assigned a value each time the mixin is used.

@mixin default-box($color, $boxModel, $padding) {
  $borderColor: $color;
  border: 1px solid $borderColor;
  clear: both;
  display: $boxModel;
  margin: 5px 0;
  padding: 5px $padding;
}

header{ @include default-box(#666, block, 10px); }
footer{ @include default-box(#999, inline-block, 5px);
*/
/* Mixins */
@import "../../common";

.player-chip-count > .monospaced-font-bold {
  font-size: 1.2em;
  @include lg {
    font-size: 1.2em;
  }
  @include xxl {
    font-size: 1.33em;
  }
}

// current seated player's turn to act
.active-player {
  border: 0.3em;
  border-style: solid;
  box-sizing: border-box;
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  border-color: $neutral-accent-100;
}

.disabled {
  opacity: 0.9;
}

div[class^="seat-"] > h4 {
  margin: 0;
}
div[class^="seat-"] > h5 {
  margin: 0;
}
div[class^="seat-"] > h3 {
  margin: 0;
}

.seat-0 {
  @extend %seat;
}

.seat-0-container {
  grid-area: 5/3;
}

.seat-1 {
  @extend %seat;
}

.seat-1-container {
  grid-area: 4/1;
}

.seat-2 {
  @extend %seat;
  grid-area: 2/1;
}

.seat-2-container {
  grid-area: 2/1;
}

.seat-3 {
  @extend %seat;
  grid-area: 1/3;
}

.seat-3-container {
  grid-area: 1/3;
}

.seat-4 {
  @extend %seat;
  grid-area: 2/5;
}

.seat-4-container {
  grid-area: 2/5;
}

.seat-5 {
  @extend %seat;
}

.seat-5-container {
  grid-area: 4/5;
}

.empty-seat {
  opacity: 0.15;
  color: transparent;
}

.empty-seat:hover {
  opacity: 0.2;
  transition: ease-in-out 0.1s;
  color: $primary-colour-700;
}


================================================
FILE: client/app/styles/components/game/_slider.scss
================================================
.slidecontainer {
  width: 100%; /* Width of the outside container */
  margin: auto;
  text-align: center;
  padding-bottom: 1.5rem;
}

/* The slider itself */
.slider {
  -webkit-appearance: none; /* Override default CSS styles */
  appearance: none;
  width: 50%; /* Full-width */

  height: 0.3em; /* Specified height */
  background: $neutral-colour-700; /* Grey background */
  outline: none; /* Remove outline */
  opacity: 0.5; /* Set transparency (for mouse-over effects on hover) */
  -webkit-transition: 0.2s; /* 0.2 seconds transition on hover */
  transition: opacity 0.2s;
}

/* Mouse-over effects */
.slider:hover {
  opacity: 1; /* Fully shown on mouse-over */
}

/* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
.slider::-webkit-slider-thumb {
  -webkit-appearance: none; /* Override default look */
  appearance: none;
  width: 1.1em; /* Set a specific slider handle width */
  height: 1.1em; /* Slider handle height */
  background: $neutral-accent-200;
  border-radius: 100%;
  cursor: pointer; /* Cursor on hover */
}

.slider::-moz-range-thumb {
  width: 2em; /* Set a specific slider handle width */
  height: 2em; /* Slider handle height */
  background: #4caf50; /* Green background */
  cursor: pointer; /* Cursor on hover */
}


================================================
FILE: client/app/styles/components/game/_table.scss
================================================
/* Six Player Table */
.table-container {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1.5fr 1fr 1.5fr;
  grid-template-rows: repeat(5, 5fr);
  width: 100%;
  height: 100%;
  margin-left: 14vw;
  margin-right: 14vw;

  @include md {
    margin: auto;
  }
}

.table-container h2 {
  justify-self: center;
  margin: 0;
}

/* 
  Selects the chip count inside the player oval 
  
  Adds whitespace between player name and chip count
*/
.table-container h2 + h2 {
  padding: 0.7em 0 0 0;
}

.game-table {
  position: relative;
  grid-area: 2/2/5/5;
  width: 120%;
  height: 120%;
  left: -10.25%;
  top: -13.4%;
  border-radius: 100%;
  z-index: -10;
  background: radial-gradient($primary-colour-600, $primary-colour-900);
}

.pot-label {
  grid-area: 2/2/2/5;
  top: -0.6em;
  position: relative;
  color: $neutral-colour-100;
  justify-self: center;
}

.winners-label {
  color: $neutral-colour-100;
  font-family: $cabin;
  text-transform: capitalize;
  grid-area: 3/1/3/6;
  justify-self: center;
  font-weight: 500;
  top: -0.9em;
  position: relative;
}

.dealer-btn-pos-0 {
  @extend %dealer-btn;
  grid-area: 6/2;
  justify-self: center;
  align-self: center;
}

.dealer-btn-pos-1 {
  @extend %dealer-btn;
  grid-area: 4/1;
  align-self: center;
}

.dealer-btn-pos-2 {
  @extend %dealer-btn;
  grid-area: 1/2;

  justify-self: left;
  align-self: center;
}

.dealer-btn-pos-3 {
  @extend %dealer-btn;
  grid-area: 1/4;
}

.dealer-btn-pos-4 {
  @extend %dealer-btn;
  grid-area: 2/5;
  justify-self: center;
}

.dealer-btn-pos-5 {
  @extend %dealer-btn;
  grid-area: 4/5;
  justify-self: center;
}

.player-bet-label {
  margin-right: 0.25em;
  margin-left: 0.32em;
  height: 1.25em;
  width: 100%;
}

.player-bet-chip {
  background-color: $neutral-accent-200;
  height: 1.25em;
  width: 1.25em;
  border-radius: 1.25em;
  //box-shadow: 1px 1px 1px 0px rgba(0, 0, 0, 0.55);
}

.player-bet-container-pos-0 {
  grid-area: 5/3/5/5;
}

.player-bet-pos-0 {
  display: flex;
  justify-content: center;
}

.player-bet-container-pos-1 {
  grid-area: 5/1/5/3;
  align-self: start;
}

.player-bet-pos-1 {
  width: 100%;
  text-align: left;
  display: flex;
}

.player-bet-container-pos-2 {
  grid-area: 2/1 / span 1 / span 2;
  position: relative;
}

.player-bet-pos-2 {
  text-align: left;
  display: flex;
}

.player-bet-container-pos-3 {
  margin-top: 0.25em;
  grid-area: 1/4 / span 1 / span 2;
  text-align: left;
}

.player-bet-pos-3 {
  width: 100%;
  display: flex;
  justify-content: right;
}

.player-bet-container-pos-4 {
  grid-area: 2/5;
  position: relative;
  text-align: right;
}

.player-bet-pos-4 {
  text-align: right;
  position: absolute;
  bottom: 0;
  display: flex;
  flex-direction: row-reverse;
}

.player-bet-container-pos-5 {
  grid-area: 5/5;
  text-align: right;
  position: relative;
}

.player-bet-pos-5 {
  position: absolute;
  display: flex;
  flex-direction: row-reverse;
}


================================================
FILE: client/app/styles/layout/_app.scss
================================================
@import "../common/mixins";

%full-screen {
  position: absolute;
  height: 100%;
  width: 100%;
}

.app {
  @extend %full-screen;
}

.app-wrapper {
  @extend %full-screen;
  display: grid;
  grid-template-columns: auto;
  grid-template-rows: 0.8fr 15fr;
  grid-template-areas:
    "navbar"
    "main";
}

.game-view-grid {
  display: grid;
  grid-template-columns: max-content;
  grid-template-rows: 75vh 10vh;

  @include md {
    margin-left: 14vw;
    margin-right: 14vw;
  }

  /* Prevents table Aspect Ratio from being too wide on wider screens. */
  @include xl {
    margin-left: 23vw;
    margin-right: 23vw;
  }
}


================================================
FILE: client/app/styles/main.scss
================================================
/* Main App Grid */
@import "layout/app";

/* Improt common modules */
@import "common";

/* Components */
@import "components/game";
@import "components/footer";
@import "components/navbar";
@import "components/buttons";
@import "components/lobby";

@import "components/forms";

// Main Layout Colours
html {
  color: $neutral-colour-100;
  background: $gradient; // #10212d;
  height: 100%;
}


================================================
FILE: client/app/utils/request.js
================================================
/**
 * Parses the JSON returned by a network request
 *
 * @param  {object} response A response from a network request
 *
 * @return {object}          The parsed JSON from the request
 */
export function parseJSON(response) {
  if (response.status === 204 || response.status === 205) {
    return null
  }
  return response.json()
}

/**
 * Checks if a network request came back fine, and throws an error if not
 *
 * @param  {object} response   A response from a network request
 *
 * @return {object|undefined} Returns either the response, or throws an error
 */
export function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response
  }

  const error = new Error(response.statusText)
  error.response = response
  throw error
}


================================================
FILE: client/config/jest-mocks/cssModule.js
================================================
module.exports = 'CSS_MODULE'


================================================
FILE: client/config/jest-mocks/image.js
================================================
module.exports = 'IMAGE_MOCK'


================================================
FILE: client/config/jest.config.js
================================================
module.exports = {
  collectCoverageFrom: [
    'app/**/*.{js,jsx}',
    '!app/**/*.test.{js,jsx}',
    '!app/*/RbGenerated*/*.{js,jsx}',
    '!app/app.js',
    '!app/*/*/Loadable.{js,jsx}'
  ],
  coverageThreshold: {
    global: {
      statements: 90,
      branches: 80,
      functions: 92,
      lines: 90
    }
  },
  coverageReporters: ['json', 'lcov', 'text-summary'],
  moduleDirectories: ['node_modules', 'app'],
  moduleNameMapper: {
    '.*\\.(css|less|styl|scss|sass)$':
      '<rootDir>/config/jest-mocks/cssModule.js',
    '.*\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
      '<rootDir>/config/jest-mocks/image.js'
  },
  setupTestFrameworkScriptFile: '<rootDir>/config/test-setup.js',
  testEnvironment: 'node',
  testRegex: 'tests/.*\\.test\\.js$'
}


================================================
FILE: client/config/test-setup.js
================================================
// needed for regenerator-runtime
// (ES7 generator support is required by redux-saga)
import 'babel-polyfill'

// Enzyme adapter for React 16
import Enzyme from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'

Enzyme.configure({ adapter: new Adapter() })


================================================
FILE: client/config/webpack.base.babel.js
================================================
/**
 * COMMON WEBPACK CONFIGURATION
 */

const path = require('path')
const webpack = require('webpack')

process.noDeprecation = true

module.exports = options => ({
  mode: options.mode,
  entry: options.entry,
  output: Object.assign(
    {
      // Compile into js/build.js
      path: path.resolve(process.cwd(), 'build'),
      publicPath: '/'
    },
    options.output
  ), // Merge with env dependent settings
  module: {
    rules: [
      {
        test: /\.js$/, // Transform all .js files required somewhere with Babel
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: options.babelQuery
        }
      },
      {
        // Preprocess our own .scss files
        test: /\.scss$/,
        exclude: /node_modules/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      },
      {
        // Preprocess 3rd party .css files located in node_modules
        test: /\.css$/,
        include: /node_modules/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(eot|svg|otf|ttf|woff|woff2)$/,
        use: 'file-loader'
      },
      {
        test: /\.(jpg|png|gif)$/,
        use: [
          'file-loader',
          {
            loader: 'image-webpack-loader',
            options: {
              query: {
                gifsicle: {
                  interlaced: true
                },
                mozjpeg: {
                  progressive: true
                },
                optipng: {
                  optimizationLevel: 7
                },
                pngquant: {
                  quality: '65-90',
                  speed: 4
                }
              }
            }
          }
        ]
      },
      {
        test: /\.html$/,
        use: 'html-loader'
      },
      {
        test: /\.json$/,
        use: 'json-loader'
      },
      {
        test: /\.(mp4|webm)$/,
        use: {
          loader: 'url-loader',
          options: {
            limit: 10000
          }
        }
      }
    ]
  },
  plugins: options.plugins.concat([
    new webpack.ProvidePlugin({
      // make fetch available
      fetch: 'exports-loader?self.fetch!whatwg-fetch'
    }),

    // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV`
    // inside your code for any environment checks; UglifyJS will automatically
    // drop any unreachable code.
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV)
      }
    })
  ]),
  resolve: {
    modules: ['app', 'node_modules'],
    extensions: ['.js', '.jsx', '.scss', '.react.js'],
    mainFields: ['browser', 'jsnext:main', 'main']
  },
  devtool: options.devtool,
  target: 'web', // Make web variables accessible to webpack, e.g. window
  performance: options.performance || {},
  optimization: {
    namedModules: true,
    splitChunks: {
      name: 'vendor',
      minChunks: 2
    }
  }
})


================================================
FILE: client/config/webpack.dev.babel.js
================================================
/**
 * DEVELOPMENT WEBPACK CONFIGURATION
 */

const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CircularDependencyPlugin = require('circular-dependency-plugin')

module.exports = require('./webpack.base.babel')({
  mode: 'development',
  // Add hot reloading in development
  entry: [
    'eventsource-polyfill', // Necessary for hot reloading with IE
    'webpack-hot-middleware/client?reload=true',
    path.join(process.cwd(), 'app/app.js') // Start with js/app.js
  ],

  // Don't use hashes in dev mode for better performance
  output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js'
  },

  // Add development plugins
  plugins: [
    new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
    new HtmlWebpackPlugin({
      inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
      template: 'app/index.html'
    }),
    new CircularDependencyPlugin({
      exclude: /a\.js|node_modules/, // exclude node_modules
      failOnError: false // show a warning when there is a circular dependency
    })
  ],

  // Emit a source map for easier debugging
  // See https://webpack.js.org/configuration/devtool/#devtool
  devtool: 'eval-source-map',

  performance: {
    hints: false
  }
})


================================================
FILE: client/config/webpack.prod.babel.js
================================================
// Important modules this config uses
const path = require('path')
// const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = require('./webpack.base.babel')({
  mode: 'production',
  // In production, we skip all hot-reloading stuff
  entry: [path.join(process.cwd(), 'app/app.js')],

  // Utilize long-term caching by adding content hashes (not compilation hashes) to compiled assets
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].chunk.js',
    publicPath: '/'
  },

  plugins: [
    // Minify and optimize the index.html
    new HtmlWebpackPlugin({
      template: 'app/index.html',
      minify: {
        removeComments: true,
        collapseWhitespace: true,
        removeRedundantAttributes: true,
        useShortDoctype: true,
        removeEmptyAttributes: true,
        removeStyleLinkTypeAttributes: true,
        keepClosingSlash: true,
        minifyJS: true,
        minifyCSS: true,
        minifyURLs: true
      },
      inject: true
    })
  ],

  performance: {
    assetFilter: assetFilename =>
      !/(\.map$)|(^(main\.|favicon\.))/.test(assetFilename)
  }
})


================================================
FILE: client/jest.config.js
================================================
module.exports = require('./config/jest.config')


================================================
FILE: client/netlify.toml
================================================
[build]
  publish = "build"
  command = "yarn run build:prod"

[build.environment]
  NODE_VERSION = "10.16.3"
  YARN_VERION = "1.17.3"

# The following redirect is intended for use with most SPAs that handle
# routing internally.
[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[headers]]
  # Define which paths this specific [[headers]] block will cover.
  for = "/*"
    [headers.values]
    Access-Control-Allow-Origin = "*"

================================================
FILE: client/package.json
================================================
{
  "name": "poker-client",
  "version": "0.1.0",
  "description": "Poker Client",
  "repository": {
    "type": "git",
    "url": "https://github.com/therewillbecode/haskell-poker"
  },
  "engines": {
    "npm": ">=3",
    "node": ">=5"
  },
  "author": "therewillbecode",
  "license": "Unlicense",
  "scripts": {
    "prebuild": "npm run build:clean",
    "build:prod": "NODE_ENV=production node_modules/.bin/webpack --config config/webpack.prod.babel.js --color -p --progress --hide-modules --display-optimization-bailout",
    "build:clean": "rimraf ./build",
    "start": "cross-env NODE_ENV=development node server/index.js",
    "start:docker": "cross-env NODE_ENV=docker node server/index.js",
    "start:production": "npm run test && npm run build && npm run start:prod",
    "start:prod": "cross-env NODE_ENV=production node server/index.js",
    "deploy": "npm run build:clean && yarn run copy-static && yarn run build && aws s3 sync build/ s3://poker-client --delete",
    "clean": "npm run test:clean && npm run build:clean",
    "lint": "npm run lint:eslint",
    "lint:eslint": "eslint .",
    "eslint:fix": "eslint --fix .",
    "prettier:fix": "node ./node_modules/prettier/bin-prettier.js --write app /**/*.js",
    "test:clean": "rimraf ./coverage",
    "test": "cross-env NODE_ENV=test jest --coverage",
    "test:watch": "cross-env NODE_ENV=test jest --watchAll"
  },
  "dependencies": {
    "axios": "^0.19.0",
    "babel-polyfill": "6.26.0",
    "chalk": "^2.3.2",
    "fontfaceobserver": "2.0.13",
    "history": "4.7.2",
    "hoist-non-react-statics": "3.0.1",
    "immutable": "3.8.2",
    "invariant": "2.2.4",
    "ip": "1.1.5",
    "modularscale-sass": "^3.0.5",
    "prop-types": "15.6.2",
    "react": "^16.11.0",
    "react-dom": "^16.11.0",
    "react-helmet": "5.2.0",
    "react-loadable": "^5.4.0",
    "react-redux": "5.0.7",
    "react-router-dom": "^4.3.1",
    "react-router-redux": "5.0.0-alpha.6",
    "reconnecting-websocket": "^4.2.0",
    "redux": "4.0.0",
    "redux-immutable": "4.0.0",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.3.0",
    "reselect": "3.0.1",
    "sanitize.css": "11.0.0",
    "warning": "^4.0.1",
    "whatwg-fetch": "2.0.4"
  },
  "devDependencies": {
    "add-asset-html-webpack-plugin": "2.1.3",
    "babel-cli": "6.26.0",
    "babel-core": "^6.26.3",
    "babel-eslint": "8.2.6",
    "babel-loader": "7.1.5",
    "babel-plugin-dynamic-import-node": "2.0.0",
    "babel-plugin-react-transform": "3.0.0",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
    "babel-plugin-transform-react-constant-elements": "6.23.0",
    "babel-plugin-transform-react-inline-elements": "6.22.0",
    "babel-plugin-transform-react-remove-prop-types": "0.4.14",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "6.24.1",
    "babel-preset-stage-0": "6.24.1",
    "circular-dependency-plugin": "5.0.2",
    "compression": "1.7.4",
    "cross-env": "6.0.3",
    "css-loader": "1.0.0",
    "enzyme": "^3.3.0",
    "enzyme-adapter-react-16": "^1.1.1",
    "eslint": "5.3.0",
    "eslint-config-airbnb": "17.0.0",
    "eslint-config-airbnb-base": "13.0.0",
    "eslint-config-prettier": "^2.9.0",
    "eslint-import-resolver-webpack": "^0.10.0",
    "eslint-plugin-import": "^2.12.0",
    "eslint-plugin-jsx-a11y": "6.1.1",
    "eslint-plugin-prettier": "^2.6.2",
    "eslint-plugin-react": "^7.9.1",
    "eventsource-polyfill": "0.9.6",
    "exports-loader": "0.7.0",
    "express": "4.17.1",
    "file-loader": "1.1.11",
    "html-loader": "0.5.5",
    "html-webpack-plugin": "3.2.0",
    "image-webpack-loader": "^4.3.1",
    "imports-loader": "0.8.0",
    "jest-cli": "^23.1.0",
    "lint-staged": "^7.1.3",
    "moxios": "^0.4.0",
    "node-plop": "^0.16.0",
    "node-sass": "^4.13.0",
    "null-loader": "0.1.1",
    "plop": "2.1.0",
    "prettier": "^1.14.2",
    "react-test-renderer": "^16.4.0",
    "redux-mock-store": "^1.5.3",
    "rimraf": "2.6.2",
    "sass-loader": "^7.0.1",
    "shelljs": "^0.8.1",
    "style-loader": "^0.22.1",
    "url-loader": "1.0.1",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.0.3",
    "webpack-dev-middleware": "^3.3.3",
    "webpack-dev-server": "^3.9.0",
    "webpack-hot-middleware": "^2.3.2"
  }
}


================================================
FILE: client/server/index.js
================================================
/* eslint consistent-return:0 */

const express = require('express')
const { resolve } = require('path')
const logger = require('./util//logger')

const argv = require('./util/argv')
const port = require('./util//port')
const setup = require('./middlewares/frontendMiddleware')

const app = express()

// If you need a backend, e.g. an API, add your custom backend-specific middleware here
// app.use('/api', myApi);

// In production we need to pass these values in instead of relying on webpack
setup(app, {
  outputPath: resolve(process.cwd(), 'build'),
  publicPath: '/'
})

// get the intended host and port number, use localhost and port 3000 if not provided
const customHost = argv.host || process.env.HOST
const host = customHost || null // Let http.Server use its default IPv6/4 host
const prettyHost = customHost || 'localhost'

// Start your app.
app.listen(port, host, err => {
  if (err) {
    return logger.error(err.message)
  }
  logger.appStarted(port, prettyHost)
})


================================================
FILE: client/server/middlewares/addDevMiddlewares.js
================================================
const path = require('path')
const webpack = require('webpack')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpackHotMiddleware = require('webpack-hot-middleware')

function createWebpackMiddleware(compiler, publicPath) {
  return webpackDevMiddleware(compiler, {
    noInfo: true,
    publicPath,
    silent: true,
    stats: 'errors-only'
  })
}

module.exports = function addDevMiddlewares(app, webpackConfig) {
  const compiler = webpack(webpackConfig)
  const middleware = createWebpackMiddleware(
    compiler,
    webpackConfig.output.publicPath
  )

  app.use(middleware)
  app.use(webpackHotMiddleware(compiler))

  // Since webpackDevMiddleware uses memory-fs internally to store build
  // artifacts, we use it instead
  const fs = middleware.fileSystem

  app.get('*', (req, res) => {
    fs.readFile(path.join(compiler.outputPath, 'index.html'), (err, file) => {
      if (err) {
        res.sendStatus(404)
      } else {
        res.send(file.toString())
      }
    })
  })
}


================================================
FILE: client/server/middlewares/addProdMiddlewares.js
================================================
const path = require('path')
const express = require('express')
const compression = require('compression')

module.exports = function addProdMiddlewares(app, options) {
  const publicPath = options.publicPath || '/'
  const outputPath = options.outputPath || path.resolve(process.cwd(), 'build')

  // compression middleware compresses your server responses which makes them
  // smaller (applies also to assets). You can read more about that technique
  // and other good practices on official Express.js docs http://mxs.is/googmy
  app.use(compression())
  app.use(publicPath, express.static(outputPath))

  app.get('*', (req, res) =>
    res.sendFile(path.resolve(outputPath, 'index.html'))
  )
}


================================================
FILE: client/server/middlewares/frontendMiddleware.js
================================================
/* eslint-disable global-require */

/**
 * Front-end middleware
 */
module.exports = (app, options) => {
  const isProd = process.env.NODE_ENV === 'production'

  if (isProd) {
    const addProdMiddlewares = require('./addProdMiddlewares')
    addProdMiddlewares(app, options)
  } else {
    const webpackConfig = require('../../config/webpack.dev.babel')
    const addDevMiddlewares = require('./addDevMiddlewares')
    addDevMiddlewares(app, webpackConfig)
  }

  return app
}


================================================
FILE: client/server/util/argv.js
================================================
module.exports = require('minimist')(process.argv.slice(2))


================================================
FILE: client/server/util/logger.js
================================================
/* eslint-disable no-console */

const chalk = require('chalk')
const ip = require('ip')

const divider = chalk.gray('\n-----------------------------------')

/**
 * Logger middleware, you can customize it to make messages more personal
 */
const logger = {
  // Called whenever there's an error on the server we want to print
  error: err => {
    console.error(chalk.red(err))
  },

  // Called when express.js app starts on given port w/o errors
  appStarted: (port, host) => {
    console.log(`Server started ! ${chalk.green('✓')}`)

    console.log(`
${chalk.bold('Access URLs:')}${divider}
Localhost: ${chalk.magenta(`http://${host}:${port}`)}
      LAN: ${chalk.magenta(`http://${ip.address()}:${port}`)}${divider}
${chalk.blue(`Press ${chalk.italic('CTRL-C')} to stop`)}
    `)
  }
}

module.exports = logger


================================================
FILE: client/server/util/port.js
================================================
const argv = require('./argv')

module.exports = parseInt(argv.port || process.env.PORT || '3000', 10)


================================================
FILE: client/shell.nix
================================================
# so we can access the `pkgs` and `stdenv` variables
with import <nixpkgs> {};

# Make a new "derivation" that represents our shell
stdenv.mkDerivation {
  name = "my-environment";

  # The packages in the `buildInputs` list will be added to the PATH in our shell
  buildInputs = [
    pkgs.nodejs-10_x
    pkgs.yarn
    pkgs.ocaml
    pkgs.pngquant
    pkgs.libpng12
    pkgs.python
    pkgs.autoreconfHook
  ];
}


================================================
FILE: client/static/fonts/GothamPro/GothamHTF-BookCondensed.otf
================================================






<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
  <link rel="dns-prefetch" href="https://assets-cdn.github.com">
  <link rel="dns-prefetch" href="https://avatars0.githubusercontent.com">
  <link rel="dns-prefetch" href="https://avatars1.githubusercontent.com">
  <link rel="dns-prefetch" href="https://avatars2.githubusercontent.com">
  <link rel="dns-prefetch" href="https://avatars3.githubusercontent.com">
  <link rel="dns-prefetch" href="https://github-cloud.s3.amazonaws.com">
  <link rel="dns-prefetch" href="https://user-images.githubusercontent.com/">



  <link crossorigin="anonymous" media="all" integrity="sha512-Z0JAar9+DkI1NjGVdZr3GivARUgJtA0o2eHlTv7Ou2gshR5awWVf8QGsq11Ns9ZxQLEs+G5/SuARmvpOLMzulw==" rel="stylesheet" href="https://assets-cdn.github.com/assets/frameworks-95aff0b550d3fe338b645a4deebdcb1b.css" />
  <link crossorigin="anonymous" media="all" integrity="sha512-h5cEqWTuBT7ANPGSQLt1mH+ozRnf2uZHIo5hzaBUEaFGGVZkq/aXrTxFNXPfCm9ir2ztHtlW4AAMl2IxBKc1pQ==" rel="stylesheet" href="https://assets-cdn.github.com/assets/github-e6bb18b320358b77abe040d2eb46b547.css" />
  
  
  
  

  <meta name="viewport" content="width=device-width">
  
  <title>kelsoswebsite/GothamHTF-BookCondensed.otf at master · NatashaTheRobot/kelsoswebsite</title>
    <meta name="description" content="GitHub is where people build software. More than 28 million people use GitHub to discover, fork, and contribute to over 85 million projects.">
    <link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
  <link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
  <meta property="fb:app_id" content="1401488693436528">

    
    <meta property="og:image" content="https://avatars1.githubusercontent.com/u/1157147?s=400&amp;v=4" /><meta property="og:site_name" content="GitHub" /><meta property="og:type" content="object" /><meta property="og:title" content="NatashaTheRobot/kelsoswebsite" /><meta property="og:url" content="https://github.com/NatashaTheRobot/kelsoswebsite" /><meta property="og:description" content="Contribute to kelsoswebsite development by creating an account on Github." />

  <link rel="assets" href="https://assets-cdn.github.com/">
  <link rel="web-socket" href="wss://live.github.com/_sockets/VjI6MjY4MDM4MjAxOmE3NGY2ZTU4YWEzMjY1ZTM1NTlhYWM0OGZhMzc3MDAyNTg0NDA1MjQxOGMzMDQxMDYwNjNjYTlmNDgxNmUxMGQ=--43060a1274d6c7b3cbe68d0aaacc37494c45f9fd">
  <meta name="pjax-timeout" content="1000">
  <link rel="sudo-modal" href="/sessions/sudo_modal">
  <meta name="request-id" content="CD90:4116:459C922:7E6350F:5B756E0B" data-pjax-transient>


  

  <meta name="selected-link" value="repo_source" data-pjax-transient>

    <meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
  <meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
  <meta name="google-site-verification" content="GXs5KoUUkNCoaAZn7wPN-t01Pywp9M3sEjnt_3_ZWPc">
    <meta name="google-analytics" content="UA-3769691-2">

<meta name="octolytics-host" content="collector.githubapp.com" /><meta name="octolytics-app-id" content="github" /><meta name="octolytics-event-url" content="https://collector.githubapp.com/github-external/browser_event" /><meta name="octolytics-dimension-request_id" content="CD90:4116:459C922:7E6350F:5B756E0B" /><meta name="octolytics-dimension-region_edge" content="iad" /><meta name="octolytics-dimension-region_render" content="iad" /><meta name="octolytics-actor-id" content="13610012" /><meta name="octolytics-actor-login" content="therewillbecode" /><meta name="octolytics-actor-hash" content="095fb9283e4ca607acaf1787e0f0b4528656454602bfea91ea2b04bed13dc891" />
<meta name="analytics-location" content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-pjax-transient="true" />



  <meta class="js-ga-set" name="userId" content="3bf10623e60ddb700ce45c195d27120d" %>

<meta class="js-ga-set" name="dimension1" content="Logged In">


  

      <meta name="hostname" content="github.com">
    <meta name="user-login" content="therewillbecode">

      <meta name="expected-hostname" content="github.com">
    <meta name="js-proxy-site-detection-payload" content="NThkN2Y0YzA0Njg5ZWRlYzkwN2MzMTZlZDFkMDFlNDVlYWI0YTY0ODNmMGY1ZWMzNGVlYmYxY2YyNDQ0MTIzNXx7InJlbW90ZV9hZGRyZXNzIjoiOTUuMTUxLjg0LjEwIiwicmVxdWVzdF9pZCI6IkNEOTA6NDExNjo0NTlDOTIyOjdFNjM1MEY6NUI3NTZFMEIiLCJ0aW1lc3RhbXAiOjE1MzQ0MjI1NDIsImhvc3QiOiJnaXRodWIuY29tIn0=">

    <meta name="enabled-features" content="DASHBOARD_V2_LAYOUT,DASHBOARD_V2_LAYOUT_OPT_IN,EXPLORE_DISCOVER_REPOSITORIES,UNIVERSE_BANNER,FREE_TRIALS,MARKETPLACE_INSIGHTS,MARKETPLACE_DOCKERFILE_CI_CTA,MARKETPLACE_PLAN_RESTRICTION_EDITOR,MARKETPLACE_SEARCH,MARKETPLACE_INSIGHTS_CONVERSION_PERCENTAGES,MARKETPLACE_RETARGETING">

  <meta name="html-safe-nonce" content="b8dfbd9d3a9229a41eca73ceb9de018c127caaac">

  <meta http-equiv="x-pjax-version" content="60e4fa41ffbcbd51f51925bcc7ea1d9f">
  

      <link href="https://github.com/NatashaTheRobot/kelsoswebsite/commits/master.atom" rel="alternate" title="Recent Commits to kelsoswebsite:master" type="application/atom+xml">

  <meta name="go-import" content="github.com/NatashaTheRobot/kelsoswebsite git https://github.com/NatashaTheRobot/kelsoswebsite.git">

  <meta name="octolytics-dimension-user_id" content="1157147" /><meta name="octolytics-dimension-user_login" content="NatashaTheRobot" /><meta name="octolytics-dimension-repository_id" content="6568626" /><meta name="octolytics-dimension-repository_nwo" content="NatashaTheRobot/kelsoswebsite" /><meta name="octolytics-dimension-repository_public" content="true" /><meta name="octolytics-dimension-repository_is_fork" content="false" /><meta name="octolytics-dimension-repository_network_root_id" content="6568626" /><meta name="octolytics-dimension-repository_network_root_nwo" content="NatashaTheRobot/kelsoswebsite" /><meta name="octolytics-dimension-repository_explore_github_marketplace_ci_cta_shown" content="false" />


    <link rel="canonical" href="https://github.com/NatashaTheRobot/kelsoswebsite/blob/master/public/assets/fonts/Gotham/GothamHTF-BookCondensed.otf" data-pjax-transient>


  <meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">

  <meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">

  <link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#000000">
  <link rel="icon" type="image/x-icon" class="js-site-favicon" href="https://assets-cdn.github.com/favicon.ico">

<meta name="theme-color" content="#1e2327">


  <meta name="u2f-support" content="true">

<link rel="manifest" href="/manifest.json" crossOrigin="use-credentials">

  </head>

  <body class="logged-in env-production page-blob">
    

  <div class="position-relative js-header-wrapper ">
    <a href="#start-of-content" tabindex="1" class="p-3 bg-blue text-white show-on-focus js-skip-to-content">Skip to content</a>
    <div id="js-pjax-loader-bar" class="pjax-loader-bar"><div class="progress"></div></div>

    
    
    



        
<header class="Header  f5" role="banner">
  <div class="d-flex flex-justify-between px-3 container-lg">
    <div class="d-flex flex-justify-between ">
      <div class="">
        <a class="header-logo-invertocat" href="https://github.com/" data-hotkey="g d" aria-label="Homepage" data-ga-click="Header, go to dashboard, icon:logo">
  <svg height="32" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="32" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
</a>

      </div>

    </div>

    <div class="HeaderMenu d-flex flex-justify-between flex-auto">
      <div class="d-flex">
            <div class="">
              <div class="header-search scoped-search site-scoped-search js-site-search position-relative js-jump-to"
  role="search combobox"
  aria-owns="jump-to-results"
  aria-label="Search or jump to"
  aria-haspopup="listbox"
  aria-expanded="true"
>
  <div class="position-relative">
    <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="js-site-search-form" data-scope-type="Repository" data-scope-id="6568626" data-scoped-search-url="/NatashaTheRobot/kelsoswebsite/search" data-unscoped-search-url="/search" action="/NatashaTheRobot/kelsoswebsite/search" accept-charset="UTF-8" method="get"><input name="utf8" type="hidden" value="&#x2713;" />
      <label class="form-control header-search-wrapper header-search-wrapper-jump-to position-relative d-flex flex-justify-between flex-items-center js-chromeless-input-container">
        <input type="text"
          class="form-control header-search-input jump-to-field js-jump-to-field js-site-search-focus js-site-search-field is-clearable"
          data-hotkey="s,/"
          name="q"
          value=""
          placeholder="Search or jump to…"
          data-unscoped-placeholder="Search or jump to…"
          data-scoped-placeholder="Search or jump to…"
          autocapitalize="off"
          aria-autocomplete="list"
          aria-controls="jump-to-results"
          data-jump-to-suggestions-path="/_graphql/GetSuggestedNavigationDestinations#csrf-token=fugzxIjE9QzlcLUTJKU+RU4wyHm8mEVK+sRELVe8s/vgsdm1BiGexqQb9gz2Ghu8MUeQ9Ss/+boFOZYNfiSR1w=="
          spellcheck="false"
          autocomplete="off"
          >
          <input type="hidden" class="js-site-search-type-field" name="type" >
            <img src="https://assets-cdn.github.com/images/search-shortcut-hint.svg" alt="" class="mr-2 header-search-key-slash">

            <div class="Box position-absolute overflow-hidden d-none jump-to-suggestions js-jump-to-suggestions-container">
              <ul class="d-none js-jump-to-suggestions-template-container">
                <li class="d-flex flex-justify-start flex-items-center p-0 f5 navigation-item js-navigation-item">
                  <a tabindex="-1" class="no-underline d-flex flex-auto flex-items-center p-2 jump-to-suggestions-path js-jump-to-suggestion-path js-navigation-open" href="">
                    <div class="jump-to-octicon js-jump-to-octicon mr-2 text-center d-none"></div>
                    <img class="avatar mr-2 flex-shrink-0 js-jump-to-suggestion-avatar" alt="" aria-label="Team" src="" width="28" height="28">

                    <div class="jump-to-suggestion-name js-jump-to-suggestion-name flex-auto overflow-hidden text-left no-wrap css-truncate css-truncate-target">
                    </div>

                    <div class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none js-jump-to-badge-search">
                      <span class="js-jump-to-badge-search-text-default d-none" aria-label="in this repository">
                        In this repository
                      </span>
                      <span class="js-jump-to-badge-search-text-global d-none" aria-label="in all of GitHub">
                        All GitHub
                      </span>
                      <span aria-hidden="true" class="d-inline-block ml-1 v-align-middle">↵</span>
                    </div>

                    <div aria-hidden="true" class="border rounded-1 flex-shrink-0 bg-gray px-1 text-gray-light ml-1 f6 d-none d-on-nav-focus js-jump-to-badge-jump">
                      Jump to
                      <span class="d-inline-block ml-1 v-align-middle">↵</span>
                    </div>
                  </a>
                </li>
                <svg height="16" width="16" class="octicon octicon-repo flex-shrink-0 js-jump-to-repo-octicon-template" title="Repository" aria-label="Repository" viewBox="0 0 12 16" version="1.1" role="img"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
                <svg height="16" width="16" class="octicon octicon-project flex-shrink-0 js-jump-to-project-octicon-template" title="Project" aria-label="Project" viewBox="0 0 15 16" version="1.1" role="img"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
                <svg height="16" width="16" class="octicon octicon-search flex-shrink-0 js-jump-to-search-octicon-template" title="Search" aria-label="Search" viewBox="0 0 16 16" version="1.1" role="img"><path fill-rule="evenodd" d="M15.7 13.3l-3.81-3.83A5.93 5.93 0 0 0 13 6c0-3.31-2.69-6-6-6S1 2.69 1 6s2.69 6 6 6c1.3 0 2.48-.41 3.47-1.11l3.83 3.81c.19.2.45.3.7.3.25 0 .52-.09.7-.3a.996.996 0 0 0 0-1.41v.01zM7 10.7c-2.59 0-4.7-2.11-4.7-4.7 0-2.59 2.11-4.7 4.7-4.7 2.59 0 4.7 2.11 4.7 4.7 0 2.59-2.11 4.7-4.7 4.7z"/></svg>
              </ul>
              <ul class="d-none js-jump-to-no-results-template-container">
                <li class="d-flex flex-justify-center flex-items-center p-3 f5 d-none">
                  <span class="text-gray">No suggested jump to results</span>
                </li>
              </ul>

              <ul id="jump-to-results" class="js-navigation-container jump-to-suggestions-results-container js-jump-to-suggestions-results-container" >
                <li class="d-flex flex-justify-center flex-items-center p-0 f5">
                  <img src="https://assets-cdn.github.com/images/spinners/octocat-spinner-128.gif" alt="Octocat Spinner Icon" class="m-2" width="28">
                </li>
              </ul>
            </div>
      </label>
</form>  </div>
</div>

            </div>

          <ul class="d-flex pl-2 flex-items-center text-bold list-style-none" role="navigation">
            <li>
              <a class="js-selected-navigation-item HeaderNavlink px-2" data-hotkey="g p" data-ga-click="Header, click, Nav menu - item:pulls context:user" aria-label="Pull requests you created" data-selected-links="/pulls /pulls/assigned /pulls/mentioned /pulls" href="/pulls">
                Pull requests
</a>            </li>
            <li>
              <a class="js-selected-navigation-item HeaderNavlink px-2" data-hotkey="g i" data-ga-click="Header, click, Nav menu - item:issues context:user" aria-label="Issues you created" data-selected-links="/issues /issues/assigned /issues/mentioned /issues" href="/issues">
                Issues
</a>            </li>
              <li>
                <a class="js-selected-navigation-item HeaderNavlink px-2" data-ga-click="Header, click, Nav menu - item:marketplace context:user" data-octo-click="marketplace_click" data-octo-dimensions="location:nav_bar" data-selected-links=" /marketplace" href="/marketplace">
                   Marketplace
</a>              </li>
            <li>
              <a class="js-selected-navigation-item HeaderNavlink px-2" data-ga-click="Header, click, Nav menu - item:explore" data-selected-links="/explore /trending /trending/developers /integrations /integrations/feature/code /integrations/feature/collaborate /integrations/feature/ship showcases showcases_search showcases_landing /explore" href="/explore">
                Explore
</a>            </li>
          </ul>
      </div>

      <div class="d-flex">
        
<ul class="user-nav d-flex flex-items-center list-style-none" id="user-links">
  <li class="dropdown">
    <span class="d-inline-block  px-2">
      
    <a aria-label="You have unread notifications" class="notification-indicator tooltipped tooltipped-s  js-socket-channel js-notification-indicator" data-hotkey="g n" data-ga-click="Header, go to notifications, icon:unread" data-channel="notification-changed:13610012" href="/notifications">
        <span class="mail-status unread"></span>
        <svg class="octicon octicon-bell" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M13.99 11.991v1H0v-1l.73-.58c.769-.769.809-2.547 1.189-4.416.77-3.767 4.077-4.996 4.077-4.996 0-.55.45-1 .999-1 .55 0 1 .45 1 1 0 0 3.387 1.229 4.156 4.996.38 1.879.42 3.657 1.19 4.417l.659.58h-.01zM6.995 15.99c1.11 0 1.999-.89 1.999-1.999H4.996c0 1.11.89 1.999 1.999 1.999z"/></svg>
</a>
    </span>
  </li>

  <li class="dropdown">
    <details class="details-overlay details-reset d-flex px-2 flex-items-center">
      <summary class="HeaderNavlink"
         aria-label="Create new…"
         data-ga-click="Header, create new, icon:add">
        <svg class="octicon octicon-plus float-left mr-1 mt-1" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M12 9H7v5H5V9H0V7h5V2h2v5h5v2z"/></svg>
        <span class="dropdown-caret mt-1"></span>
      </summary>
      <details-menu class="dropdown-menu dropdown-menu-sw">
        
<a role="menuitem" class="dropdown-item" href="/new" data-ga-click="Header, create new repository">
  New repository
</a>

  <a role="menuitem" class="dropdown-item" href="/new/import" data-ga-click="Header, import a repository">
    Import repository
  </a>

<a role="menuitem" class="dropdown-item" href="https://gist.github.com/" data-ga-click="Header, create new gist">
  New gist
</a>

  <a role="menuitem" class="dropdown-item" href="/organizations/new" data-ga-click="Header, create new organization">
    New organization
  </a>


  <div class="dropdown-divider"></div>
  <div class="dropdown-header">
    <span title="NatashaTheRobot/kelsoswebsite">This repository</span>
  </div>
    <a role="menuitem" class="dropdown-item" href="/NatashaTheRobot/kelsoswebsite/issues/new" data-ga-click="Header, create new issue">
      New issue
    </a>

      </details-menu>
    </details>
  </li>

  <li class="dropdown">

    <details class="details-overlay details-reset d-flex pl-2 flex-items-center">
      <summary class="HeaderNavlink name mt-1"
        aria-label="View profile and more"
        data-ga-click="Header, show menu, icon:avatar">
        <img alt="@therewillbecode" class="avatar float-left mr-1" src="https://avatars1.githubusercontent.com/u/13610012?s=40&amp;v=4" height="20" width="20">
        <span class="dropdown-caret"></span>
      </summary>
      <details-menu class="dropdown-menu dropdown-menu-sw">
        <ul>
          <li class="header-nav-current-user css-truncate"><a role="menuitem" class="no-underline user-profile-link px-3 pt-2 pb-2 mb-n2 mt-n1 d-block" href="/therewillbecode" data-ga-click="Header, go to profile, text:Signed in as">Signed in as <strong class="css-truncate-target">therewillbecode</strong></a></li>
          <li class="dropdown-divider"></li>
          <li><a role="menuitem" class="dropdown-item" href="/therewillbecode" data-ga-click="Header, go to profile, text:your profile">Your profile</a></li>
          <li><a role="menuitem" class="dropdown-item" href="/therewillbecode?tab=repositories" data-ga-click="Header, go to repositories, text:your repositories">Your repositories</a></li>
          <li><a role="menuitem" class="dropdown-item" href="/therewillbecode?tab=stars" data-ga-click="Header, go to starred repos, text:your stars">Your stars</a></li>
            <li><a role="menuitem" class="dropdown-item" href="https://gist.github.com/" data-ga-click="Header, your gists, text:your gists">Your gists</a></li>
          <li class="dropdown-divider"></li>
          <li><a role="menuitem" class="dropdown-item" href="https://help.github.com" data-ga-click="Header, go to help, text:help">Help</a></li>
          <li><a role="menuitem" class="dropdown-item" href="/settings/profile" data-ga-click="Header, go to settings, icon:settings">Settings</a></li>
          <li>
            <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="logout-form" action="/logout" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="CFVUUYt0GAnKF5OwDXaRogwFDcI2aBhtTdF2AqoEa0W3T6AfJVMIX383BbZ08oZCAzTcR+Au9tEN2WrsYuyqJA==" />
              <button type="submit" class="dropdown-item dropdown-signout" data-ga-click="Header, sign out, icon:logout" role="menuitem">
                Sign out
              </button>
</form>          </li>
        </ul>
      </details-menu>
    </details>
  </li>
</ul>



        <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="sr-only right-0" action="/logout" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="qtODRutXu2nmdW8B3UbDc+na00lv/kGuetgYbk2NMRsVyXcIRXCrP1NV+QekwtST5usCzLm4rxI60ASAhWXweg==" />
          <button type="submit" class="dropdown-item dropdown-signout" data-ga-click="Header, sign out, icon:logout">
            Sign out
          </button>
</form>      </div>
    </div>
  </div>
</header>

      

  </div>

  <div id="start-of-content" class="show-on-focus"></div>

    <div id="js-flash-container">


</div>



  <div role="main" class="application-main ">
        <div itemscope itemtype="http://schema.org/SoftwareSourceCode" class="">
    <div id="js-repo-pjax-container" data-pjax-container >
      







  <div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav  ">
    <div class="repohead-details-container clearfix container">

      <ul class="pagehead-actions">
  <li>
        <!-- '"` --><!-- </textarea></xmp> --></option></form><form data-autosubmit="true" data-remote="true" class="js-social-container" action="/notifications/subscribe" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="CJ/EGIn9sp48FPPhcMLlkMo8psdTqWcAQfVqi5nkoHBxiAiDTtmfk705KhTAiCYsr8/kR3yiF6WugeFsDpNwQA==" />      <input type="hidden" name="repository_id" id="repository_id" value="6568626" class="form-control" />

        <div class="select-menu js-menu-container js-select-menu">
          <a href="/NatashaTheRobot/kelsoswebsite/subscription"
            class="btn btn-sm btn-with-count select-menu-button js-menu-target"
            role="button"
            aria-haspopup="true"
            aria-expanded="false"
            aria-label="Toggle repository notifications menu"
            data-ga-click="Repository, click Watch settings, action:blob#show">
            <span class="js-select-button">
                <svg class="octicon octicon-eye v-align-text-bottom" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"/></svg>
                Watch
            </span>
          </a>
          <a class="social-count js-social-count"
            href="/NatashaTheRobot/kelsoswebsite/watchers"
            aria-label="3 users are watching this repository">
            3
          </a>

        <div class="select-menu-modal-holder">
          <div class="select-menu-modal subscription-menu-modal js-menu-content">
            <div class="select-menu-header js-navigation-enable" tabindex="-1">
              <svg class="octicon octicon-x js-menu-close" role="img" aria-label="Close" viewBox="0 0 12 16" version="1.1" width="12" height="16"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
              <span class="select-menu-title">Notifications</span>
            </div>

              <div class="select-menu-list js-navigation-container" role="menu">

                <div class="select-menu-item js-navigation-item selected" role="menuitem" tabindex="0">
                  <svg class="octicon octicon-check select-menu-item-icon" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5L12 5z"/></svg>
                  <div class="select-menu-item-text">
                    <input type="radio" name="do" id="do_included" value="included" checked="checked" />
                    <span class="select-menu-item-heading">Not watching</span>
                    <span class="description">Be notified when participating or @mentioned.</span>
                    <span class="js-select-button-text hidden-select-button-text">
                      <svg class="octicon octicon-eye v-align-text-bottom" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"/></svg>
                      Watch
                    </span>
                  </div>
                </div>

                <div class="select-menu-item js-navigation-item " role="menuitem" tabindex="0">
                  <svg class="octicon octicon-check select-menu-item-icon" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5L12 5z"/></svg>
                  <div class="select-menu-item-text">
                    <input type="radio" name="do" id="do_subscribed" value="subscribed" />
                    <span class="select-menu-item-heading">Watching</span>
                    <span class="description">Be notified of all conversations.</span>
                    <span class="js-select-button-text hidden-select-button-text">
                      <svg class="octicon octicon-eye v-align-text-bottom" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6C13 14 16 8 16 8s-3-6-7.94-6zM8 12c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4zm2-4c0 1.11-.89 2-2 2-1.11 0-2-.89-2-2 0-1.11.89-2 2-2 1.11 0 2 .89 2 2z"/></svg>
                        Unwatch
                    </span>
                  </div>
                </div>

                <div class="select-menu-item js-navigation-item " role="menuitem" tabindex="0">
                  <svg class="octicon octicon-check select-menu-item-icon" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5L12 5z"/></svg>
                  <div class="select-menu-item-text">
                    <input type="radio" name="do" id="do_ignore" value="ignore" />
                    <span class="select-menu-item-heading">Ignoring</span>
                    <span class="description">Never be notified.</span>
                    <span class="js-select-button-text hidden-select-button-text">
                      <svg class="octicon octicon-mute v-align-text-bottom" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8 2.81v10.38c0 .67-.81 1-1.28.53L3 10H1c-.55 0-1-.45-1-1V7c0-.55.45-1 1-1h2l3.72-3.72C7.19 1.81 8 2.14 8 2.81zm7.53 3.22l-1.06-1.06-1.97 1.97-1.97-1.97-1.06 1.06L11.44 8 9.47 9.97l1.06 1.06 1.97-1.97 1.97 1.97 1.06-1.06L13.56 8l1.97-1.97z"/></svg>
                        Stop ignoring
                    </span>
                  </div>
                </div>

              </div>

            </div>
          </div>
        </div>
</form>
  </li>

  <li>
    
  <div class="js-toggler-container js-social-container starring-container ">
    <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="starred js-social-form" action="/NatashaTheRobot/kelsoswebsite/unstar" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="Oxdz6HsLejdHSJhDV8MT4nm22+Aaxp8fudKzQHGgvWFQ5XijIhKCcGoArCBK3quTXlcegVB2NtsSU5Ji8Bci9Q==" />
      <input type="hidden" name="context" value="repository"></input>
      <button
        type="submit"
        class="btn btn-sm btn-with-count js-toggler-target"
        aria-label="Unstar this repository" title="Unstar NatashaTheRobot/kelsoswebsite"
        data-ga-click="Repository, click unstar button, action:blob#show; text:Unstar">
        <svg class="octicon octicon-star v-align-text-bottom" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74L14 6z"/></svg>
        Unstar
      </button>
        <a class="social-count js-social-count" href="/NatashaTheRobot/kelsoswebsite/stargazers"
           aria-label="9 users starred this repository">
          9
        </a>
</form>
    <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="unstarred js-social-form" action="/NatashaTheRobot/kelsoswebsite/star" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="JL5pfIpQY7pD010QdC7ubZDKPro3M648omMCnC51CsOQbqzGSt1h/T3+Yh5+XMPcq8PTFqhCGu0OO9kyy3TUvQ==" />
      <input type="hidden" name="context" value="repository"></input>
      <button
        type="submit"
        class="btn btn-sm btn-with-count js-toggler-target"
        aria-label="Star this repository" title="Star NatashaTheRobot/kelsoswebsite"
        data-ga-click="Repository, click star button, action:blob#show; text:Star">
        <svg class="octicon octicon-star v-align-text-bottom" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M14 6l-4.9-.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14 7 11.67 11.33 14l-.93-4.74L14 6z"/></svg>
        Star
      </button>
        <a class="social-count js-social-count" href="/NatashaTheRobot/kelsoswebsite/stargazers"
           aria-label="9 users starred this repository">
          9
        </a>
</form>  </div>

  </li>

  <li>
          <details class="details-reset details-overlay details-overlay-dark d-inline-block float-left"
            data-deferred-details-content-url="/NatashaTheRobot/kelsoswebsite/fork?fragment=1">
            <summary class="btn btn-sm btn-with-count"
              title="Fork your own copy of NatashaTheRobot/kelsoswebsite to your account"
              data-ga-click="Repository, show fork modal, action:blob#show; text:Fork">
              <svg class="octicon octicon-repo-forked v-align-text-bottom" viewBox="0 0 10 16" version="1.1" width="10" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8 1a1.993 1.993 0 0 0-1 3.72V6L5 8 3 6V4.72A1.993 1.993 0 0 0 2 1a1.993 1.993 0 0 0-1 3.72V6.5l3 3v1.78A1.993 1.993 0 0 0 5 15a1.993 1.993 0 0 0 1-3.72V9.5l3-3V4.72A1.993 1.993 0 0 0 8 1zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3 10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zm3-10c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
              Fork
            </summary>
            <details-dialog class="anim-fade-in fast Box Box--overlay d-flex flex-column">
              <div class="Box-header">
                <button class="Box-btn-octicon btn-octicon float-right" type="button" aria-label="Close dialog" data-close-dialog>
                  <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
                </button>
                <h3 class="Box-title">Where should we fork this repository?</h3>
              </div>
              <div class="Box-body overflow-auto text-center">
                <include-fragment>
                  <div class="octocat-spinner my-3" aria-label="Loading..."></div>
                  <p class="f5 text-gray">If this dialog fails to load, you can visit <a href="/NatashaTheRobot/kelsoswebsite/fork">the fork page</a> directly.</p>
                </include-fragment>
              </div>
            </details-dialog>
          </details>

    <a href="/NatashaTheRobot/kelsoswebsite/network/members" class="social-count"
       aria-label="51 users forked this repository">
      51
    </a>
  </li>
</ul>

      <h1 class="public ">
  <svg class="octicon octicon-repo" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"/></svg>
  <span class="author" itemprop="author"><a class="url fn" rel="author" href="/NatashaTheRobot">NatashaTheRobot</a></span><!--
--><span class="path-divider">/</span><!--
--><strong itemprop="name"><a data-pjax="#js-repo-pjax-container" href="/NatashaTheRobot/kelsoswebsite">kelsoswebsite</a></strong>

</h1>

    </div>
    
<nav class="reponav js-repo-nav js-sidenav-container-pjax container"
     itemscope
     itemtype="http://schema.org/BreadcrumbList"
     role="navigation"
     data-pjax="#js-repo-pjax-container">

  <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
    <a class="js-selected-navigation-item selected reponav-item" itemprop="url" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches repo_packages /NatashaTheRobot/kelsoswebsite" href="/NatashaTheRobot/kelsoswebsite">
      <svg class="octicon octicon-code" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M9.5 3L8 4.5 11.5 8 8 11.5 9.5 13 14 8 9.5 3zm-5 0L0 8l4.5 5L6 11.5 2.5 8 6 4.5 4.5 3z"/></svg>
      <span itemprop="name">Code</span>
      <meta itemprop="position" content="1">
</a>  </span>

    <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
      <a itemprop="url" data-hotkey="g i" class="js-selected-navigation-item reponav-item" data-selected-links="repo_issues repo_labels repo_milestones /NatashaTheRobot/kelsoswebsite/issues" href="/NatashaTheRobot/kelsoswebsite/issues">
        <svg class="octicon octicon-issue-opened" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"/></svg>
        <span itemprop="name">Issues</span>
        <span class="Counter">1</span>
        <meta itemprop="position" content="2">
</a>    </span>

  <span itemscope itemtype="http://schema.org/ListItem" itemprop="itemListElement">
    <a data-hotkey="g p" itemprop="url" class="js-selected-navigation-item reponav-item" data-selected-links="repo_pulls checks /NatashaTheRobot/kelsoswebsite/pulls" href="/NatashaTheRobot/kelsoswebsite/pulls">
      <svg class="octicon octicon-git-pull-request" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 11.28V5c-.03-.78-.34-1.47-.94-2.06C9.46 2.35 8.78 2.03 8 2H7V0L4 3l3 3V4h1c.27.02.48.11.69.31.21.2.3.42.31.69v6.28A1.993 1.993 0 0 0 10 15a1.993 1.993 0 0 0 1-3.72zm-1 2.92c-.66 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2zM4 3c0-1.11-.89-2-2-2a1.993 1.993 0 0 0-1 3.72v6.56A1.993 1.993 0 0 0 2 15a1.993 1.993 0 0 0 1-3.72V4.72c.59-.34 1-.98 1-1.72zm-.8 10c0 .66-.55 1.2-1.2 1.2-.65 0-1.2-.55-1.2-1.2 0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2zM2 4.2C1.34 4.2.8 3.65.8 3c0-.65.55-1.2 1.2-1.2.65 0 1.2.55 1.2 1.2 0 .65-.55 1.2-1.2 1.2z"/></svg>
      <span itemprop="name">Pull requests</span>
      <span class="Counter">1</span>
      <meta itemprop="position" content="3">
</a>  </span>

    <a data-hotkey="g b" class="js-selected-navigation-item reponav-item" data-selected-links="repo_projects new_repo_project repo_project /NatashaTheRobot/kelsoswebsite/projects" href="/NatashaTheRobot/kelsoswebsite/projects">
      <svg class="octicon octicon-project" viewBox="0 0 15 16" version="1.1" width="15" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M10 12h3V2h-3v10zm-4-2h3V2H6v8zm-4 4h3V2H2v12zm-1 1h13V1H1v14zM14 0H1a1 1 0 0 0-1 1v14a1 1 0 0 0 1 1h13a1 1 0 0 0 1-1V1a1 1 0 0 0-1-1z"/></svg>
      Projects
      <span class="Counter" >0</span>
</a>

    <a class="js-selected-navigation-item reponav-item" data-hotkey="g w" data-selected-links="repo_wiki /NatashaTheRobot/kelsoswebsite/wiki" href="/NatashaTheRobot/kelsoswebsite/wiki">
      <svg class="octicon octicon-book" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M3 5h4v1H3V5zm0 3h4V7H3v1zm0 2h4V9H3v1zm11-5h-4v1h4V5zm0 2h-4v1h4V7zm0 2h-4v1h4V9zm2-6v9c0 .55-.45 1-1 1H9.5l-1 1-1-1H2c-.55 0-1-.45-1-1V3c0-.55.45-1 1-1h5.5l1 1 1-1H15c.55 0 1 .45 1 1zm-8 .5L7.5 3H2v9h6V3.5zm7-.5H9.5l-.5.5V12h6V3z"/></svg>
      Wiki
</a>

  <a class="js-selected-navigation-item reponav-item" data-selected-links="repo_graphs repo_contributors dependency_graph pulse /NatashaTheRobot/kelsoswebsite/pulse" href="/NatashaTheRobot/kelsoswebsite/pulse">
    <svg class="octicon octicon-graph" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M16 14v1H0V0h1v14h15zM5 13H3V8h2v5zm4 0H7V3h2v10zm4 0h-2V6h2v7z"/></svg>
    Insights
</a>

</nav>


  </div>

<div class="container new-discussion-timeline experiment-repo-nav  ">
  <div class="repository-content ">

    
  <a class="d-none js-permalink-shortcut" data-hotkey="y" href="/NatashaTheRobot/kelsoswebsite/blob/4058de68dd765f7dbdcd27227555d2029f47dd26/public/assets/fonts/Gotham/GothamHTF-BookCondensed.otf">Permalink</a>

  <!-- blob contrib key: blob_contributors:v21:253dca872f182ec8e0b273f345400e34 -->

  

  <div class="file-navigation">
    
<div class="select-menu branch-select-menu js-menu-container js-select-menu float-left">
  <button class=" btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w"
    
    type="button" aria-label="Switch branches or tags" aria-expanded="false" aria-haspopup="true">
      <i>Branch:</i>
      <span class="js-select-button css-truncate-target">master</span>
  </button>

  <div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax>

    <div class="select-menu-modal">
      <div class="select-menu-header">
        <svg class="octicon octicon-x js-menu-close" role="img" aria-label="Close" viewBox="0 0 12 16" version="1.1" width="12" height="16"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
        <span class="select-menu-title">Switch branches/tags</span>
      </div>

      <div class="select-menu-filters">
        <div class="select-menu-text-filter">
          <input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="form-control js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
        </div>
        <div class="select-menu-tabs">
          <ul>
            <li class="select-menu-tab">
              <a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a>
            </li>
            <li class="select-menu-tab">
              <a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tag…" class="js-select-menu-tab" role="tab">Tags</a>
            </li>
          </ul>
        </div>
      </div>

      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu">

        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">


            <a class="select-menu-item js-navigation-item js-navigation-open selected"
               href="/NatashaTheRobot/kelsoswebsite/blob/master/public/assets/fonts/Gotham/GothamHTF-BookCondensed.otf"
               data-name="master"
               data-skip-pjax="true"
               rel="nofollow">
              <svg class="octicon octicon-check select-menu-item-icon" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5L12 5z"/></svg>
              <span class="select-menu-item-text css-truncate-target js-select-menu-filter-text">
                master
              </span>
            </a>
        </div>

          <div class="select-menu-no-results">Nothing to show</div>
      </div>

      <div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
        <div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">


        </div>

        <div class="select-menu-no-results">Nothing to show</div>
      </div>

    </div>
  </div>
</div>

    <div class="BtnGroup float-right">
      <a href="/NatashaTheRobot/kelsoswebsite/find/master"
            class="js-pjax-capture-input btn btn-sm BtnGroup-item"
            data-pjax
            data-hotkey="t">
        Find file
      </a>
      <clipboard-copy for="blob-path" class="btn btn-sm BtnGroup-item">
        Copy path
      </clipboard-copy>
    </div>
    <div id="blob-path" class="breadcrumb">
      <span class="repo-root js-repo-root"><span class="js-path-segment"><a data-pjax="true" href="/NatashaTheRobot/kelsoswebsite"><span>kelsoswebsite</span></a></span></span><span class="separator">/</span><span class="js-path-segment"><a data-pjax="true" href="/NatashaTheRobot/kelsoswebsite/tree/master/public"><span>public</span></a></span><span class="separator">/</span><span class="js-path-segment"><a data-pjax="true" href="/NatashaTheRobot/kelsoswebsite/tree/master/public/assets"><span>assets</span></a></span><span class="separator">/</span><span class="js-path-segment"><a data-pjax="true" href="/NatashaTheRobot/kelsoswebsite/tree/master/public/assets/fonts"><span>fonts</span></a></span><span class="separator">/</span><span class="js-path-segment"><a data-pjax="true" href="/NatashaTheRobot/kelsoswebsite/tree/master/public/assets/fonts/Gotham"><span>Gotham</span></a></span><span class="separator">/</span><strong class="final-path">GothamHTF-BookCondensed.otf</strong>
    </div>
  </div>


  
  <div class="commit-tease">
      <span class="float-right">
        <a class="commit-tease-sha" href="/NatashaTheRobot/kelsoswebsite/commit/efc87b11815027969c5030a9c8b1f90232a964f4" data-pjax>
          efc87b1
        </a>
        <relative-time datetime="2012-11-06T20:09:47Z">Nov 6, 2012</relative-time>
      </span>
      <div>
        <a rel="author" data-skip-pjax="true" data-hovercard-user-id="1157147" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="/NatashaTheRobot"><img class="avatar" src="https://avatars0.githubusercontent.com/u/1157147?s=40&amp;v=4" width="20" height="20" alt="@NatashaTheRobot" /></a>
        <a class="user-mention" rel="author" data-hovercard-user-id="1157147" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="/NatashaTheRobot">NatashaTheRobot</a>
          <a data-pjax="true" title="created kelso kennedy&#39;s website" class="message" href="/NatashaTheRobot/kelsoswebsite/commit/efc87b11815027969c5030a9c8b1f90232a964f4">created kelso kennedy's website</a>
      </div>

    <div class="commit-tease-contributors">
      
<details class="details-reset details-overlay details-overlay-dark lh-default text-gray-dark float-left mr-2" id="blob_contributors_box">
  <summary class="btn-link" aria-haspopup="dialog" >
    
    <span><strong>1</strong> contributor</span>
  </summary>
  <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast " aria-label="Users who have contributed to this file">
    <div class="Box-header">
      <button class="Box-btn-octicon btn-octicon float-right" type="button" aria-label="Close dialog" data-close-dialog>
        <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
      </button>
      <h3 class="Box-title">Users who have contributed to this file</h3>
    </div>
    
        <ul class="list-style-none overflow-auto">
            <li class="Box-row">
              <a class="link-gray-dark no-underline" href="/NatashaTheRobot">
                <img class="avatar mr-2" alt="" src="https://avatars0.githubusercontent.com/u/1157147?s=40&amp;v=4" width="20" height="20" />
                NatashaTheRobot
</a>            </li>
        </ul>

  </details-dialog>
</details>
      
    </div>
  </div>



  <div class="file">
    <div class="file-header">
  <div class="file-actions">

    <div class="BtnGroup">
      <a id="raw-url" class="btn btn-sm BtnGroup-item" href="/NatashaTheRobot/kelsoswebsite/raw/master/public/assets/fonts/Gotham/GothamHTF-BookCondensed.otf">Download</a>
      <a rel="nofollow" class="btn btn-sm BtnGroup-item" href="/NatashaTheRobot/kelsoswebsite/commits/master/public/assets/fonts/Gotham/GothamHTF-BookCondensed.otf">History</a>
    </div>


        <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="inline-form" action="/NatashaTheRobot/kelsoswebsite/delete/master/public/assets/fonts/Gotham/GothamHTF-BookCondensed.otf" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="authenticity_token" value="WYtzYccOzqDY22Dj36Cwj859vZfg43Y/WUNuMnDtt8pu2iehnauJQZwxkhgmQkE6beMUe0i9qBTtj6/8AYdgww==" />
          <button class="btn-octicon btn-octicon-danger tooltipped tooltipped-nw" type="submit"
            aria-label="Fork this project and delete the file" data-disable-with>
            <svg class="octicon octicon-trashcan" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z"/></svg>
          </button>
</form>  </div>

  <div class="file-info">
    32.3 KB
  </div>
</div>

    

  <div itemprop="text" class="blob-wrapper data type-text">
      <div class="image">
          <a href="/NatashaTheRobot/kelsoswebsite/blob/master/public/assets/fonts/Gotham/GothamHTF-BookCondensed.otf?raw=true">View Raw</a>
      </div>
  </div>

  </div>

  <details class="details-reset details-overlay details-overlay-dark">
    <summary data-hotkey="l" aria-label="Jump to line"></summary>
    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast linejump" aria-label="Jump to line">
      <!-- '"` --><!-- </textarea></xmp> --></option></form><form class="js-jump-to-line-form Box-body d-flex" action="" accept-charset="UTF-8" method="get"><input name="utf8" type="hidden" value="&#x2713;" />
        <input class="form-control flex-auto mr-3 linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" aria-label="Jump to line" autofocus>
        <button type="submit" class="btn" data-close-dialog>Go</button>
</form>    </details-dialog>
  </details>


  </div>
  <div class="modal-backdrop js-touch-events"></div>
</div>

    </div>
  </div>

  </div>

        
<div class="footer container-lg px-3" role="contentinfo">
  <div class="position-relative d-flex flex-justify-between pt-6 pb-2 mt-6 f6 text-gray border-top border-gray-light ">
    <ul class="list-style-none d-flex flex-wrap ">
      <li class="mr-3">&copy; 2018 <span title="0.27286s from unicorn-6bd9bd9cc-hvp42">GitHub</span>, Inc.</li>
        <li class="mr-3"><a data-ga-click="Footer, go to terms, text:terms" href="https://github.com/site/terms">Terms</a></li>
        <li class="mr-3"><a data-ga-click="Footer, go to privacy, text:privacy" href="https://github.com/site/privacy">Privacy</a></li>
        <li class="mr-3"><a href="https://help.github.com/articles/github-security/" data-ga-click="Footer, go to security, text:security">Security</a></li>
        <li class="mr-3"><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
        <li><a data-ga-click="Footer, go to help, text:help" href="https://help.github.com">Help</a></li>
    </ul>

    <a aria-label="Homepage" title="GitHub" class="footer-octicon" href="https://github.com">
      <svg height="24" class="octicon octicon-mark-github" viewBox="0 0 16 16" version="1.1" width="24" aria-hidden="true"><path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/></svg>
</a>
   <ul class="list-style-none d-flex flex-wrap ">
        <li class="mr-3"><a data-ga-click="Footer, go to contact, text:contact" href="https://github.com/contact">Contact GitHub</a></li>
        <li class="mr-3"><a href="/pricing" data-ga-click="Footer, go to Pricing, text:Pricing">Pricing</a></li>
      <li class="mr-3"><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
      <li class="mr-3"><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
        <li class="mr-3"><a href="https://blog.github.com" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
        <li><a data-ga-click="Footer, go to about, text:about" href="https://github.com/about">About</a></li>

    </ul>
  </div>
  <div class="d-flex flex-justify-center pb-6">
    <span class="f6 text-gray-light"></span>
  </div>
</div>



  <div id="ajax-error-message" class="ajax-error-message flash flash-error">
    <svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/></svg>
    <button type="button" class="flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
      <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
    </button>
    You can’t perform that action at this time.
  </div>


    <script crossorigin="anonymous" integrity="sha512-2VdGgXQE8W5ONZ4OsrbEo/noennUZaqXkevD9R8juTHiCsjT0HFTTF6MoBfySUc8G+eFqUcDgd7v+CAu8Gjxlg==" type="application/javascript" src="https://assets-cdn.github.com/assets/compat-f849c975b0ffaa01d6ca305e48417d08.js"></script>
    <script crossorigin="anonymous" integrity="sha512-Swe7kfA0LYlT3FJ/ke9bPeUkLzJicE/ZxqOlslN1BTzsmekU9LCRmkEhprM8P9jyIT1PPZnHu/8tNM5k6JstFg==" type="application/javascript" src="https://assets-cdn.github.com/assets/frameworks-000e724656d8b68c57faba0b6d80278a.js"></script>
    
    <script crossorigin="anonymous" async="async" integrity="sha512-iK04uvFyMwXxWLbY/eleTk1H7shOe5W3V3LhirjsdmqMRoFuwWN1MHovDavK+Gm5INFyv+LiG32Aeui7DZk+/g==" type="application/javascript" src="https://assets-cdn.github.com/assets/github-ca5c0c6b6a11f4b7ad8aa92f3a2a45d4.js"></script>
    
    
    
  <div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner d-none">
    <svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/></svg>
    <span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
    <span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
  </div>
  <div class="facebox" id="facebox" style="display:none;">
  <div class="facebox-popup">
    <div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description">
    </div>
    <button type="button" class="facebox-close js-facebox-close" aria-label="Close modal">
      <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
    </button>
  </div>
</div>

  <template id="site-details-dialog">
  <details class="details-reset details-overlay details-overlay-dark lh-default text-gray-dark" open>
    <summary aria-haspopup="dialog" aria-label="Close dialog"></summary>
    <details-dialog class="Box Box--overlay d-flex flex-column anim-fade-in fast">
      <button class="m-3 btn-octicon position-absolute right-0 top-0" type="button" aria-label="Close dialog" data-close-dialog>
        <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
      </button>
      <div class="octocat-spinner my-6 js-details-dialog-spinner"></div>
    </details-dialog>
  </details>
</template>

  <div class="Popover js-hovercard-content position-absolute" style="display: none; outline: none;" tabindex="0">
  <div class="Popover-message Popover-message--bottom-left Popover-message--large Box box-shadow-large" style="width:360px;">
  </div>
</div>

<div id="hovercard-aria-description" class="sr-only">
  Press h to open a hovercard with more details.
</div>


  </body>
</html>



================================================
FILE: docker-compose.yml
================================================
version: "3.5"

services:
  db:
    image: postgres:9.4
    environment:
      - DB_USER=postgres
      - DB_PASS=postgres
      - DB_NAME=poker
      - POSTGRES_PASSWORD=postgres
  #  volumes:
  #   - db-data:/var/lib/postgresql/data
    restart: on-failure
    networks:
      - backend
  
  redis:
    image: redis:5.0-rc4-alpine
    networks:
      - backend
    restart: on-failure
    volumes:
      - redis-data:/var/lib/redis
  
  server:
    build: ./server 
    environment:
     - dbConnStr=host=db port=5432 user=postgres dbname=postgres password=postgres
     - secret=aw4-4z0ds21c970dasdak4dm=9jhkbn8da268tkj7=rsfdaf92x88
     - redisHost=redis
    depends_on:
     - db
     - redis
    ports:
     - "8000:8000"
     - "5000:5000"
    restart: on-failure
    networks:
     - backend

  client:
    build: ./client
    restart: on-failure
    environment: 
      - HOST=0.0.0.0
    ports:
      - target: 3000
        published: 3000
        protocol: tcp
        mode: host

networks:
  backend:

volumes:
  db-data:
  redis-data:

================================================
FILE: server/.dev.env
================================================
dbConnStr='port=5432 user=postgres dbname=postgres password=postgres'
port=8000
secret="wwaaifidsa9109f0dasfda-=2-13"



================================================
FILE: server/.dockerignore
================================================
.dockerignore
.gitignore
.stack-work
Dockerfile

================================================
FILE: server/.gitignore
================================================
poker-server.cabal
*~
.env
build
dist
dist-*
cabal-dev
*.o
*.hi
*.chi
*.chs.h
*.dyn_o
*.dyn_hi
.hpc
.hsenv
.cabal-sandbox/
cabal.sandbox.config
*.prof
*.aux
*.hp
*.eventlog
.stack-work/
cabal.project.local
cabal.project.local~
.HTF/
.ghc.environment.*

.prod.env

================================================
FILE: server/.projectile
================================================


================================================
FILE: server/ChangeLog.md
================================================
# Changelog for poker-server

## Unreleased changes


================================================
FILE: server/Dockerfile
================================================
FROM fpco/stack-build-small

RUN mkdir -p /app


COPY . /app

WORKDIR /app

RUN apt-get update && \
  apt-get install libpq-dev lzma-dev libpq-dev -yy

RUN stack build --only-dependencies

RUN stack build 

CMD stack run


================================================
FILE: server/README.md
================================================
# Server


================================================
FILE: server/Setup.hs
================================================
import Distribution.Simple
main = defaultMain


================================================
FILE: server/UNLICENSE.txt
================================================
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org>

================================================
FILE: server/app/Main.hs
================================================
{-# LANGUAGE OverloadedStrings #-}

module Main where

import           Control.Concurrent.Async
import qualified Data.ByteString.Lazy          as BL
import           Data.Text.Encoding            as TSE
import           Database.Redis                 ( defaultConnectInfo )
import           Network.Wai.Handler.Warp
import           Prelude
import qualified System.Remote.Monitoring      as EKG

import qualified Data.ByteString.Lazy.Char8    as C

import           API
import           Database
import           Env
import           Socket

import           Crypto.JWT
import           Data.Proxy
import           Types

main :: IO ((), ())
main = do
  dbConnString  <- getDBConnStrFromEnv
  userAPIPort   <- getAuthAPIPort defaultUserAPIPort
  socketAPIPort <- getSocketAPIPort defaultSocketAPIPort
  redisConfig   <- getRedisHostFromEnv defaultRedisHost
  print "REDIS config: "
  print redisConfig
  secretKey <- getSecretKey
  let runSocketAPI =
        runSocketServer secretKey socketAPIPort dbConnString redisConfig
      app'     = app secretKey dbConnString redisConfig
      settings = setPort userAPIPort (setHost "0.0.0.0" defaultSettings)

  migrateDB dbConnString
  ekg <- runMonitoringServer
  concurrently (runSettings settings app') runSocketAPI
 where
  defaultUserAPIPort             = 8000
  defaultSocketAPIPort           = 5000
  defaultRedisHost               = "localhost"
  defaultMonitoringServerAddress = "localhost"
  defaultMonitoringServerPort    = 9999
  runMonitoringServer =
    EKG.forkServer defaultMonitoringServerAddress defaultMonitoringServerPort



================================================
FILE: server/bootstrap.sh
================================================
# install virtual env and python

# activate python virtual env
virtualenv venv
~ source venv/bin/activate

# install ansible
sudo dnf install ansible

pip install docker-py boto

# install aws-cli

# Give executable permissions to the
# script which dynamically retrieves 
# AWS EC2 instance inventory 
chmod +x ansible/inventory/ec2.py

# Environment variables to the AWS EC2 inventory management script 
#
#
# Our EC2 dynamic inventory script has the file name ec2.py
#
# This variable tells Ansible to use the dynamic 
# EC2 script instead of a static /etc/ansible/hosts file.
export EC2_INI_PATH=./ansible/inventory/ec2.ini
# This variable tells ec2.py where the ec2.ini config file is located.
export ANSIBLE_INVENTORY=./ansible/inventory/ec2.py

================================================
FILE: server/deploy-server.sh
================================================
#!/bin/bash
## First arg to the bashfile must be the path to the key file to SSH into the EC2 host(s)
##
## Build and push new docker image to AWS ECR then pull and run new image in EC2 instance.
## Ensure AWS credentials are set in environment or the ansible playbook will fail to login to AWS

if [ $# -eq 0 ]
  then
    echo "You forgot to speciffy the path to the key file to authenticate the SSH connection"
fi

sudo -H pip install pip==18.0.0 \
    && sudo -H pip uninstall --yes setuptools \
    && sudo -H pip install 'setuptools<20.2' --ignore-installed \
    && sudo -H pip install 'requests[security]' --ignore-installed \
    && sudo -H pip install boto awscli ansible docker-py --ignore-installed \
    && ANSIBLE_CONFIG=ansible/ansible.cfg ansible-playbook ansible/push-new-image.yml ansible/deploy-image.yml --key-file=$1 -vvvv

================================================
FILE: server/deploy.sh
================================================
#!/usr/bin/env bash
set -o errexit
set -o pipefail
set -o nounset

# Location of executable
BUILD_DIR="./build"
BINARY_PATH=$BUILD_DIR"/poker-server-exe"

# Where we deploy
HOST="34.244.29.59"
REMOTE="ubuntu@"$HOST

RUN="sudo /opt/server/server-exe"


serverHealthCheck(){
    local url="https://tenpoker.co.uk/lobby"
    local statusCode=`echo $(curl -s -o /dev/null -w "%{http_code}" $url)`

    if [[ "$statusCode" != 2* ]] && [[ "$statusCode" != 0* ]]; then
        echo "Error: Server responded with http status: $statusCode"  # if the content of statusCode isn't a "2xx" print the error.
    fi

    if [[ "$statusCode" = 0* ]]; then
        echo "Error: Server unreachable: $statusCode" # connection refused
    fi

     if [[ "$statusCode" = 2* ]]; then
        echo "Success: Server responded with http status: $statusCode"
    fi
}

# compile binrary
stack build --copy-bins --local-bin-path $BUILD_DIR


ssh -i ~/.ssh/id_rsa $REMOTE sudo "systemctl stop server.service" 


# copy server binary to remote
scp -i ~/.ssh/id_rsa $BINARY_PATH $REMOTE:/opt/server

# restart server using systemd
ssh -i ~/.ssh/id_rsa $REMOTE sudo "systemctl start server.service"

serverHealthCheck

================================================
FILE: server/docs/lobbyAPI.md
================================================
## POST /gooby

### Request:

- Supported content types are:

    - `application/json;charset=utf-8`
    - `application/json`

- Sample User (`application/json;charset=utf-8`, `application/json`):

    ```javascript
{"email":"gooby@g.com","username":"Tom","chips":2000,"password":"n84!@R5G"}
    ```

### Response:

- Status code 200
- Headers: []

- Supported content types are:

    - `application/json;charset=utf-8`
    - `application/json`

- Sample User (`application/json;charset=utf-8`, `application/json`):

    ```javascript
1
    ```



================================================
FILE: server/docs/socket.md
================================================
{"tag":"subscribeToTable","contents":"Black"}

{"tag":"TakeSeat","contents":["Black",3000]}

{"tag":"GameMove","contents":["Black",{"tag":"PostBlind","contents":"SmallBlind"}]}
{"tag":"GameMove","contents":["Black",{"tag":"PostBlind","contents":"BigBlind"}]}
{"tag":"GameMove","contents":["Black",{"tag":"Call"}]}
{"tag":"GameMove","contents":["Black",{"tag":"Check"}]}
{"tag":"GameMove","contents":["Black",{"tag":"Bet","contents":100}]}
{"tag":"LeaveSeat","contents":"Black"}

================================================
FILE: server/docs/userAPI.md
================================================
## POST /register

### Request:

- Supported content types are:

  - `application/json;charset=utf-8`
  - `application/json`

- Sample Register (`application/json;charset=utf-8`, `application/json`):

      ```javascript

  {
  newUsername: "Argo",
  newEmail: "gooby@goo.com",
  newPassword: "password123"
  }
  ```

### Response:

- Supported content types are:

  - `application/json;charset=utf-8`
  - `application/json`

- Sample ReturnToken (`application/json;charset=utf-8`, `application/json`):

      ```javascript

  {
  "access_token": "eyJhbGciOiJIUzI1NiIs",
  "expiration": 3600,
  "refresh_token": "EwMIjImdgoeswazNQx"
  }

      ```

## POST /login

### Request:

- Supported content types are:

  - `application/json;charset=utf-8`
  - `application/json`

- Sample Login (`application/json;charset=utf-8`, `application/json`):

      ```javascript

  {
    loginUsername: "gooby",
    loginPassword: "password123"
  }
  ```

### Response:

- Supported content types are:

  - `application/json;charset=utf-8`
  - `application/json`

- Sample ReturnToken (`application/json;charset=utf-8`, `application/json`):

      ```javascript

  {
  "access_token": "eyJhbGciOiJIUzI1NiIs",
  "expiration": 3600,
  "refresh_token": "EwMIjImdgoeswazNQx"
  }

      ```

## GET /profile

### Request:

- Supported content types are:

  - `application/json;charset=utf-8`
  - `application/json`

- Headers:
  - Authorization: eyJhbGciOiJIUzI1NiIs

Ensure access token is in Authorization header

### Response:

- Supported content types are:

  - `application/json;charset=utf-8`
  - `application/json`

- Sample ReturnToken (`application/json;charset=utf-8`, `application/json`):

      ```javascript

  {
  "proChips": 3000,
  "proUsername": "Argo",
  "proEmail": "gooby@goo.com"
  }

      ```


================================================
FILE: server/package.yaml
================================================
name: poker-server
version: 0.1.0.0
github: "githubuser/poker-server"
license: Unlicense
author: "therewillbecode"
maintainer: "tomw08@gmail.com"
copyright: "2019 Tom Chambrier"

extra-source-files:
  - README.md
  - ChangeLog.md

description: A Poker Server Built With Haskell

dependencies:
  - base >= 4.12 && < 5
  - adjunctions
  - distributive
  - async
  - aeson
  - bytestring
  - comonad
  - free
  - hedis
  - ekg
  - containers
  - cryptohash
  - hashable
  - jose
  - persistent
  - persistent-postgresql
  - persistent-template
  - time
  - servant
  - servant-server
  - servant-auth
  - servant-auth-server
  - servant-auth-client
  - servant-foreign
  - servant-options
  - servant-websockets
  - pipes
  - pipes-aeson
  - pipes-concurrency
  - pipes-parse
  - transformers
  - random
  - text
  - wai
  - wai-extra
  - wai-logger
  - wai-cors
  - websockets
  - pretty-simple
  - utf8-string
  - split
  - stm
  - MonadRandom
  - monad-logger
  - mtl
  - jwt
  - listsafe
  - warp
  - lens
  - vector

library:
  source-dirs: src
  exposed-modules:
    - API
    - Bots
    - Database
    - Schema
    - Env
    - Types
    - Poker.Poker
    - Poker.Game.Actions
    - Poker.ActionValidation
    - Poker.Types
    - Poker.Game.Blinds
    - Poker.Game.Game
    - Poker.Game.Hands
    - Poker.Game.Utils
    - Poker.Game.Privacy
    - Socket
    - Socket.Table

executables:
  poker-server-exe:
    main: Main.hs
    source-dirs: app
    ghc-options:
      - -threaded
      - -rtsopts
      - -with-rtsopts=-N
    dependencies:
      - poker-server

tests:
  spec:
    main: Spec.hs
    source-dirs: test
    ghc-options:
      - -threaded
      - -rtsopts
      - -with-rtsopts=-N
    dependencies:
      - poker-server
      - hspec
      - hedgehog
      - hspec-hedgehog

===========================
Download .txt
gitextract_6a575bnu/

├── README.md
├── client/
│   ├── .babelrc
│   ├── .dockerignore
│   ├── .editorconfig
│   ├── .eslintignore
│   ├── .eslintrc
│   ├── .gitattributes
│   ├── .gitignore
│   ├── .prettierrc
│   ├── .travis.yml
│   ├── Dockerfile
│   ├── LICENSE.md
│   ├── README.md
│   ├── app/
│   │   ├── actions/
│   │   │   ├── auth.js
│   │   │   ├── games.js
│   │   │   ├── lobby.js
│   │   │   ├── profile.js
│   │   │   ├── socket.js
│   │   │   ├── tests/
│   │   │   │   └── auth.test.js
│   │   │   └── types.js
│   │   ├── app.js
│   │   ├── components/
│   │   │   ├── ActionPanel.js
│   │   │   ├── App.js
│   │   │   ├── Board.js
│   │   │   ├── Card.js
│   │   │   ├── Footer.js
│   │   │   ├── Game.js
│   │   │   ├── Home.js
│   │   │   ├── Lobby.js
│   │   │   ├── NavBar.js
│   │   │   ├── NotFoundPage.js
│   │   │   ├── Profile.js
│   │   │   ├── Seat.js
│   │   │   ├── SignInForm.js
│   │   │   ├── SignUpForm.js
│   │   │   └── Signout.js
│   │   ├── configureStore.js
│   │   ├── containers/
│   │   │   ├── AppContainer.js
│   │   │   ├── GameContainer.js
│   │   │   ├── HomeContainer.js
│   │   │   ├── LobbyContainer.js
│   │   │   ├── NavBarContainer.js
│   │   │   ├── ProfileContainer.js
│   │   │   ├── SignInFormContainer.js
│   │   │   └── SignUpFormContainer.js
│   │   ├── index.html
│   │   ├── middleware/
│   │   │   └── socket.js
│   │   ├── reducers/
│   │   │   ├── auth.js
│   │   │   ├── games.js
│   │   │   ├── lobby.js
│   │   │   ├── profile.js
│   │   │   ├── rootReducer.js
│   │   │   ├── socket.js
│   │   │   └── tests/
│   │   │       └── auth.test.js
│   │   ├── reducers.js
│   │   ├── selectors/
│   │   │   ├── auth.js
│   │   │   ├── games.js
│   │   │   ├── lobby.js
│   │   │   ├── profile.js
│   │   │   ├── route.js
│   │   │   ├── socket.js
│   │   │   └── tests/
│   │   │       └── games.test.js
│   │   ├── styles/
│   │   │   ├── _common.scss
│   │   │   ├── common/
│   │   │   │   ├── _colours.scss
│   │   │   │   ├── _mixins.scss
│   │   │   │   ├── _typography.scss
│   │   │   │   └── _variables.scss
│   │   │   ├── components/
│   │   │   │   ├── _buttons.scss
│   │   │   │   ├── _footer.scss
│   │   │   │   ├── _forms.scss
│   │   │   │   ├── _game.scss
│   │   │   │   ├── _lobby.scss
│   │   │   │   ├── _navbar.scss
│   │   │   │   └── game/
│   │   │   │       ├── _actionPanel.scss
│   │   │   │       ├── _boardCards.scss
│   │   │   │       ├── _cards.scss
│   │   │   │       ├── _seat.scss
│   │   │   │       ├── _slider.scss
│   │   │   │       └── _table.scss
│   │   │   ├── layout/
│   │   │   │   └── _app.scss
│   │   │   └── main.scss
│   │   └── utils/
│   │       └── request.js
│   ├── config/
│   │   ├── jest-mocks/
│   │   │   ├── cssModule.js
│   │   │   └── image.js
│   │   ├── jest.config.js
│   │   ├── test-setup.js
│   │   ├── webpack.base.babel.js
│   │   ├── webpack.dev.babel.js
│   │   └── webpack.prod.babel.js
│   ├── jest.config.js
│   ├── netlify.toml
│   ├── package.json
│   ├── server/
│   │   ├── index.js
│   │   ├── middlewares/
│   │   │   ├── addDevMiddlewares.js
│   │   │   ├── addProdMiddlewares.js
│   │   │   └── frontendMiddleware.js
│   │   └── util/
│   │       ├── argv.js
│   │       ├── logger.js
│   │       └── port.js
│   ├── shell.nix
│   └── static/
│       └── fonts/
│           └── GothamPro/
│               └── GothamHTF-BookCondensed.otf
├── docker-compose.yml
└── server/
    ├── .dev.env
    ├── .dockerignore
    ├── .gitignore
    ├── .projectile
    ├── ChangeLog.md
    ├── Dockerfile
    ├── README.md
    ├── Setup.hs
    ├── UNLICENSE.txt
    ├── app/
    │   └── Main.hs
    ├── bootstrap.sh
    ├── deploy-server.sh
    ├── deploy.sh
    ├── docs/
    │   ├── lobbyAPI.md
    │   ├── socket.md
    │   └── userAPI.md
    ├── package.yaml
    ├── ping.sh
    ├── provision.sh
    ├── server.service
    ├── shell.nix
    ├── src/
    │   ├── API.hs
    │   ├── Bots.hs
    │   ├── Database.hs
    │   ├── Env.hs
    │   ├── Poker/
    │   │   ├── ActionValidation.hs
    │   │   ├── Game/
    │   │   │   ├── Actions.hs
    │   │   │   ├── Blinds.hs
    │   │   │   ├── Game.hs
    │   │   │   ├── Hands.hs
    │   │   │   ├── Privacy.hs
    │   │   │   └── Utils.hs
    │   │   ├── Poker.hs
    │   │   └── Types.hs
    │   ├── Schema.hs
    │   ├── Socket/
    │   │   ├── Auth.hs
    │   │   ├── Clients.hs
    │   │   ├── Lobby.hs
    │   │   ├── Msg.hs
    │   │   ├── Setup.hs
    │   │   ├── Subscriptions.hs
    │   │   ├── Table.hs
    │   │   ├── Types.hs
    │   │   ├── Utils.hs
    │   │   └── Workers.hs
    │   ├── Socket.hs
    │   ├── Types.hs
    │   └── Users.hs
    ├── stack.yaml
    └── test/
        ├── Poker/
        │   ├── ActionSpec.hs
        │   ├── ActionValidationSpec.hs
        │   ├── BlindSpec.hs
        │   ├── GameSpec.hs
        │   ├── Generators.hs
        │   ├── HandSpec.hs
        │   └── UtilsSpec.hs
        ├── PokerSpec.hs
        └── Spec.hs
Download .txt
SYMBOL INDEX (63 symbols across 16 files)

FILE: client/app/actions/auth.js
  constant AUTH_API_URL (line 25) | const AUTH_API_URL =
  function login (line 40) | function login(username, password, history) {
  function register (line 67) | function register(username, email, password, history) {

FILE: client/app/actions/profile.js
  constant AUTH_API_URL (line 6) | const AUTH_API_URL = 'https://tenpoker.co.uk'

FILE: client/app/actions/types.js
  constant AUTH_REQUESTED (line 4) | const AUTH_REQUESTED = 'AUTH_REQUESTED'
  constant AUTHENTICATED (line 5) | const AUTHENTICATED = 'AUTHENTICATED'
  constant UNAUTHENTICATED (line 6) | const UNAUTHENTICATED = 'UNAUTHENTICATED'
  constant AUTHENTICATION_ERROR (line 7) | const AUTHENTICATION_ERROR = 'AUTHENTICATION_ERROR'
  constant GET_PROFILE_REQUEST (line 10) | const GET_PROFILE_REQUEST = 'GET_PROFILE_REQUEST'
  constant GET_PROFILE_SUCCESS (line 11) | const GET_PROFILE_SUCCESS = 'GET_PROFILE_SUCCESS'
  constant GET_PROFILE_ERR (line 12) | const GET_PROFILE_ERR = 'GET_PROFILE_ERR'
  constant CONNECT_SOCKET (line 15) | const CONNECT_SOCKET = 'CONNECT_SOCKET'
  constant SOCKET_CONNECTED (line 16) | const SOCKET_CONNECTED = 'SOCKET_CONNECTED'
  constant DISCONNECT_SOCKET (line 17) | const DISCONNECT_SOCKET = 'DISCONNECT_SOCKET'
  constant SOCKET_AUTH_SUCCESS (line 18) | const SOCKET_AUTH_SUCCESS = 'SOCKET_AUTH_SUCCESS'
  constant SOCKET_AUTH_ERR (line 19) | const SOCKET_AUTH_ERR = 'SOCKET_AUTH_ERR'
  constant SOCKET_CONN_ERR (line 20) | const SOCKET_CONN_ERR = 'SOCKET_CONN_ERR'
  constant GET_LOBBY (line 23) | const GET_LOBBY = 'server/GET_LOBBY'
  constant NEW_LOBBY (line 24) | const NEW_LOBBY = 'NEW_LOBBY'
  constant TAKE_SEAT (line 25) | const TAKE_SEAT = 'server/TAKE_SEAT'
  constant SUBSCRIBE_TO_TABLE (line 26) | const SUBSCRIBE_TO_TABLE = 'server/SUBSCRIBE_TO_TABLE'
  constant NEW_GAME_STATE (line 29) | const NEW_GAME_STATE = 'NEW_GAME_STATE'
  constant SUCCESSFULLY_SAT_DOWN (line 30) | const SUCCESSFULLY_SAT_DOWN = 'SUCCESSFULLY_SAT_DOWN'
  constant POST_BIG_BLIND (line 31) | const POST_BIG_BLIND = 'server/POST_BIG_BLIND'
  constant POST_SMALL_BLIND (line 32) | const POST_SMALL_BLIND = 'server/POST_SMALL_BLIND'
  constant BET (line 33) | const BET = 'server/BET'
  constant RAISE (line 34) | const RAISE = 'server/RAISE'
  constant CHECK (line 35) | const CHECK = 'server/CHECK'
  constant FOLD (line 36) | const FOLD = 'server/FOLD'
  constant CALL (line 37) | const CALL = 'server/CALL'
  constant SIT_IN (line 38) | const SIT_IN = 'server/SIT_IN'
  constant LEAVE_SEAT (line 39) | const LEAVE_SEAT = 'server/LEAVE_SEAT'

FILE: client/app/app.js
  constant MOUNT_NODE (line 32) | const MOUNT_NODE = document.getElementById('app')

FILE: client/app/configureStore.js
  function configureStore (line 13) | function configureStore(initialState = {}, history) {

FILE: client/app/containers/AppContainer.js
  class AppContainer (line 12) | class AppContainer extends Component {
    method componentDidMount (line 13) | componentDidMount() {
    method render (line 27) | render() {

FILE: client/app/containers/GameContainer.js
  class GameContainer (line 9) | class GameContainer extends React.Component {
    method constructor (line 10) | constructor(props) {
    method render (line 21) | render() {

FILE: client/app/containers/LobbyContainer.js
  class LobbyContainer (line 9) | class LobbyContainer extends React.Component {
    method componentDidMount (line 10) | componentDidMount() {
    method render (line 14) | render() {

FILE: client/app/containers/ProfileContainer.js
  class ProfileContainer (line 10) | class ProfileContainer extends React.Component {
    method componentDidMount (line 11) | componentDidMount() {
    method render (line 15) | render() {

FILE: client/app/containers/SignInFormContainer.js
  class SignInFormContainer (line 8) | class SignInFormContainer extends React.Component {
    method constructor (line 9) | constructor(props) {
    method render (line 36) | render() {

FILE: client/app/containers/SignUpFormContainer.js
  class SignUpFormContainer (line 8) | class SignUpFormContainer extends React.Component {
    method constructor (line 9) | constructor(props) {
    method render (line 39) | render() {

FILE: client/app/middleware/socket.js
  constant SOCKET_API_URL (line 18) | const SOCKET_API_URL =
  function addHandlers (line 29) | function addHandlers(socket, authToken, dispatch) {
  function connHandler (line 79) | function connHandler(dispatch, action) {
  function evaluate (line 126) | function evaluate(action, option) {
  function defaultExecute (line 146) | function defaultExecute(dispatch, next, action) {

FILE: client/app/reducers.js
  function routeReducer (line 27) | function routeReducer(state = routeInitialState, action) {
  function createReducer (line 42) | function createReducer(injectedReducers) {

FILE: client/app/reducers/socket.js
  function socket (line 19) | function socket(state = initialState, action) {

FILE: client/app/utils/request.js
  function parseJSON (line 8) | function parseJSON(response) {
  function checkStatus (line 22) | function checkStatus(response) {

FILE: client/server/middlewares/addDevMiddlewares.js
  function createWebpackMiddleware (line 6) | function createWebpackMiddleware(compiler, publicPath) {
Condensed preview — 160 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (482K chars).
[
  {
    "path": "README.md",
    "chars": 6015,
    "preview": "# Poker Maison\n\n## A poker app crafted with Haskell and React\n\nSupports games across multiple tables in realtime.\n\nPlaye"
  },
  {
    "path": "client/.babelrc",
    "chars": 440,
    "preview": "{\n  \"presets\": [\n    [\n      \"env\",\n      {\n        \"modules\": false\n      }\n    ],\n    \"react\",\n    \"stage-0\"\n  ],\n  \"e"
  },
  {
    "path": "client/.dockerignore",
    "chars": 12,
    "preview": "node_modules"
  },
  {
    "path": "client/.editorconfig",
    "chars": 208,
    "preview": "# editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\ni"
  },
  {
    "path": "client/.eslintignore",
    "chars": 218,
    "preview": "/build/**\n/coverage/**\n/docs/**\n/jsdoc/**\n/templates/**\n/tests/bench/**\n/tests/fixtures/**\n/tests/performance/**\n/tmp/**"
  },
  {
    "path": "client/.eslintrc",
    "chars": 1181,
    "preview": "{\n  \"parser\": \"babel-eslint\",\n  \"extends\": [\n    \"airbnb\",\n    \"plugin:react/recommended\",\n    \"prettier/react\",\n    \"pr"
  },
  {
    "path": "client/.gitattributes",
    "chars": 1470,
    "preview": "# From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes\n\n# Handle line endings automatically for "
  },
  {
    "path": "client/.gitignore",
    "chars": 146,
    "preview": "# Don't check auto-generated stuff into git\ncoverage\nbuild\nnode_modules\nstats.json\n\n# Cruft\n.DS_Store\nnpm-debug.log\n.ide"
  },
  {
    "path": "client/.prettierrc",
    "chars": 43,
    "preview": "{\n  \"singleQuote\": true,\n  \"semi\": false\n}\n"
  },
  {
    "path": "client/.travis.yml",
    "chars": 198,
    "preview": "language: node_js\n\nos: osx\n\nnode_js:\n  - 8\n  - 6\n  \nscript:\n  - npm run test\n  - npm run build\n\nnotifications:\n  email:\n"
  },
  {
    "path": "client/Dockerfile",
    "chars": 346,
    "preview": "# base image\nFROM node:10.16.3-alpine\n\nRUN apk add --no-cache \\\n    autoconf \\\n    automake \\\n    bash \\\n    g++ \\\n    l"
  },
  {
    "path": "client/LICENSE.md",
    "chars": 1082,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2018 Dinesh Pandiyan\n\nPermission is hereby granted, free of charge, to any person o"
  },
  {
    "path": "client/README.md",
    "chars": 5424,
    "preview": "<img src=\"https://raw.githubusercontent.com/flexdinesh/react-redux-boilerplate/master/app/components/Header/images/banne"
  },
  {
    "path": "client/app/actions/auth.js",
    "chars": 2379,
    "preview": "import axios from 'axios'\n\nimport * as types from './types'\nimport { checkStatus } from '../utils/request'\n\n/* Action Cr"
  },
  {
    "path": "client/app/actions/games.js",
    "chars": 1875,
    "preview": "import * as types from './types'\n\nexport const newGameState = (tableName, gameState) => ({\n  type: types.NEW_GAME_STATE,"
  },
  {
    "path": "client/app/actions/lobby.js",
    "chars": 652,
    "preview": "/* \n   The data value of the action forms the websocket msg payload.\n*/\nimport * as types from './types'\n\nexport const g"
  },
  {
    "path": "client/app/actions/profile.js",
    "chars": 1585,
    "preview": "import axios from 'axios'\n\nimport * as types from './types'\n\n/* Action Creators for User API authentication */\nconst AUT"
  },
  {
    "path": "client/app/actions/socket.js",
    "chars": 500,
    "preview": "import * as types from './types'\n\nexport const socketConnErr = err => ({ type: types.SOCKET_CONN_ERR, err })\n\nexport con"
  },
  {
    "path": "client/app/actions/tests/auth.test.js",
    "chars": 4174,
    "preview": "/* eslint-disable */\nimport configureMockStore from \"redux-mock-store\";\nimport thunk from \"redux-thunk\";\nimport axios fr"
  },
  {
    "path": "client/app/actions/types.js",
    "chars": 1569,
    "preview": "/* Actions prefixed with /server denote actions which trigger the sending of a websocket msg to server*/\n\n/* User API Ty"
  },
  {
    "path": "client/app/app.js",
    "chars": 1639,
    "preview": "/**\n * app.js\n *\n * This is the entry file for the application, only setup and boilerplate\n * code.\n */\n\n// Needed for r"
  },
  {
    "path": "client/app/components/ActionPanel.js",
    "chars": 3706,
    "preview": "import React from 'react'\n\n// TODO move to own component called pocket cards\nimport Card from './Card'\n\nconst getPocketC"
  },
  {
    "path": "client/app/components/App.js",
    "chars": 1662,
    "preview": "/**\n *\n * App\n *\n * This component is the skeleton around the actual pages, and should only\n * contain code that should "
  },
  {
    "path": "client/app/components/Board.js",
    "chars": 427,
    "preview": "import React from 'react';\n\nimport Card from './Card';\n\nconst Board = ({ cards }) => (\n  <div className=\"board-cards\">\n "
  },
  {
    "path": "client/app/components/Card.js",
    "chars": 1197,
    "preview": "import React from 'react';\n\nimport clubs from '../../static/Clubs.svg'\nimport hearts from '../../static/Hearts.svg'\nimpo"
  },
  {
    "path": "client/app/components/Footer.js",
    "chars": 119,
    "preview": "import React from 'react';\n\n\nconst Footer = () => (\n  <div className='footer'> Footer</div>\n);\n\nexport default Footer;\n"
  },
  {
    "path": "client/app/components/Game.js",
    "chars": 6725,
    "preview": "import React from 'react'\n\nimport ActionPanel from './ActionPanel'\nimport Board from './Board'\nimport Seat from './Seat'"
  },
  {
    "path": "client/app/components/Home.js",
    "chars": 96,
    "preview": "import React from 'react';\n\nconst Home = () => (\n  <div> Home\n  </div>\n);\n\nexport default Home;\n"
  },
  {
    "path": "client/app/components/Lobby.js",
    "chars": 1219,
    "preview": "import React from 'react';\n\nconst Lobby = ({ lobby, history, subscribeToATable }) =>\n  < table className=\"table game-tab"
  },
  {
    "path": "client/app/components/NavBar.js",
    "chars": 2202,
    "preview": "import React from 'react'\n\nconst NavBar = ({\n  isAuthenticated,\n  currRoute,\n  username,\n  history,\n  logoutUser\n}) => ("
  },
  {
    "path": "client/app/components/NotFoundPage.js",
    "chars": 120,
    "preview": "import React from 'react';\n\nconst NotFoundPage = () => (\n  <div> NotFoundPage\n  </div>\n);\n\nexport default NotFoundPage;\n"
  },
  {
    "path": "client/app/components/Profile.js",
    "chars": 118,
    "preview": "import React from 'react'\n\nconst Profile = username => <div className=\"profile\">profile</div>\n\nexport default Profile\n"
  },
  {
    "path": "client/app/components/Seat.js",
    "chars": 1760,
    "preview": "import React from 'react';\n\nlet isPlayerInactive = playerState =>\n  playerState === 'Folded' ||\n  playerState === 'SatOu"
  },
  {
    "path": "client/app/components/SignInForm.js",
    "chars": 1419,
    "preview": "import React from 'react';\n\nconst SignInForm = ({ handleChange, handleSubmit }) => (\n  <div className=\"form-container\">\n"
  },
  {
    "path": "client/app/components/SignUpForm.js",
    "chars": 2322,
    "preview": "import React from 'react'\n\nconst SignUpForm = ({ handleChange, handleSubmit }) => (\n  <div className=\"form-container\">\n "
  },
  {
    "path": "client/app/components/Signout.js",
    "chars": 103,
    "preview": "import React from 'react';\n\nconst Signout = () => (\n  <div> Signout </div>\n);\n\nexport default Signout;\n"
  },
  {
    "path": "client/app/configureStore.js",
    "chars": 1799,
    "preview": "/**\n * Create the store with dynamic reducers\n */\n\nimport { createStore, applyMiddleware, compose } from 'redux'\nimport "
  },
  {
    "path": "client/app/containers/AppContainer.js",
    "chars": 1270,
    "preview": "import React, { Component } from 'react'\nimport { connect } from 'react-redux'\nimport { withRouter } from 'react-router'"
  },
  {
    "path": "client/app/containers/GameContainer.js",
    "chars": 1586,
    "preview": "import React from 'react'\nimport { connect } from \"react-redux\";\n\nimport Game from '../components/Game'\nimport { getGame"
  },
  {
    "path": "client/app/containers/HomeContainer.js",
    "chars": 58,
    "preview": "import Home from '../components/Home'\n\nexport default Home"
  },
  {
    "path": "client/app/containers/LobbyContainer.js",
    "chars": 781,
    "preview": "import React from 'react'\nimport { connect } from \"react-redux\";\nimport { withRouter } from 'react-router-dom'\n\nimport {"
  },
  {
    "path": "client/app/containers/NavBarContainer.js",
    "chars": 642,
    "preview": "import { withRouter } from 'react-router-dom'\nimport { connect } from 'react-redux'\n\nimport { logoutUser } from '../acti"
  },
  {
    "path": "client/app/containers/ProfileContainer.js",
    "chars": 736,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\n\nimport { getUsername } from '../selectors/auth'\nimport "
  },
  {
    "path": "client/app/containers/SignInFormContainer.js",
    "chars": 1111,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\nimport { withRouter } from 'react-router'\n\nimport { logi"
  },
  {
    "path": "client/app/containers/SignUpFormContainer.js",
    "chars": 1197,
    "preview": "import React from 'react'\nimport { connect } from 'react-redux'\nimport { withRouter } from 'react-router'\n\nimport { regi"
  },
  {
    "path": "client/app/index.html",
    "chars": 952,
    "preview": "<!doctype html>\n<html lang=\"en\">\n\n<head>\n  <!-- The first thing in any HTML file should be the charset -->\n  <meta chars"
  },
  {
    "path": "client/app/middleware/socket.js",
    "chars": 4285,
    "preview": "import { fromJS } from 'immutable'\n\nimport { disconnectSocket } from '../actions/auth'\nimport {\n  socketConnErr,\n  socke"
  },
  {
    "path": "client/app/reducers/auth.js",
    "chars": 788,
    "preview": "import Immutable from 'immutable';\n\nimport * as types from '../actions/types';\n\nconst initialState = Immutable.Map({ aut"
  },
  {
    "path": "client/app/reducers/games.js",
    "chars": 331,
    "preview": "import Immutable from 'immutable';\n\nimport * as types from '../actions/types';\n\nconst initialState = Immutable.Map({});\n"
  },
  {
    "path": "client/app/reducers/lobby.js",
    "chars": 315,
    "preview": "import Immutable, { fromJS } from 'immutable';\n\nimport * as types from '../actions/types';\n\nconst initialState = Immutab"
  },
  {
    "path": "client/app/reducers/profile.js",
    "chars": 625,
    "preview": "import Immutable from 'immutable'\n\nimport * as types from '../actions/types'\n\nconst initialState = Immutable.Map({\n  pro"
  },
  {
    "path": "client/app/reducers/rootReducer.js",
    "chars": 311,
    "preview": "import { combineReducers } from 'redux-immutable'\n\nimport auth from './auth'\nimport socket from './socket'\nimport lobby "
  },
  {
    "path": "client/app/reducers/socket.js",
    "chars": 930,
    "preview": "\nimport Immutable, { fromJS } from 'immutable';\n\nimport {\n  SOCKET_AUTH_SUCCESS,\n  SOCKET_AUTH_ERR,\n  SOCKET_CONN_ERR,\n "
  },
  {
    "path": "client/app/reducers/tests/auth.test.js",
    "chars": 1454,
    "preview": "/* eslint-disable */\nimport { Map } from \"immutable\";\n\nimport reducer from \"../auth\";\nimport * as types from \"../../acti"
  },
  {
    "path": "client/app/reducers.js",
    "chars": 1056,
    "preview": "/**\n * Combine all reducers in this file and export the combined reducers.\n */\n\nimport { fromJS } from 'immutable'\nimpor"
  },
  {
    "path": "client/app/selectors/auth.js",
    "chars": 312,
    "preview": "import Immutable from 'immutable'\nimport { createSelector } from 'reselect'\n\nexport const auth = state => state.get('glo"
  },
  {
    "path": "client/app/selectors/games.js",
    "chars": 1056,
    "preview": "import { createSelector } from 'reselect'\n\nexport const getGames = state => state.get('global').get('games')\n\nexport con"
  },
  {
    "path": "client/app/selectors/lobby.js",
    "chars": 149,
    "preview": "import Immutable from 'immutable';\nimport { createSelector } from 'reselect'\n\nexport const getLobbyState = state => stat"
  },
  {
    "path": "client/app/selectors/profile.js",
    "chars": 190,
    "preview": "import Immutable from 'immutable'\nimport { createSelector } from 'reselect'\n\nexport const profile = state => ({})\n\nexpor"
  },
  {
    "path": "client/app/selectors/route.js",
    "chars": 268,
    "preview": "import Immutable from 'immutable'\nimport { createSelector } from 'reselect'\n\nexport const getLocation = state => state.g"
  },
  {
    "path": "client/app/selectors/socket.js",
    "chars": 250,
    "preview": "import Immutable from 'immutable';\nimport { createSelector } from 'reselect'\n\nexport const socket = state => state.get('"
  },
  {
    "path": "client/app/selectors/tests/games.test.js",
    "chars": 296,
    "preview": "/* eslint-disable */\nimport Immutable from 'immutable'\n\ndescribe('Games Selectors', () => {\n  describe('getGames', () =>"
  },
  {
    "path": "client/app/styles/_common.scss",
    "chars": 145,
    "preview": "/* Re-export all common scss files */\n@import 'common/mixins';\n@import 'common/variables';\n@import 'common/typography';\n"
  },
  {
    "path": "client/app/styles/common/_colours.scss",
    "chars": 523,
    "preview": "@import \"variables\";\n\n.card {\n  background-color: $neutral-colour-100;\n  color: $neutral-colour-800;\n}\n\n.navbar {\n  //ba"
  },
  {
    "path": "client/app/styles/common/_mixins.scss",
    "chars": 1562,
    "preview": "@import \"variables\";\n\n/*\n---------------------------------\n  Media queries\n---------------------------------\n*/\n\n// Smal"
  },
  {
    "path": "client/app/styles/common/_typography.scss",
    "chars": 6191,
    "preview": "// Import Modular Scale Plugin\n@import \"~modularscale-sass/stylesheets/modularscale\";\n\n@import \"variables\";\n\n/*\n--------"
  },
  {
    "path": "client/app/styles/common/_variables.scss",
    "chars": 3278,
    "preview": "/*** Variables ***/\n\n/*\n---------------------------------\n  Colours\n---------------------------------\n*/\n\n$primary-colou"
  },
  {
    "path": "client/app/styles/components/_buttons.scss",
    "chars": 362,
    "preview": "@import \"../common\";\n\n.button {\n  border-radius: 1.5em;\n  width: 8em;\n  height: 4rem;\n  margin: 0.5em;\n  padding: 0.8em;"
  },
  {
    "path": "client/app/styles/components/_footer.scss",
    "chars": 32,
    "preview": ".footer {\n  background: green;\n}"
  },
  {
    "path": "client/app/styles/components/_forms.scss",
    "chars": 162,
    "preview": ".form-container {\n  width: 35vw;\n  min-width: 200px;\n  margin: auto;\n  text-align: center;\n}\n.form {\n  text-align: centr"
  },
  {
    "path": "client/app/styles/components/_game.scss",
    "chars": 430,
    "preview": "@import \"game/table\";\n@import \"game/actionPanel\";\n@import \"game/boardCards\";\n@import \"game/cards\";\n@import \"game/seat\";\n"
  },
  {
    "path": "client/app/styles/components/_lobby.scss",
    "chars": 137,
    "preview": ".game-table-list {\n  margin: auto;\n  text-align: center;\n  margin-top: 10%;\n  width: 60%;\n}\n\n.game-table-list tr {\n  pad"
  },
  {
    "path": "client/app/styles/components/_navbar.scss",
    "chars": 789,
    "preview": ".navbar {\n  display: flex;\n  text-align: center;\n  min-height: 4em;\n}\n\n.navbar-brand {\n  width: 16em;\n  margin: auto;\n  "
  },
  {
    "path": "client/app/styles/components/game/_actionPanel.scss",
    "chars": 227,
    "preview": ".action-panel {\n  grid-column-start: 2;\n  grid-row-start: 2;\n  width: 70%;\n  margin: auto;\n  grid-row-end: 4;\n  display:"
  },
  {
    "path": "client/app/styles/components/game/_boardCards.scss",
    "chars": 207,
    "preview": ".board-cards {\n  justify-self: center;\n  align-self: center;\n  grid-area: 3/1/5/6;\n}\n\n.board-cards-container {\n  display"
  },
  {
    "path": "client/app/styles/components/game/_cards.scss",
    "chars": 1511,
    "preview": "@import \"../../common\";\n\n.rank {\n  font-family: \"MonoP-Bold\", sans-serif;\n  font-weight: 800;\n  height: 1.32em;\n}\n\n.suit"
  },
  {
    "path": "client/app/styles/components/game/_seat.scss",
    "chars": 1732,
    "preview": "/*\n\nThe real power of mixins comes when you pass them arguments. \nArguments are declared as a parenthesized, comma-separ"
  },
  {
    "path": "client/app/styles/components/game/_slider.scss",
    "chars": 1319,
    "preview": ".slidecontainer {\n  width: 100%; /* Width of the outside container */\n  margin: auto;\n  text-align: center;\n  padding-bo"
  },
  {
    "path": "client/app/styles/components/game/_table.scss",
    "chars": 2911,
    "preview": "/* Six Player Table */\n.table-container {\n  display: grid;\n  grid-template-columns: 1.5fr 1fr 1.5fr 1fr 1.5fr;\n  grid-te"
  },
  {
    "path": "client/app/styles/layout/_app.scss",
    "chars": 624,
    "preview": "@import \"../common/mixins\";\n\n%full-screen {\n  position: absolute;\n  height: 100%;\n  width: 100%;\n}\n\n.app {\n  @extend %fu"
  },
  {
    "path": "client/app/styles/main.scss",
    "chars": 395,
    "preview": "/* Main App Grid */\n@import \"layout/app\";\n\n/* Improt common modules */\n@import \"common\";\n\n/* Components */\n@import \"comp"
  },
  {
    "path": "client/app/utils/request.js",
    "chars": 778,
    "preview": "/**\n * Parses the JSON returned by a network request\n *\n * @param  {object} response A response from a network request\n "
  },
  {
    "path": "client/config/jest-mocks/cssModule.js",
    "chars": 30,
    "preview": "module.exports = 'CSS_MODULE'\n"
  },
  {
    "path": "client/config/jest-mocks/image.js",
    "chars": 30,
    "preview": "module.exports = 'IMAGE_MOCK'\n"
  },
  {
    "path": "client/config/jest.config.js",
    "chars": 809,
    "preview": "module.exports = {\n  collectCoverageFrom: [\n    'app/**/*.{js,jsx}',\n    '!app/**/*.test.{js,jsx}',\n    '!app/*/RbGenera"
  },
  {
    "path": "client/config/test-setup.js",
    "chars": 263,
    "preview": "// needed for regenerator-runtime\n// (ES7 generator support is required by redux-saga)\nimport 'babel-polyfill'\n\n// Enzym"
  },
  {
    "path": "client/config/webpack.base.babel.js",
    "chars": 2945,
    "preview": "/**\n * COMMON WEBPACK CONFIGURATION\n */\n\nconst path = require('path')\nconst webpack = require('webpack')\n\nprocess.noDepr"
  },
  {
    "path": "client/config/webpack.dev.babel.js",
    "chars": 1338,
    "preview": "/**\n * DEVELOPMENT WEBPACK CONFIGURATION\n */\n\nconst path = require('path')\nconst webpack = require('webpack')\nconst Html"
  },
  {
    "path": "client/config/webpack.prod.babel.js",
    "chars": 1184,
    "preview": "// Important modules this config uses\nconst path = require('path')\n// const webpack = require('webpack');\nconst HtmlWebp"
  },
  {
    "path": "client/jest.config.js",
    "chars": 49,
    "preview": "module.exports = require('./config/jest.config')\n"
  },
  {
    "path": "client/netlify.toml",
    "chars": 445,
    "preview": "[build]\n  publish = \"build\"\n  command = \"yarn run build:prod\"\n\n[build.environment]\n  NODE_VERSION = \"10.16.3\"\n  YARN_VER"
  },
  {
    "path": "client/package.json",
    "chars": 4233,
    "preview": "{\n  \"name\": \"poker-client\",\n  \"version\": \"0.1.0\",\n  \"description\": \"Poker Client\",\n  \"repository\": {\n    \"type\": \"git\",\n"
  },
  {
    "path": "client/server/index.js",
    "chars": 985,
    "preview": "/* eslint consistent-return:0 */\n\nconst express = require('express')\nconst { resolve } = require('path')\nconst logger = "
  },
  {
    "path": "client/server/middlewares/addDevMiddlewares.js",
    "chars": 1018,
    "preview": "const path = require('path')\nconst webpack = require('webpack')\nconst webpackDevMiddleware = require('webpack-dev-middle"
  },
  {
    "path": "client/server/middlewares/addProdMiddlewares.js",
    "chars": 700,
    "preview": "const path = require('path')\nconst express = require('express')\nconst compression = require('compression')\n\nmodule.expor"
  },
  {
    "path": "client/server/middlewares/frontendMiddleware.js",
    "chars": 480,
    "preview": "/* eslint-disable global-require */\n\n/**\n * Front-end middleware\n */\nmodule.exports = (app, options) => {\n  const isProd"
  },
  {
    "path": "client/server/util/argv.js",
    "chars": 60,
    "preview": "module.exports = require('minimist')(process.argv.slice(2))\n"
  },
  {
    "path": "client/server/util/logger.js",
    "chars": 817,
    "preview": "/* eslint-disable no-console */\n\nconst chalk = require('chalk')\nconst ip = require('ip')\n\nconst divider = chalk.gray('\\n"
  },
  {
    "path": "client/server/util/port.js",
    "chars": 103,
    "preview": "const argv = require('./argv')\n\nmodule.exports = parseInt(argv.port || process.env.PORT || '3000', 10)\n"
  },
  {
    "path": "client/shell.nix",
    "chars": 415,
    "preview": "# so we can access the `pkgs` and `stdenv` variables\nwith import <nixpkgs> {};\n\n# Make a new \"derivation\" that represent"
  },
  {
    "path": "client/static/fonts/GothamPro/GothamHTF-BookCondensed.otf",
    "chars": 55427,
    "preview": "\n\n\n\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n  <link rel=\"dns-prefetch\" href=\"https://asse"
  },
  {
    "path": "docker-compose.yml",
    "chars": 1047,
    "preview": "version: \"3.5\"\n\nservices:\n  db:\n    image: postgres:9.4\n    environment:\n      - DB_USER=postgres\n      - DB_PASS=postgr"
  },
  {
    "path": "server/.dev.env",
    "chars": 119,
    "preview": "dbConnStr='port=5432 user=postgres dbname=postgres password=postgres'\nport=8000\nsecret=\"wwaaifidsa9109f0dasfda-=2-13\"\n\n"
  },
  {
    "path": "server/.dockerignore",
    "chars": 47,
    "preview": ".dockerignore\n.gitignore\n.stack-work\nDockerfile"
  },
  {
    "path": "server/.gitignore",
    "chars": 262,
    "preview": "poker-server.cabal\n*~\n.env\nbuild\ndist\ndist-*\ncabal-dev\n*.o\n*.hi\n*.chi\n*.chs.h\n*.dyn_o\n*.dyn_hi\n.hpc\n.hsenv\n.cabal-sandbo"
  },
  {
    "path": "server/.projectile",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "server/ChangeLog.md",
    "chars": 52,
    "preview": "# Changelog for poker-server\n\n## Unreleased changes\n"
  },
  {
    "path": "server/Dockerfile",
    "chars": 221,
    "preview": "FROM fpco/stack-build-small\n\nRUN mkdir -p /app\n\n\nCOPY . /app\n\nWORKDIR /app\n\nRUN apt-get update && \\\n  apt-get install li"
  },
  {
    "path": "server/README.md",
    "chars": 9,
    "preview": "# Server\n"
  },
  {
    "path": "server/Setup.hs",
    "chars": 46,
    "preview": "import Distribution.Simple\nmain = defaultMain\n"
  },
  {
    "path": "server/UNLICENSE.txt",
    "chars": 1209,
    "preview": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, c"
  },
  {
    "path": "server/app/Main.hs",
    "chars": 1589,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n\nmodule Main where\n\nimport           Control.Concurrent.Async\nimport qualified Data.B"
  },
  {
    "path": "server/bootstrap.sh",
    "chars": 751,
    "preview": "# install virtual env and python\n\n# activate python virtual env\nvirtualenv venv\n~ source venv/bin/activate\n\n# install an"
  },
  {
    "path": "server/deploy-server.sh",
    "chars": 842,
    "preview": "#!/bin/bash\n## First arg to the bashfile must be the path to the key file to SSH into the EC2 host(s)\n##\n## Build and pu"
  },
  {
    "path": "server/deploy.sh",
    "chars": 1186,
    "preview": "#!/usr/bin/env bash\nset -o errexit\nset -o pipefail\nset -o nounset\n\n# Location of executable\nBUILD_DIR=\"./build\"\nBINARY_P"
  },
  {
    "path": "server/docs/lobbyAPI.md",
    "chars": 546,
    "preview": "## POST /gooby\n\n### Request:\n\n- Supported content types are:\n\n    - `application/json;charset=utf-8`\n    - `application/"
  },
  {
    "path": "server/docs/socket.md",
    "chars": 477,
    "preview": "{\"tag\":\"subscribeToTable\",\"contents\":\"Black\"}\n\n{\"tag\":\"TakeSeat\",\"contents\":[\"Black\",3000]}\n\n{\"tag\":\"GameMove\",\"contents"
  },
  {
    "path": "server/docs/userAPI.md",
    "chars": 1797,
    "preview": "## POST /register\n\n### Request:\n\n- Supported content types are:\n\n  - `application/json;charset=utf-8`\n  - `application/j"
  },
  {
    "path": "server/package.yaml",
    "chars": 1790,
    "preview": "name: poker-server\nversion: 0.1.0.0\ngithub: \"githubuser/poker-server\"\nlicense: Unlicense\nauthor: \"therewillbecode\"\nmaint"
  },
  {
    "path": "server/ping.sh",
    "chars": 854,
    "preview": "\n#!/usr/bin/env bash\nset -o errexit\nset -o pipefail\nset -o nounset\n\nBINARY_PATH=\".stack-work/dist/x86_64-linux/Cabal-2.4"
  },
  {
    "path": "server/provision.sh",
    "chars": 817,
    "preview": "#!/usr/bin/env bash\n\nset -o errexit\nset -o pipefail\nset -o nounset\n\nREMOTE=\"ubuntu@34.244.29.59\"\nINSTALL_SYS_DEPENDENCIE"
  },
  {
    "path": "server/server.service",
    "chars": 195,
    "preview": "[Unit]\nDescription=server\n\n[Service]\nType=simple\nExecStart=/opt/server/poker-server-exe\nRestart=always\nUser=ubuntu\nEnvir"
  },
  {
    "path": "server/shell.nix",
    "chars": 3471,
    "preview": "{ nixpkgs ? import <nixpkgs> {}, compiler ? \"default\", doBenchmark ? false }:\n\nlet\n\n  inherit (nixpkgs) pkgs;\n\n  f = { m"
  },
  {
    "path": "server/src/API.hs",
    "chars": 5547,
    "preview": "{-# LANGUAGE DataKinds #-}\n{-# LANGUAGE DeriveGeneric #-}\n{-# LANGUAGE FlexibleInstances #-}\n{-# LANGUAGE MultiParamType"
  },
  {
    "path": "server/src/Bots.hs",
    "chars": 6661,
    "preview": "{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE ScopedTypeV"
  },
  {
    "path": "server/src/Database.hs",
    "chars": 7997,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Database where\n\nimport Control.Lens hiding ("
  },
  {
    "path": "server/src/Env.hs",
    "chars": 2098,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n\nmodule Env where\n\nimport qualified Data.ByteString.Char8 as C\nimport Data.ByteString"
  },
  {
    "path": "server/src/Poker/ActionValidation.hs",
    "chars": 11278,
    "preview": "--  TODO - should factor out the hasEnoughChips check for each action and then just sequence it\n--  inside the parent va"
  },
  {
    "path": "server/src/Poker/Game/Actions.hs",
    "chars": 5752,
    "preview": "{-# LANGUAGE FlexibleContexts #-}\n{-# LANGUAGE MultiParamTypeClasses #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Poker."
  },
  {
    "path": "server/src/Poker/Game/Blinds.hs",
    "chars": 3651,
    "preview": "{-# LANGUAGE FlexibleContexts #-}\n{-# LANGUAGE MultiParamTypeClasses #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Poker."
  },
  {
    "path": "server/src/Poker/Game/Game.hs",
    "chars": 15613,
    "preview": "{-# LANGUAGE FlexibleContexts #-}\n{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE MultiParamTypeClasses #-}\n{-# LANGUAGE Record"
  },
  {
    "path": "server/src/Poker/Game/Hands.hs",
    "chars": 2364,
    "preview": "{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE TupleSections #-}\n\nmodule Poker.Game.Hands where\n\nimport Data.Function (on)\nimp"
  },
  {
    "path": "server/src/Poker/Game/Privacy.hs",
    "chars": 2554,
    "preview": "{-\n  Logic for excluding sensitive game data from game state.\n-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Poker.Game.Pri"
  },
  {
    "path": "server/src/Poker/Game/Utils.hs",
    "chars": 6042,
    "preview": "{-# LANGUAGE FlexibleContexts #-}\n{-# LANGUAGE MultiParamTypeClasses #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Poker."
  },
  {
    "path": "server/src/Poker/Poker.hs",
    "chars": 6127,
    "preview": "{-# LANGUAGE FlexibleContexts #-}\n{-# LANGUAGE MultiParamTypeClasses #-}\n{-# LANGUAGE RecordWildCards #-}\n\n{-\n  Public A"
  },
  {
    "path": "server/src/Poker/Types.hs",
    "chars": 12450,
    "preview": "{-# LANGUAGE DataKinds #-}\n{-# LANGUAGE DeriveAnyClass #-}\n{-# LANGUAGE DeriveGeneric #-}\n{-# LANGUAGE DerivingStrategie"
  },
  {
    "path": "server/src/Schema.hs",
    "chars": 1596,
    "preview": "{-# LANGUAGE DataKinds #-}\n{-# LANGUAGE DerivingStrategies #-}\n{-# LANGUAGE FlexibleInstances #-}\n{-# LANGUAGE GADTs #-}"
  },
  {
    "path": "server/src/Socket/Auth.hs",
    "chars": 1517,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Socket.Auth where\n\nimport Control.Monad.Exce"
  },
  {
    "path": "server/src/Socket/Clients.hs",
    "chars": 5913,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Socket.Clients where\n\nimport Control.Concurr"
  },
  {
    "path": "server/src/Socket/Lobby.hs",
    "chars": 2572,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Socket.Lobby where\n\nimport Control.Concurren"
  },
  {
    "path": "server/src/Socket/Msg.hs",
    "chars": 8626,
    "preview": "{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE TupleSectio"
  },
  {
    "path": "server/src/Socket/Setup.hs",
    "chars": 1037,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n\nmodule Socket.Setup where\n\nimport Control.Concurrent.Async (Async)\nimport Control.Mo"
  },
  {
    "path": "server/src/Socket/Subscriptions.hs",
    "chars": 3394,
    "preview": "{-\n  Logic for updating players about table changes\n-}\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #"
  },
  {
    "path": "server/src/Socket/Table.hs",
    "chars": 10723,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Socket.Table where\n\nimport Control.Concurren"
  },
  {
    "path": "server/src/Socket/Types.hs",
    "chars": 4943,
    "preview": "{-# LANGUAGE DeriveAnyClass #-}\n{-# LANGUAGE DeriveGeneric #-}\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWil"
  },
  {
    "path": "server/src/Socket/Utils.hs",
    "chars": 1162,
    "preview": "module Socket.Utils where\n\nimport Data.Aeson (decode, encode)\nimport qualified Data.ByteString as BS\nimport qualified Da"
  },
  {
    "path": "server/src/Socket/Workers.hs",
    "chars": 3495,
    "preview": "{-# LANGUAGE RecordWildCards #-}\n\nmodule Socket.Workers where\n\nimport Control.Concurrent (threadDelay)\nimport Control.Co"
  },
  {
    "path": "server/src/Socket.hs",
    "chars": 11180,
    "preview": "{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE ScopedTypeV"
  },
  {
    "path": "server/src/Types.hs",
    "chars": 1417,
    "preview": "{-# LANGUAGE DeriveAnyClass #-}\n{-# LANGUAGE DeriveGeneric #-}\n{-# LANGUAGE OverloadedStrings #-}\n\nmodule Types where\n\ni"
  },
  {
    "path": "server/src/Users.hs",
    "chars": 4267,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Users where\n\nimport Control.Monad.Except\n  ("
  },
  {
    "path": "server/stack.yaml",
    "chars": 2930,
    "preview": "# Resolver to choose a 'specific' stackage snapshot or a compiler version.\n# A snapshot resolver dictates the compiler v"
  },
  {
    "path": "server/test/Poker/ActionSpec.hs",
    "chars": 11196,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Poker.ActionSpec where\n\nimport Control.Lens "
  },
  {
    "path": "server/test/Poker/ActionValidationSpec.hs",
    "chars": 33685,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Poker.ActionValidationSpec where\n\nimport Con"
  },
  {
    "path": "server/test/Poker/BlindSpec.hs",
    "chars": 13111,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n\nmodule Poker.BlindSpec where\n\nimport Control.Lens ("
  },
  {
    "path": "server/test/Poker/GameSpec.hs",
    "chars": 41252,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE ScopedTypeVariables #-}\n\nmodule Poker.G"
  },
  {
    "path": "server/test/Poker/Generators.hs",
    "chars": 6162,
    "preview": "{-# LANGUAGE DataKinds #-}\n{-# LANGUAGE LambdaCase #-}\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #"
  },
  {
    "path": "server/test/Poker/HandSpec.hs",
    "chars": 1038,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE ScopedTypeVariables #-}\n\nmodule Poker.H"
  },
  {
    "path": "server/test/Poker/UtilsSpec.hs",
    "chars": 2987,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n\nmodule Poker.UtilsSpec where\n\nimport Control.Lens ((.~))\nimport Data.List ()\nimport "
  },
  {
    "path": "server/test/PokerSpec.hs",
    "chars": 2389,
    "preview": "{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE ScopedTypeVariables #-}\n\nmodule PokerSp"
  },
  {
    "path": "server/test/Spec.hs",
    "chars": 44,
    "preview": "{-# OPTIONS_GHC -F -pgmF hspec-discover #-}\n"
  }
]

About this extraction

This page contains the full source code of the therewillbecode/haskell-poker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 160 files (443.2 KB), approximately 125.8k tokens, and a symbol index with 63 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!