├── assets
├── css
│ ├── phoenix.css
│ ├── footer.css
│ ├── account.css
│ ├── tab.css
│ ├── pagination.css
│ ├── auth.css
│ ├── home.css
│ ├── app.css
│ ├── chat.css
│ ├── raise-modal.css
│ ├── rooms.css
│ ├── toasts.css
│ ├── modal.css
│ ├── profile.css
│ ├── room.css
│ ├── controls.css
│ └── header.css
├── static
│ └── images
│ │ ├── poker.jpg
│ │ ├── office.jpg
│ │ ├── phoenix.png
│ │ ├── spades.png
│ │ ├── card-back.svg.png
│ │ └── cards
│ │ ├── 2_of_spades.svg
│ │ ├── 3_of_spades.svg
│ │ └── 4_of_spades.svg
├── elm
│ ├── src
│ │ ├── Data
│ │ │ ├── Configuration.elm
│ │ │ ├── Notifications
│ │ │ │ ├── Delete.elm
│ │ │ │ └── Invite.elm
│ │ │ ├── WinningHand.elm
│ │ │ ├── Facebook.elm
│ │ │ ├── Session.elm
│ │ │ ├── Chat.elm
│ │ │ ├── AuthToken.elm
│ │ │ ├── PasswordReset.elm
│ │ │ ├── RoomPage.elm
│ │ │ ├── Profile.elm
│ │ │ ├── Player.elm
│ │ │ ├── Room.elm
│ │ │ └── Card.elm
│ │ ├── Views
│ │ │ ├── Helpers.elm
│ │ │ ├── Account.elm
│ │ │ ├── Form.elm
│ │ │ ├── Chat.elm
│ │ │ ├── Footer.elm
│ │ │ ├── Bank.elm
│ │ │ ├── Header.elm
│ │ │ └── Actions.elm
│ │ ├── Page
│ │ │ ├── NotFound.elm
│ │ │ ├── Errored.elm
│ │ │ ├── Room
│ │ │ │ ├── PushMessages.elm
│ │ │ │ ├── Helpers.elm
│ │ │ │ └── SocketConfig.elm
│ │ │ ├── Home.elm
│ │ │ ├── Register.elm
│ │ │ ├── Login.elm
│ │ │ ├── ResetPassword.elm
│ │ │ ├── ForgotPassword.elm
│ │ │ └── Room.elm
│ │ ├── Widgets
│ │ │ ├── FacebookLogin.elm
│ │ │ ├── Toast.elm
│ │ │ ├── Modal.elm
│ │ │ ├── Dropdown.elm
│ │ │ ├── PlayerToolbar.elm
│ │ │ └── Pagination.elm
│ │ ├── Ports.elm
│ │ ├── Types
│ │ │ ├── Room
│ │ │ │ ├── ModalState.elm
│ │ │ │ └── Messages.elm
│ │ │ ├── Dropdowns.elm
│ │ │ └── Page.elm
│ │ ├── Util.elm
│ │ ├── Route.elm
│ │ └── Request
│ │ │ └── Player.elm
│ └── vendor
│ │ └── Phoenix
│ │ ├── Internal
│ │ ├── Message.elm
│ │ ├── Helpers.elm
│ │ ├── Channel.elm
│ │ ├── Presence.elm
│ │ └── Socket.elm
│ │ ├── Push.elm
│ │ ├── Presence.elm
│ │ └── Socket.elm
├── package.json
├── elm-package.json
├── brunch-config.js
└── js
│ ├── socket.js
│ └── app.js
├── Procfile
├── test
├── test_helper.exs
├── poker_ex_client_web
│ ├── views
│ │ ├── page_view_test.exs
│ │ ├── layout_view_test.exs
│ │ └── error_view_test.exs
│ └── controllers
│ │ └── page_controller_test.exs
└── support
│ ├── channel_case.ex
│ └── conn_case.ex
├── lib
├── poker_ex_client_web
│ ├── templates
│ │ ├── page
│ │ │ └── index.html.eex
│ │ └── layout
│ │ │ └── app.html.eex
│ ├── views
│ │ ├── page_view.ex
│ │ ├── layout_view.ex
│ │ ├── error_view.ex
│ │ └── error_helpers.ex
│ ├── controllers
│ │ └── page_controller.ex
│ ├── router.ex
│ ├── gettext.ex
│ ├── channels
│ │ └── user_socket.ex
│ └── endpoint.ex
├── poker_ex_client.ex
├── poker_ex_client
│ └── application.ex
└── poker_ex_client_web.ex
├── elixir_buildpack.config
├── phoenix_static_buildpack.config
├── config
├── test.exs
├── config.exs
├── dev.exs
└── prod.exs
├── priv
└── gettext
│ ├── en
│ └── LC_MESSAGES
│ │ └── errors.po
│ └── errors.pot
├── compile
├── README.md
├── .gitignore
├── mix.exs
└── mix.lock
/assets/css/phoenix.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: MIX_ENV=prod mix phx.server
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
3 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/assets/static/images/poker.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zkayser/pokerex_client/HEAD/assets/static/images/poker.jpg
--------------------------------------------------------------------------------
/elixir_buildpack.config:
--------------------------------------------------------------------------------
1 | erlang_version=20.0
2 | elixir_version=1.6.0
3 | always_rebuild=false
4 | runtime_path=/app
--------------------------------------------------------------------------------
/assets/static/images/office.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zkayser/pokerex_client/HEAD/assets/static/images/office.jpg
--------------------------------------------------------------------------------
/assets/static/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zkayser/pokerex_client/HEAD/assets/static/images/phoenix.png
--------------------------------------------------------------------------------
/assets/static/images/spades.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zkayser/pokerex_client/HEAD/assets/static/images/spades.png
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.PageView do
2 | use PokerExClientWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/assets/css/footer.css:
--------------------------------------------------------------------------------
1 | .bottom {
2 | position: relative;
3 | bottom: -15vh;
4 | }
5 |
6 | .footer-row {
7 | margin-bottom: 0;
8 | }
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.LayoutView do
2 | use PokerExClientWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/assets/static/images/card-back.svg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zkayser/pokerex_client/HEAD/assets/static/images/card-back.svg.png
--------------------------------------------------------------------------------
/test/poker_ex_client_web/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.PageViewTest do
2 | use PokerExClientWeb.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/test/poker_ex_client_web/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.LayoutViewTest do
2 | use PokerExClientWeb.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Configuration.elm:
--------------------------------------------------------------------------------
1 | module Data.Configuration exposing (..)
2 |
3 |
4 | type alias Configuration =
5 | { socketUrl : String
6 | , apiUrl : String
7 | }
8 |
--------------------------------------------------------------------------------
/phoenix_static_buildpack.config:
--------------------------------------------------------------------------------
1 | clean_cache=false
2 | compile="compile"
3 | node_version=9.0.0
4 | phoenix_relative_path=.
5 | remove_node=false
6 | assets_path=./assets
7 | phoenix_ex=phx
--------------------------------------------------------------------------------
/assets/css/account.css:
--------------------------------------------------------------------------------
1 | .account-info {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: center;
5 | align-items: center;
6 | }
7 |
8 | .account-info-item {
9 | flex: 1;
10 | }
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.PageController do
2 | use PokerExClientWeb, :controller
3 |
4 | def index(conn, _params) do
5 | render conn, "index.html"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/test/poker_ex_client_web/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.PageControllerTest do
2 | use PokerExClientWeb.ConnCase
3 |
4 | test "GET /", %{conn: conn} do
5 | conn = get conn, "/"
6 | assert html_response(conn, 200) =~ "Welcome to Phoenix!"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/assets/elm/src/Views/Helpers.elm:
--------------------------------------------------------------------------------
1 | module Views.Helpers exposing (ActivePage(..))
2 |
3 | {-
4 | Determines which navbar link will be rendered as active
5 | -}
6 |
7 |
8 | type ActivePage
9 | = Other
10 | | Login
11 | | Registration
12 | | Home
13 | | Room
14 | | Rooms
15 | | Profile
16 |
--------------------------------------------------------------------------------
/lib/poker_ex_client.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClient do
2 | @moduledoc """
3 | PokerExClient keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :poker_ex_client, PokerExClientWeb.Endpoint,
6 | http: [port: 4001],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
--------------------------------------------------------------------------------
/assets/css/tab.css:
--------------------------------------------------------------------------------
1 | .tab-holder {
2 | z-index: 100;
3 | box-shadow: 0.05rem 0.05rem 0.05rem 0.05rem black;
4 | }
5 |
6 | a.active.active-tab {
7 | font-weight: 600;
8 | text-decoration: underline;
9 | transform: scale(1.1);
10 | transition: transform 0.5s ease-in-out;
11 | }
12 |
13 | .tabs .tab a {
14 | cursor: pointer;
15 | }
16 |
17 | .row.tab-holder-row {
18 | margin-bottom: 0;
19 | }
--------------------------------------------------------------------------------
/assets/elm/src/Page/NotFound.elm:
--------------------------------------------------------------------------------
1 | module Page.NotFound exposing (view)
2 |
3 | import Data.Session as Session exposing (Session)
4 | import Html exposing (Html, div, h1, main_, text)
5 | import Html.Attributes exposing (class, id, src, tabindex)
6 |
7 |
8 | view : Session -> Html msg
9 | view session =
10 | main_ [ id "content", class "container", tabindex -1 ]
11 | [ h1 [] [ text "Not Found" ] ]
12 |
--------------------------------------------------------------------------------
/assets/elm/src/Widgets/FacebookLogin.elm:
--------------------------------------------------------------------------------
1 | module Widgets.FacebookLogin exposing (viewFBLogin)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (class)
5 | import Html.Events exposing (onClick)
6 |
7 |
8 | viewFBLogin : msg -> Html msg
9 | viewFBLogin msg =
10 | button [ class "btn auth-btn waves-effect blue darken-4 white-text", onClick msg ]
11 | [ text "Login with Facebook" ]
12 |
--------------------------------------------------------------------------------
/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
--------------------------------------------------------------------------------
/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This file is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here as no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Notifications/Delete.elm:
--------------------------------------------------------------------------------
1 | module Data.Notifications.Delete exposing (..)
2 |
3 | import Json.Decode as Decode exposing (Decoder)
4 | import Json.Decode.Pipeline exposing (decode, required)
5 |
6 |
7 | type alias Delete =
8 | { title : String
9 | , owner : String
10 | }
11 |
12 |
13 | decoder : Decoder Delete
14 | decoder =
15 | decode Delete
16 | |> required "title" Decode.string
17 | |> required "owner" Decode.string
18 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Notifications/Invite.elm:
--------------------------------------------------------------------------------
1 | module Data.Notifications.Invite exposing (..)
2 |
3 | import Json.Decode as Decode exposing (Decoder)
4 | import Json.Decode.Pipeline exposing (decode, required)
5 |
6 |
7 | type alias Invite =
8 | { title : String
9 | , owner : String
10 | }
11 |
12 |
13 | decoder : Decoder Invite
14 | decoder =
15 | decode Invite
16 | |> required "title" Decode.string
17 | |> required "owner" Decode.string
18 |
--------------------------------------------------------------------------------
/assets/elm/src/Ports.elm:
--------------------------------------------------------------------------------
1 | port module Ports exposing (..)
2 |
3 | import Json.Encode exposing (Value)
4 |
5 |
6 | port storeSession : Maybe String -> Cmd msg
7 |
8 |
9 | port logout : () -> Cmd msg
10 |
11 |
12 | port triggerFBInviteRequest : () -> Cmd msg
13 |
14 |
15 | port scrollChatToTop : () -> Cmd msg
16 |
17 |
18 | port onSessionChange : (Value -> msg) -> Sub msg
19 |
20 |
21 | port loginWithFB : () -> Cmd msg
22 |
23 |
24 | port onFBLogin : (Value -> msg) -> Sub msg
25 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.ErrorView do
2 | use PokerExClientWeb, :view
3 |
4 | def render("404.html", _assigns) do
5 | "Page not found"
6 | end
7 |
8 | def render("500.html", _assigns) do
9 | "Internal server error"
10 | end
11 |
12 | # In case no render clause matches or no
13 | # template is found, let's render it as 500
14 | def template_not_found(_template, assigns) do
15 | render "500.html", assigns
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/assets/elm/src/Types/Room/ModalState.elm:
--------------------------------------------------------------------------------
1 | module Types.Room.ModalState exposing (BottomModalType(..), ModalState(..))
2 |
3 | import Data.WinningHand as WinningHand exposing (WinningHand)
4 |
5 |
6 | type ModalState
7 | = Closed
8 | | JoinModalOpen
9 | | BankModalOpen
10 | | BottomModalOpen BottomModalType
11 | | RaiseModalOpen
12 | | WinningHandModal WinningHand
13 |
14 |
15 | type BottomModalType
16 | = Actions
17 | | Account
18 | | Chat
19 | | MobileMenu
20 |
--------------------------------------------------------------------------------
/assets/css/pagination.css:
--------------------------------------------------------------------------------
1 | .pagination-list {
2 | display: flex;
3 | justify-content: space-evenly;
4 | }
5 |
6 | .pagination-list > * {
7 | padding: 1rem;
8 | font-size: 1.5rem;
9 | font-weight: 600;
10 | cursor: pointer;
11 | }
12 |
13 | .pagination li {
14 | height: inherit;
15 | }
16 |
17 | .pagination-list-item {
18 | color: var(--teal);
19 | }
20 |
21 | .active-page {
22 | background-color: var(--light-blue);
23 | color: white;
24 | }
25 |
26 | .pagination li.disabled-page-icon a i {
27 | color: lightgray;
28 | }
--------------------------------------------------------------------------------
/assets/css/auth.css:
--------------------------------------------------------------------------------
1 | .auth-page {
2 | display: -webkit-box;
3 | display: -ms-flexbox;
4 | display: flex;
5 | -webkit-box-align: center;
6 | -ms-flex-align: center;
7 | align-items: center;
8 | -webkit-box-pack: center;
9 | -ms-flex-pack: center;
10 | justify-content: center;
11 | margin-top: 8em;
12 | }
13 |
14 | .auth-form {
15 | padding: 3em;
16 | max-width: 40em;
17 | margin-bottom: 8em;
18 | }
19 |
20 | .auth-btn {
21 | margin: 1rem;
22 | }
23 |
24 | .forgot-password-link {
25 | cursor: pointer;
26 | }
--------------------------------------------------------------------------------
/assets/elm/src/Types/Dropdowns.elm:
--------------------------------------------------------------------------------
1 | module Types.Dropdowns exposing (..)
2 |
3 |
4 | type OpenDropdown
5 | = AllClosed
6 | | NavBarDropdown
7 |
8 |
9 | type DropdownMsg
10 | = Toggle OpenDropdown
11 | | NavItemPicked DropdownNavbarLink
12 | | Blur
13 |
14 |
15 | type DropdownItem
16 | = None
17 | | AnyItem String
18 |
19 |
20 | type DropdownNavbarLink
21 | = Logout
22 | | Login
23 | | Register
24 | | Room -- TODO: Change this back to `Rooms` when the route is ready to go.
25 | | Rooms
26 | | Profile
27 |
--------------------------------------------------------------------------------
/compile:
--------------------------------------------------------------------------------
1 | echo "Running elm make..."
2 |
3 | elm make elm/src/Main.elm --output js/main.js --yes
4 |
5 | echo "Now building static assets..."
6 | echo "PWD: "
7 | echo `pwd`
8 |
9 |
10 | brunch build --production --debug
11 |
12 | echo "Brunch build completed"
13 |
14 | echo "Changing directories to phoenix_dir:"
15 | echo $phoenix_dir
16 | echo "Running mix phx.digest..."
17 |
18 | cd $phoenix_dir
19 |
20 | mix "${phoenix_ex}.digest"
21 |
22 | if mix help "${phoenix_ex}.digest.clean" 1>/dev/null 2>&1; then
23 | mix "${phoenix_ex}.digest.clean"
24 | fi
--------------------------------------------------------------------------------
/assets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {},
3 | "license": "MIT",
4 | "scripts": {
5 | "deploy": "brunch build --production",
6 | "watch": "brunch watch --stdin"
7 | },
8 | "dependencies": {
9 | "phoenix": "file:../deps/phoenix",
10 | "phoenix_html": "file:../deps/phoenix_html",
11 | "elm": "0.18"
12 | },
13 | "devDependencies": {
14 | "babel-brunch": "6.1.1",
15 | "brunch": "2.10.9",
16 | "clean-css-brunch": "2.10.0",
17 | "elm-brunch": "^0.10.0",
18 | "gulp": "^3.9.1",
19 | "uglify-js-brunch": "2.10.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/WinningHand.elm:
--------------------------------------------------------------------------------
1 | module Data.WinningHand exposing (..)
2 |
3 | import Data.Card as Card exposing (Card)
4 | import Json.Decode as Decode exposing (Decoder)
5 | import Json.Decode.Pipeline exposing (decode, required)
6 |
7 |
8 | type alias WinningHand =
9 | { cards : List Card
10 | , winner : String
11 | , handType : String
12 | }
13 |
14 |
15 | decoder : Decoder WinningHand
16 | decoder =
17 | decode WinningHand
18 | |> required "cards" (Decode.list Card.decoder)
19 | |> required "winner" Decode.string
20 | |> required "type" Decode.string
21 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Facebook.elm:
--------------------------------------------------------------------------------
1 | module Data.Facebook exposing (..)
2 |
3 | import Json.Decode as Decode exposing (Decoder)
4 | import Json.Decode.Pipeline exposing (decode, required)
5 | import Json.Encode as Encode
6 |
7 |
8 | type alias FBData =
9 | { name : String, id : String }
10 |
11 |
12 | decoder : Decoder FBData
13 | decoder =
14 | decode FBData
15 | |> required "name" Decode.string
16 | |> required "id" Decode.string
17 |
18 |
19 | encode : FBData -> Encode.Value
20 | encode data =
21 | Encode.object [ ( "name", Encode.string data.name ), ( "facebook_id", Encode.string data.id ) ]
22 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Session.elm:
--------------------------------------------------------------------------------
1 | module Data.Session exposing (Session, attempt)
2 |
3 | import Data.AuthToken exposing (AuthToken)
4 | import Data.Player as Player exposing (Player)
5 |
6 |
7 | type alias Session =
8 | { player : Maybe Player }
9 |
10 |
11 | attempt : String -> (AuthToken -> Cmd msg) -> Session -> ( List String, Cmd msg )
12 | attempt attemptedAction toCmd session =
13 | case Maybe.map .token session.player of
14 | Nothing ->
15 | ( [ "You have been signed out. Please sign back in to " ++ attemptedAction ++ "." ], Cmd.none )
16 |
17 | Just token ->
18 | ( [], toCmd token )
19 |
--------------------------------------------------------------------------------
/assets/elm/src/Types/Page.elm:
--------------------------------------------------------------------------------
1 | module Types.Page exposing (..)
2 |
3 | import Page.ForgotPassword as ForgotPassword
4 | import Page.Home as Home
5 | import Page.Login as Login
6 | import Page.Profile as Profile
7 | import Page.Register as Register
8 | import Page.ResetPassword as ResetPassword
9 | import Page.Room as Room
10 | import Page.Rooms as Rooms
11 |
12 |
13 | type Page
14 | = Blank
15 | | NotFound
16 | | Login Login.Model
17 | | Register Register.Model
18 | | Home Home.Model
19 | | Rooms Rooms.Model
20 | | Room Room.Model
21 | | Profile Profile.Model
22 | | ForgotPassword ForgotPassword.Model
23 | | ResetPassword ResetPassword.Model
24 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.Router do
2 | use PokerExClientWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_flash
8 | plug :protect_from_forgery
9 | plug :put_secure_browser_headers
10 | end
11 |
12 | pipeline :api do
13 | plug :accepts, ["json"]
14 | end
15 |
16 | scope "/", PokerExClientWeb do
17 | pipe_through :browser # Use the default browser stack
18 |
19 | get "/*path", PageController, :index
20 | end
21 |
22 | # Other scopes may use custom stacks.
23 | # scope "/api", PokerExClientWeb do
24 | # pipe_through :api
25 | # end
26 | end
27 |
--------------------------------------------------------------------------------
/test/poker_ex_client_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.ErrorViewTest do
2 | use PokerExClientWeb.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(PokerExClientWeb.ErrorView, "404.html", []) ==
9 | "Page not found"
10 | end
11 |
12 | test "render 500.html" do
13 | assert render_to_string(PokerExClientWeb.ErrorView, "500.html", []) ==
14 | "Internal server error"
15 | end
16 |
17 | test "render any other" do
18 | assert render_to_string(PokerExClientWeb.ErrorView, "505.html", []) ==
19 | "Internal server error"
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/assets/css/home.css:
--------------------------------------------------------------------------------
1 | .hero {
2 | background: url("/images/poker.jpg") no-repeat center center fixed;
3 | min-height: 45vh;
4 | -webkit-box-pack: center;
5 | -ms-flex-pack: center;
6 | justify-content: center;
7 | -webkit-box-orient: vertical;
8 | -webkit-box-direction: normal;
9 | -ms-flex-direction: column;
10 | flex-direction: column;
11 | }
12 |
13 | .welcome {
14 | color: #84ffff;
15 | text-shadow: 2px 2px 2em #004d40;
16 | }
17 |
18 | @media (max-width: 993px) {
19 | .landing-item:first-of-type {
20 | margin-top: 1em;
21 | }
22 | }
23 |
24 | @media (min-width: 993px) {
25 | .landing {
26 | height: 16rem;
27 | }
28 |
29 | .landing-item {
30 | margin-top: 0.5em;
31 | height: 100%;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PokerExClient
2 |
3 | To start your Phoenix server:
4 |
5 | * Install dependencies with `mix deps.get`
6 | * Install Node.js dependencies with `cd assets && npm install`
7 | * Start Phoenix endpoint with `mix phx.server`
8 |
9 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
10 |
11 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment).
12 |
13 | ## Learn more
14 |
15 | * Official website: http://www.phoenixframework.org/
16 | * Guides: http://phoenixframework.org/docs/overview
17 | * Docs: https://hexdocs.pm/phoenix
18 | * Mailing list: http://groups.google.com/group/phoenix-talk
19 | * Source: https://github.com/phoenixframework/phoenix
20 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Chat.elm:
--------------------------------------------------------------------------------
1 | module Data.Chat exposing (..)
2 |
3 | import Data.Player exposing (Player, encodeUsername)
4 | import Json.Decode as Decode exposing (Decoder)
5 | import Json.Decode.Pipeline as Pipeline exposing (decode, required)
6 | import Json.Encode as Encode exposing (Value)
7 |
8 |
9 | type alias Chat =
10 | { playerName : String
11 | , message : String
12 | }
13 |
14 |
15 | decoder : Decoder Chat
16 | decoder =
17 | decode Chat
18 | |> required "player" Decode.string
19 | |> required "message" Decode.string
20 |
21 |
22 | encode : Player -> String -> Value
23 | encode player message =
24 | Encode.object
25 | [ ( "player", encodeUsername player.username )
26 | , ( "message", Encode.string message )
27 | ]
28 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import PokerExClientWeb.Gettext
9 |
10 | # Simple translation
11 | gettext "Here is the string to translate"
12 |
13 | # Plural translation
14 | ngettext "Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3
17 |
18 | # Domain-based translation
19 | dgettext "errors", "Here is the error message to translate"
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :poker_ex_client
24 | end
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # App artifacts
2 | /_build
3 | /db
4 | /deps
5 | /*.ez
6 |
7 | # Generated on crash by the VM
8 | erl_crash.dump
9 |
10 | # Generated on crash by NPM
11 | npm-debug.log
12 |
13 | # Static artifacts
14 | /node_modules
15 | /package-lock.json
16 | /assets/node_modules
17 | /assets/elm/elm-stuff
18 | /assets/elm-stuff
19 | /assets/js/main.js
20 | /elm-stuff
21 | # Since we are building assets from assets/,
22 | # we ignore priv/static. You may want to comment
23 | # this depending on your deployment strategy.
24 | /priv/static/
25 |
26 | # Files matching config/*.secret.exs pattern contain sensitive
27 | # data and you should not commit them into version control.
28 | #
29 | # Alternatively, you may comment the line below and commit the
30 | # secrets files as long as you replace their contents by environment
31 | # variables.
32 | /config/*.secret.exs
33 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # Configures the endpoint
9 | config :poker_ex_client, PokerExClientWeb.Endpoint,
10 | url: [host: "localhost"],
11 | secret_key_base: System.get_env("SECRET_KEY_BASE"),
12 | render_errors: [view: PokerExClientWeb.ErrorView, accepts: ~w(html json)],
13 | pubsub: [name: PokerExClient.PubSub,
14 | adapter: Phoenix.PubSub.PG2]
15 |
16 | # Configures Elixir's Logger
17 | config :logger, :console,
18 | format: "$time $metadata[$level] $message\n",
19 | metadata: [:request_id]
20 |
21 | # Import environment specific config. This must remain at the bottom
22 | # of this file so it overrides the configuration defined above.
23 | import_config "#{Mix.env}.exs"
24 |
--------------------------------------------------------------------------------
/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | import other functionality to make it easier
8 | to build common datastructures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | # The default endpoint for testing
24 | @endpoint PokerExClientWeb.Endpoint
25 | end
26 | end
27 |
28 |
29 | setup _tags do
30 | :ok
31 | end
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/Errored.elm:
--------------------------------------------------------------------------------
1 | module Page.Errored exposing (..)
2 |
3 | import Data.Session as Session exposing (Session)
4 | import Html exposing (Html, div, h1, main_, p, text)
5 | import Html.Attributes exposing (class, id)
6 | import Views.Helpers as Helpers exposing (ActivePage)
7 |
8 |
9 | type PageLoadError
10 | = PageLoadError Model
11 |
12 |
13 | type alias Model =
14 | { activePage : ActivePage
15 | , errorMessage : String
16 | }
17 |
18 |
19 | pageLoadError : ActivePage -> String -> PageLoadError
20 | pageLoadError activePage errorMessage =
21 | PageLoadError { activePage = activePage, errorMessage = errorMessage }
22 |
23 |
24 | view : Session -> PageLoadError -> Html msg
25 | view session (PageLoadError model) =
26 | main_ [ id "content", class "container" ]
27 | [ h1 [] [ text "Error Loading Page" ]
28 | , div [ class "row" ]
29 | [ p [] [ text model.errorMessage ] ]
30 | ]
31 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/AuthToken.elm:
--------------------------------------------------------------------------------
1 | module Data.AuthToken exposing (..)
2 |
3 | import HttpBuilder exposing (RequestBuilder, withHeader)
4 | import Json.Decode as Decode exposing (Decoder)
5 | import Json.Encode as Encode exposing (Value)
6 |
7 |
8 | type AuthToken
9 | = AuthToken String
10 |
11 |
12 | encode : AuthToken -> Value
13 | encode (AuthToken token) =
14 | Encode.string token
15 |
16 |
17 | decoder : Decoder AuthToken
18 | decoder =
19 | Decode.string
20 | |> Decode.map AuthToken
21 |
22 |
23 | withAuthorization : Maybe AuthToken -> RequestBuilder a -> RequestBuilder a
24 | withAuthorization maybeToken builder =
25 | case maybeToken of
26 | Just (AuthToken token) ->
27 | builder
28 | |> withHeader "Bearer" ("Token" ++ token)
29 |
30 | Nothing ->
31 | builder
32 |
33 |
34 | authTokenToString : AuthToken -> String
35 | authTokenToString (AuthToken token) =
36 | token
37 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/PasswordReset.elm:
--------------------------------------------------------------------------------
1 | module Data.PasswordReset exposing (..)
2 |
3 | import Data.Player as Player exposing (Player)
4 | import Json.Decode as Decode exposing (Decoder)
5 | import Json.Decode.Pipeline exposing (decode, required)
6 |
7 |
8 | type alias PasswordReset =
9 | { message : String, type_ : RequestResult }
10 |
11 |
12 | type RequestResult
13 | = Success
14 | | Error
15 |
16 |
17 | decoder : Decoder PasswordReset
18 | decoder =
19 | decode PasswordReset
20 | |> required "message" Decode.string
21 | |> required "type" resultDecoder
22 |
23 |
24 | resultDecoder : Decoder RequestResult
25 | resultDecoder =
26 | Decode.string
27 | |> Decode.andThen (\str -> Decode.succeed (stringToResult str))
28 |
29 |
30 | stringToResult : String -> RequestResult
31 | stringToResult string =
32 | case string of
33 | "success" ->
34 | Success
35 |
36 | _ ->
37 | Error
38 |
--------------------------------------------------------------------------------
/assets/css/app.css:
--------------------------------------------------------------------------------
1 | /* This file is for your main application css. */
2 | :root {
3 | --teal: #009688;
4 | --bright-red: rgb(244, 67, 54);
5 | --green-accent: #00c853;
6 | --mobile: 600px;
7 | --light-blue: #039be5;
8 | --material-pink: #ee6e73;
9 | }
10 |
11 | body {
12 | min-height: 100vh;
13 | font-family: Helvetica, Arial, sans-serif;
14 | }
15 |
16 | @media (max-width: 600px) {
17 | body {
18 | width: 100vw;
19 | }
20 | }
21 |
22 | main {
23 | min-height: inherit;
24 | }
25 |
26 | .rounded {
27 | border-radius: 1.5em;
28 | }
29 |
30 | .large-text {
31 | font-size: xx-large;
32 | }
33 |
34 | .medium-text {
35 | font-size: 1.05rem;
36 | }
37 |
38 | .pop-text {
39 | font-size: 1.25rem;
40 | }
41 |
42 | .center-align {
43 | text-align: center;
44 | }
45 |
46 | @media (max-width: 560px) {
47 | .center-align-small {
48 | text-align: center;
49 | }
50 |
51 | .pad-left-small {
52 | padding-left: 10em;
53 | }
54 |
55 | .margin-bottom-small {
56 | margin-bottom: 2em;
57 | }
58 | }
--------------------------------------------------------------------------------
/assets/elm/src/Views/Account.elm:
--------------------------------------------------------------------------------
1 | module Views.Account exposing (view)
2 |
3 | import Data.Player as Player exposing (Player)
4 | import Html as Html exposing (..)
5 | import Html.Attributes as Attrs exposing (..)
6 | import Html.Events as Events exposing (onClick)
7 | import Route
8 |
9 |
10 | view : Player -> Html msg
11 | view player =
12 | div [ class "account-container" ]
13 | [ span [ class "modal-header red-text" ] [ text "Your Account" ]
14 | , div [ class "row account-info" ]
15 | [ span [ class "account-info-item" ] [ h5 [] [ text <| "Username: " ++ Player.usernameToString player.username ] ]
16 | , span [ class "account-info-item" ] [ h5 [] [ text <| "Chips: " ++ toString player.chips ] ]
17 | ]
18 | , div [ class "row account-info" ]
19 | [ span [ class "account-info-item" ] [ h5 [] [ text <| "Email: " ++ player.email ] ]
20 | , span [ class "account-info-item" ] [ a [ Route.href Route.Home ] [ text "Go to your account" ] ]
21 | ]
22 | ]
23 |
--------------------------------------------------------------------------------
/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common datastructures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with connections
21 | use Phoenix.ConnTest
22 | import PokerExClientWeb.Router.Helpers
23 |
24 | # The default endpoint for testing
25 | @endpoint PokerExClientWeb.Endpoint
26 | end
27 | end
28 |
29 |
30 | setup _tags do
31 | {:ok, conn: Phoenix.ConnTest.build_conn()}
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/assets/elm/src/Util.elm:
--------------------------------------------------------------------------------
1 | module Util exposing ((=>), appendErrors, onClickStopPropagation, pair, viewIf)
2 |
3 | import Html exposing (Attribute, Html)
4 | import Html.Events exposing (Options, defaultOptions, onWithOptions)
5 | import Json.Decode as Decode
6 |
7 |
8 | (=>) : a -> b -> ( a, b )
9 | (=>) =
10 | (,)
11 | infixl 0 =>
12 |
13 |
14 | pair : a -> b -> ( a, b )
15 | pair first second =
16 | first => second
17 |
18 |
19 | viewIf : Bool -> Html msg -> Html msg
20 | viewIf condition content =
21 | if condition then
22 | content
23 | else
24 | Html.text ""
25 |
26 |
27 | onClickStopPropagation : msg -> Attribute msg
28 | onClickStopPropagation msg =
29 | onWithOptions "click" noBubble (Decode.succeed msg)
30 |
31 |
32 | noBubble : Options
33 | noBubble =
34 | { stopPropagation = True
35 | , preventDefault = True
36 | }
37 |
38 |
39 | appendErrors : { model | errors : List error } -> List error -> { model | errors : List error }
40 | appendErrors model errors =
41 | { model | errors = model.errors ++ errors }
42 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/RoomPage.elm:
--------------------------------------------------------------------------------
1 | module Data.RoomPage exposing (RoomPage)
2 |
3 | import Data.Player as Player exposing (Player)
4 | import Data.Room as Room exposing (Room)
5 | import Phoenix.Channel as Channel exposing (Channel)
6 | import Types.Room.Messages as Messages exposing (RoomMsg)
7 | import Types.Room.ModalState as ModalState exposing (ModalState)
8 | import Views.Chat as Chat exposing (Chat)
9 |
10 |
11 | type alias RoomPage =
12 | { room : String
13 | , roomModel : Room
14 | , roomType : String
15 | , roomMessages : List String
16 | , players : List Player
17 | , player : Player
18 | , joinValue : String
19 | , joined : Bool
20 | , channelSubscriptions : List (Channel RoomMsg)
21 | , modalRendered : ModalState
22 | , errorMessages : List String
23 | , raiseAmount : Int
24 | , raiseInterval : Int
25 | , chipsAvailable : Int
26 | , chipsAvailableForJoin : Int
27 | , addAmount : Int
28 | , chat : Chat
29 | , currentChatMsg : String
30 | , socketUrl : String
31 | , apiUrl : String
32 | }
33 |
--------------------------------------------------------------------------------
/lib/poker_ex_client/application.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClient.Application do
2 | use Application
3 |
4 | # See https://hexdocs.pm/elixir/Application.html
5 | # for more information on OTP Applications
6 | def start(_type, _args) do
7 | import Supervisor.Spec
8 |
9 | # Define workers and child supervisors to be supervised
10 | children = [
11 | # Start the endpoint when the application starts
12 | supervisor(PokerExClientWeb.Endpoint, []),
13 | # Start your own worker by calling: PokerExClient.Worker.start_link(arg1, arg2, arg3)
14 | # worker(PokerExClient.Worker, [arg1, arg2, arg3]),
15 | ]
16 |
17 | # See https://hexdocs.pm/elixir/Supervisor.html
18 | # for other strategies and supported options
19 | opts = [strategy: :one_for_one, name: PokerExClient.Supervisor]
20 | Supervisor.start_link(children, opts)
21 | end
22 |
23 | # Tell Phoenix to update the endpoint configuration
24 | # whenever the application is updated.
25 | def config_change(changed, _new, removed) do
26 | PokerExClientWeb.Endpoint.config_change(changed, removed)
27 | :ok
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/assets/css/chat.css:
--------------------------------------------------------------------------------
1 | .message-container {
2 | display: flex;
3 | justify-content: baseline;
4 | align-items: center;
5 | font-size: 1.25rem;
6 | }
7 |
8 | .chat-container {
9 | height: 30vh;
10 | overflow-y: scroll;
11 | }
12 |
13 | .chat-player-name {
14 | flex: 1;
15 | font-weight: 600;
16 | }
17 |
18 | .chat-message-item {
19 | text-align: left;
20 | flex: 6;
21 | }
22 |
23 | .chat-input-container {
24 | display: flex;
25 | justify-content: baseline;
26 | align-items: center;
27 | padding-right: 4rem;
28 | padding-left: 4rem;
29 | }
30 |
31 | .chat-input {
32 | flex: 3;
33 | }
34 |
35 | .chat-submit-btn {
36 | flex: 0.2;
37 | transform: translateY(-1rem);
38 | }
39 |
40 | .chat-submit-mobile {
41 | display: none;
42 | }
43 |
44 | .close-chat {
45 | position: absolute;
46 | top: 5px;
47 | right: 5px;
48 | }
49 |
50 | @media (max-width: 600px) {
51 | .chat-input-container {
52 | padding-right: 2rem;
53 | }
54 |
55 | .chat-container {
56 | height: 25vh;
57 | }
58 |
59 | .chat-submit-btn {
60 | display: none;
61 | }
62 |
63 | .chat-submit-mobile {
64 | display: initial;
65 | flex: 0.5;
66 | margin-left: 1rem;
67 | translateY: (-1rem);
68 | }
69 | }
--------------------------------------------------------------------------------
/assets/css/raise-modal.css:
--------------------------------------------------------------------------------
1 | .raise-buttons-container {
2 | display: -webkit-box;
3 | display: -ms-flexbox;
4 | display: flex;
5 | -webkit-box-orient: horizontal;
6 | -webkit-box-direction: normal;
7 | -ms-flex-direction: row;
8 | flex-direction: row;
9 | -webkit-box-pack: justify;
10 | -ms-flex-pack: justify;
11 | justify-content: space-between;
12 | margin-top: 1rem;
13 | }
14 |
15 | .raise-buttons-container:nth-child(1) {
16 | -webkit-box-flex: 1;
17 | -ms-flex: 1;
18 | flex: 1;
19 | }
20 |
21 | .raise-buttons-container:nth-child(2) {
22 | -webkit-box-flex: 1;
23 | -ms-flex: 1;
24 | flex: 1;
25 | }
26 |
27 | .raise-buttons-container:nth-child(3) {
28 | -webkit-box-flex: 1;
29 | -ms-flex: 1;
30 | flex: 1;
31 | }
32 |
33 | .raise-modal-header {
34 | text-align: center;
35 | }
36 |
37 | .raise-modal-close-row {
38 | display: -webkit-box;
39 | display: -ms-flexbox;
40 | display: flex;
41 | -webkit-box-pack: end;
42 | -ms-flex-pack: end;
43 | justify-content: flex-end;
44 | margin-top: 5rem;
45 | }
46 |
47 | @media (max-width: 400px) {
48 | .raise-modal-close-row {
49 | margin-top: 3rem;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | >
10 | >
11 | >
12 |
13 | PokerEx
14 | ">
15 |
16 | ">
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule PokerExClient.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :poker_ex_client,
7 | version: "0.5.0",
8 | elixir: "~> 1.4",
9 | elixirc_paths: elixirc_paths(Mix.env),
10 | compilers: [:phoenix, :gettext] ++ Mix.compilers,
11 | start_permanent: Mix.env == :prod,
12 | deps: deps()
13 | ]
14 | end
15 |
16 | # Configuration for the OTP application.
17 | #
18 | # Type `mix help compile.app` for more information.
19 | def application do
20 | [
21 | mod: {PokerExClient.Application, []},
22 | extra_applications: [:logger, :runtime_tools]
23 | ]
24 | end
25 |
26 | # Specifies which paths to compile per environment.
27 | defp elixirc_paths(:test), do: ["lib", "test/support"]
28 | defp elixirc_paths(_), do: ["lib"]
29 |
30 | # Specifies your project dependencies.
31 | #
32 | # Type `mix help deps` for examples and options.
33 | defp deps do
34 | [
35 | {:phoenix, "~> 1.3.0"},
36 | {:phoenix_pubsub, "~> 1.0"},
37 | {:phoenix_html, "~> 2.10"},
38 | {:phoenix_live_reload, "~> 1.0", only: :dev},
39 | {:gettext, "~> 0.11"},
40 | {:cowboy, "~> 1.0"}
41 | ]
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/assets/css/rooms.css:
--------------------------------------------------------------------------------
1 | .lobby-container {
2 | padding: 4rem;
3 | }
4 |
5 | @media (max-width: 600px) {
6 | .lobby-container {
7 | padding: 0;
8 | }
9 | }
10 |
11 | .lobby-title {
12 | text-align: center;
13 | }
14 |
15 | .rooms-list {
16 | width: 100%;
17 | }
18 |
19 | .room-list-title {
20 | font-size: 2rem;
21 | text-transform: capitalize;
22 | font-weight: 400;
23 | flex: 1.5;
24 | margin: 0.75rem;
25 | display: flex;
26 | align-items: center;
27 | padding-left: 2rem;
28 | }
29 |
30 | @media (max-width: 600px) {
31 | .room-list-title {
32 | font-size: 1.25rem;
33 | font-weight: 600;
34 | margin: 0 0.75rem 0 0;
35 | padding-left: 0.75rem;
36 | }
37 | }
38 |
39 | .room-list-status {
40 | font-size: 1.25rem;
41 | margin: 0.75rem;
42 | flex: 1.5;
43 | }
44 |
45 | .room-list-status:nth-child {
46 | padding: 0.75rem;
47 | }
48 |
49 | @media (max-width: 600px) {
50 | .room-list-status {
51 | font-size: 1rem;
52 | }
53 | }
54 |
55 | .room-info-item {
56 | display: flex;
57 | justify-content: space-between;
58 | margin: 0.75rem 2rem 0.75rem 2rem;
59 | }
60 |
61 | .room-info-item .room-list-title {
62 | margin: 0.75rem 0 0.75rem 2rem;
63 | }
64 |
65 | .room-info-item .status {
66 | margin: 0.75rem 2rem 0.75rem 0;
67 | }
--------------------------------------------------------------------------------
/assets/elm/src/Data/Profile.elm:
--------------------------------------------------------------------------------
1 | module Data.Profile exposing (..)
2 |
3 | import Json.Decode as Decode exposing (Decoder)
4 | import Json.Decode.Pipeline exposing (decode, hardcoded, optional, required)
5 | import Json.Encode as Encode
6 |
7 |
8 | type alias Profile =
9 | { errors : List Error
10 | , id : Int
11 | , username : String
12 | , email : String
13 | , chips : Int
14 | , blurb : String
15 | , isNewProfile : Bool
16 | }
17 |
18 |
19 | type alias Error =
20 | ( Field, String )
21 |
22 |
23 | type Field
24 | = Chips
25 | | E_mail
26 | | Blurb
27 | | Server
28 |
29 |
30 | initialProfile : Profile
31 | initialProfile =
32 | { errors = []
33 | , id = 0
34 | , username = ""
35 | , email = ""
36 | , chips = 1000
37 | , blurb = ""
38 | , isNewProfile = True
39 | }
40 |
41 |
42 | decoder : Decoder Profile
43 | decoder =
44 | decode Profile
45 | |> hardcoded []
46 | -- Come back to this later
47 | |> required "id" Decode.int
48 | |> required "name" Decode.string
49 | |> required "email" Decode.string
50 | |> required "chips" Decode.int
51 | |> required "blurb" Decode.string
52 | |> hardcoded False
53 |
--------------------------------------------------------------------------------
/assets/css/toasts.css:
--------------------------------------------------------------------------------
1 | .messages-container {
2 | position: fixed;
3 | z-index: 100;
4 | min-width: 25%;
5 | right: 0%;
6 | top: 15%;
7 | display: -webkit-box;
8 | display: -ms-flexbox;
9 | display: flex;
10 | -webkit-box-orient: vertical;
11 | -webkit-box-direction: normal;
12 | -ms-flex-direction: column;
13 | flex-direction: column;
14 | -webkit-box-align: baseline;
15 | -ms-flex-align: baseline;
16 | align-items: baseline;
17 | }
18 |
19 | .message {
20 | color: white;
21 | font-size: 1.25rem;
22 | margin: 0.5rem 0;
23 | padding: 1rem;
24 | -webkit-box-shadow: 0 0 1rem 0 black;
25 | box-shadow: 0 0 1rem 0 black;
26 | -webkit-animation: slideInMessage 0.75s ease-in-out;
27 | animation: slideInMessage 0.75s ease-in-out;
28 | }
29 |
30 | .error-message {
31 | background-color: var(--bright-red);
32 | }
33 |
34 | .success-message {
35 | background-color: var(--green-accent);
36 | }
37 |
38 | @media (max-width: 600px) {
39 | .message-container {
40 | min-width: 100%;
41 | max-width: 100%;
42 | bottom: 0%;
43 | position: fixed;
44 | }
45 |
46 | .message {
47 | margin: 0;
48 | font-size: 1.5rem;
49 | text-align: center;
50 | bottom: 0%;
51 | width: 100%;
52 | position: fixed;
53 | }
54 | }
--------------------------------------------------------------------------------
/assets/elm/src/Views/Form.elm:
--------------------------------------------------------------------------------
1 | module Views.Form exposing (input, password, textarea, viewErrors)
2 |
3 | import Html exposing (Attribute, Html, fieldset, li, text, ul)
4 | import Html.Attributes exposing (class, type_)
5 |
6 |
7 | password : List (Attribute msg) -> List (Html msg) -> Html msg
8 | password attrs =
9 | control Html.input ([ type_ "password" ] ++ attrs)
10 |
11 |
12 | input : List (Attribute msg) -> List (Html msg) -> Html msg
13 | input attrs =
14 | control Html.input ([ type_ "text" ] ++ attrs)
15 |
16 |
17 | textarea : List (Attribute msg) -> List (Html msg) -> Html msg
18 | textarea =
19 | control Html.textarea
20 |
21 |
22 | viewErrors : List ( a, String ) -> Html msg
23 | viewErrors errors =
24 | case errors of
25 | [] ->
26 | text ""
27 |
28 | _ ->
29 | errors
30 | |> List.map (\( _, error ) -> li [] [ text error ])
31 | |> ul [ class "card-panel red darken-1 white-text" ]
32 |
33 |
34 |
35 | -- INTERNAL --
36 |
37 |
38 | control : (List (Attribute msg) -> List (Html msg) -> Html msg) -> List (Attribute msg) -> List (Html msg) -> Html msg
39 | control element attributes children =
40 | fieldset [ class "form-group" ]
41 | [ element (class "form-control" :: attributes) children ]
42 |
--------------------------------------------------------------------------------
/assets/css/modal.css:
--------------------------------------------------------------------------------
1 | .modal-backdrop {
2 | position: fixed;
3 | top: 0;
4 | bottom: 0;
5 | left: 0;
6 | right: 0;
7 | background-color: rgba(0, 0, 0, 0.3);
8 | }
9 |
10 | .modal-content {
11 | position: fixed;
12 | top: 20%;
13 | bottom: 20%;
14 | right: 20%;
15 | left: 20%;
16 | padding: 2rem 3rem;
17 | overflow: auto;
18 | }
19 |
20 | @media (max-width: 600px) {
21 | .modal-content {
22 | right: 0;
23 | left: 0;
24 | }
25 | }
26 |
27 | .bottom-modal {
28 | position: fixed;
29 | width: 100vw;
30 | height: 25vh;
31 | bottom: 0;
32 | text-align: center;
33 | }
34 |
35 | .modal-header {
36 | font-weight: bold;
37 | font-size: 1.5rem;
38 | text-decoration: underline;
39 | }
40 |
41 | .close-modal {
42 | cursor: pointer;
43 | }
44 |
45 | .actions-container .close-modal, .bank-container .close-modal {
46 | position: absolute;
47 | right: 0;
48 | bottom: 0;
49 | margin: 0 1rem 1rem 0;
50 | }
51 |
52 | .close-mobile-menu {
53 | position: absolute;
54 | top: 0.25rem;
55 | left: 1rem;
56 | }
57 |
58 | .join-modal {
59 | text-align: center;
60 | }
61 |
62 | .winning-hand-modal {
63 | animation: fade-in-winning-hand 1s ease-in;
64 | }
65 |
66 | @keyframes fade-in-winning-hand {
67 | 0% { -webkit-transform: scale(0, 0); transform: scale(0, 0); }
68 | 100% { -webkit-transform: scale(1, 1); transform: scale(1, 1); }
69 | }
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", PokerExClientWeb.RoomChannel
6 |
7 | ## Transports
8 | transport :websocket, Phoenix.Transports.WebSocket,
9 | timeout: 45_000
10 | # transport :longpoll, Phoenix.Transports.LongPoll
11 |
12 | # Socket params are passed from the client and can
13 | # be used to verify and authenticate a user. After
14 | # verification, you can put default assigns into
15 | # the socket that will be set for all channels, ie
16 | #
17 | # {:ok, assign(socket, :user_id, verified_user_id)}
18 | #
19 | # To deny connection, return `:error`.
20 | #
21 | # See `Phoenix.Token` documentation for examples in
22 | # performing token verification on connect.
23 | def connect(_params, socket) do
24 | {:ok, socket}
25 | end
26 |
27 | # Socket id's are topics that allow you to identify all sockets for a given user:
28 | #
29 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
30 | #
31 | # Would allow you to broadcast a "disconnect" event and terminate
32 | # all active sockets and channels for a given user:
33 | #
34 | # PokerExClientWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
35 | #
36 | # Returning `nil` makes this socket anonymous.
37 | def id(_socket), do: nil
38 | end
39 |
--------------------------------------------------------------------------------
/assets/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "helpful summary of your project, less than 80 characters",
4 | "repository": "https://github.com/user/project.git",
5 | "license": "BSD3",
6 | "source-directories": [
7 | ".",
8 | "./elm/src",
9 | "./elm/vendor"
10 | ],
11 | "exposed-modules": [],
12 | "dependencies": {
13 | "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0",
14 | "debois/elm-mdl": "8.1.0 <= v < 9.0.0",
15 | "elm-community/json-extra": "2.1.0 <= v < 3.0.0",
16 | "elm-lang/core": "5.1.1 <= v < 6.0.0",
17 | "elm-lang/html": "2.0.0 <= v < 3.0.0",
18 | "elm-lang/http": "1.0.0 <= v < 2.0.0",
19 | "elm-lang/mouse": "1.0.0 <= v < 2.0.0",
20 | "elm-lang/dom": "1.0.0 <= v < 2.0.0",
21 | "elm-lang/navigation": "2.1.0 <= v < 3.0.0",
22 | "elm-lang/websocket": "1.0.2 <= v < 2.0.0",
23 | "evancz/url-parser": "2.0.1 <= v < 3.0.0",
24 | "krisajenkins/remotedata": "4.3.0 <= v < 5.0.0",
25 | "lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0",
26 | "mgold/elm-date-format": "1.3.0 <= v < 2.0.0",
27 | "rgrempel/elm-http-decorators": "2.0.0 <= v < 3.0.0",
28 | "rtfeldman/elm-validate": "1.1.3 <= v < 2.0.0",
29 | "rtfeldman/selectlist": "1.0.0 <= v < 2.0.0",
30 | "simonh1000/elm-jwt": "5.0.0 <= v < 6.0.0"
31 | },
32 | "elm-version": "0.18.0 <= v < 0.19.0"
33 | }
34 |
--------------------------------------------------------------------------------
/assets/elm/src/Widgets/Toast.elm:
--------------------------------------------------------------------------------
1 | module Widgets.Toast exposing (..)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (class)
5 |
6 |
7 | type alias Toast r =
8 | { r | errors : List String, messages : List String }
9 |
10 |
11 | type ToastMessage
12 | = Message String
13 | | Error String
14 |
15 |
16 | viewMessages : Toast r -> Html msg
17 | viewMessages toastData =
18 | let
19 | errorMessages =
20 | case toastData.errors of
21 | [] ->
22 | []
23 |
24 | _ ->
25 | List.map (\msg -> Error msg) toastData.errors
26 |
27 | messages =
28 | case toastData.messages of
29 | [] ->
30 | []
31 |
32 | _ ->
33 | List.map (\msg -> Message msg) toastData.messages
34 |
35 | allMessages =
36 | messages ++ errorMessages
37 | in
38 | case allMessages of
39 | [] ->
40 | text ""
41 |
42 | _ ->
43 | div [ class "messages-container" ] <|
44 | List.map viewMessage allMessages
45 |
46 |
47 | viewMessage : ToastMessage -> Html msg
48 | viewMessage toastMessage =
49 | case toastMessage of
50 | Message message ->
51 | div [ class "message success-message" ] [ text message ]
52 |
53 | Error message ->
54 | div [ class "message error-message" ] [ text message ]
55 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | Enum.map(Keyword.get_values(form.errors, field), fn (error) ->
13 | content_tag :span, translate_error(error), class: "help-block"
14 | end)
15 | end
16 |
17 | @doc """
18 | Translates an error message using gettext.
19 | """
20 | def translate_error({msg, opts}) do
21 | # Because error messages were defined within Ecto, we must
22 | # call the Gettext module passing our Gettext backend. We
23 | # also use the "errors" domain as translations are placed
24 | # in the errors.po file.
25 | # Ecto will pass the :count keyword if the error message is
26 | # meant to be pluralized.
27 | # On your own code and templates, depending on whether you
28 | # need the message to be pluralized or not, this could be
29 | # written simply as:
30 | #
31 | # dngettext "errors", "1 file", "%{count} files", count
32 | # dgettext "errors", "is invalid"
33 | #
34 | if count = opts[:count] do
35 | Gettext.dngettext(PokerExClientWeb.Gettext, "errors", msg, msg, count, opts)
36 | else
37 | Gettext.dgettext(PokerExClientWeb.Gettext, "errors", msg, opts)
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/Room/PushMessages.elm:
--------------------------------------------------------------------------------
1 | module Page.Room.PushMessages exposing (..)
2 |
3 | import Json.Decode as Decode exposing (Value)
4 | import Json.Encode as Encode
5 | import Phoenix
6 | import Phoenix.Push as Push
7 | import Types.Room.Messages as Messages exposing (..)
8 |
9 |
10 | type alias Msg =
11 | RoomMsg
12 |
13 |
14 | actionPush : String -> String -> Value -> String -> Cmd Msg
15 | actionPush room actionString value socketUrl =
16 | let
17 | push =
18 | Push.init ("games:" ++ room) actionString
19 | |> Push.withPayload value
20 | in
21 | Phoenix.push socketUrl push
22 |
23 |
24 | playerInfoPush : String -> String -> String -> Cmd Msg
25 | playerInfoPush username msgToChannel socketUrl =
26 | let
27 | push =
28 | Push.init ("players:" ++ username) msgToChannel
29 | |> Push.withPayload (Encode.object [ ( "player", Encode.string username ) ])
30 | in
31 | Phoenix.push socketUrl push
32 |
33 | rejoinPush : String -> String -> String -> String -> String -> Cmd Msg
34 | rejoinPush roomId username actionString amountString socketUrl =
35 | case String.toInt amountString of
36 | Ok joinAmount ->
37 | Phoenix.push socketUrl (Push.init ("games:" ++ roomId) actionString
38 | |> Push.withPayload (Encode.object [("player", Encode.string username),
39 | ("amount", Encode.int joinAmount)]))
40 | Err _ -> Cmd.none
--------------------------------------------------------------------------------
/assets/elm/src/Widgets/Modal.elm:
--------------------------------------------------------------------------------
1 | module Widgets.Modal exposing (..)
2 |
3 | import Data.Player as Player exposing (Player)
4 | import Html exposing (..)
5 | import Html.Attributes exposing (class, id, style)
6 | import Html.Events exposing (onClick)
7 |
8 |
9 | type alias Config msg =
10 | { classes : List String
11 | , contentHtml : List (Html msg)
12 | , styles : Maybe (List ( String, String ))
13 | }
14 |
15 |
16 | view : Config msg -> Html msg
17 | view config =
18 | let
19 | classes =
20 | List.map (\str -> str ++ " ") config.classes
21 | |> String.concat
22 | |> (++) "modal-content card-panel "
23 | |> String.trim
24 | in
25 | div [ class "modal-backdrop" ]
26 | [ div [ class classes, styles config ]
27 | config.contentHtml
28 | ]
29 |
30 |
31 | bottomModalView : Config msg -> Html msg
32 | bottomModalView config =
33 | let
34 | classes =
35 | List.map (\str -> str ++ " ") config.classes
36 | |> String.concat
37 | |> (++) "bottom-modal "
38 | |> String.trim
39 | in
40 | div [ class "modal-backdrop" ]
41 | [ div [ class classes, styles config ]
42 | config.contentHtml
43 | ]
44 |
45 |
46 | styles : Config msg -> Html.Attribute msg
47 | styles config =
48 | case config.styles of
49 | Nothing ->
50 | style []
51 |
52 | Just theStyles ->
53 | style theStyles
54 |
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Internal/Message.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Internal.Message exposing (..)
2 |
3 | import Json.Decode as JD exposing (Value)
4 | import Json.Encode as JE
5 | import Phoenix.Push as Push exposing (Push)
6 |
7 |
8 | type alias Message =
9 | { topic : String
10 | , event : String
11 | , payload : Value
12 | , ref : Maybe Int
13 | }
14 |
15 |
16 | type alias Topic =
17 | String
18 |
19 |
20 | type alias Event =
21 | String
22 |
23 |
24 | type alias Ref =
25 | Int
26 |
27 |
28 | init : Topic -> Event -> Message
29 | init topic event =
30 | Message topic event (JE.object []) Nothing
31 |
32 |
33 | payload : Value -> Message -> Message
34 | payload payload_ message =
35 | { message | payload = payload_ }
36 |
37 |
38 | ref : Ref -> Message -> Message
39 | ref ref_ message =
40 | { message | ref = Just ref_ }
41 |
42 |
43 | fromPush : Push msg -> Message
44 | fromPush push =
45 | init push.topic push.event
46 | |> payload push.payload
47 |
48 |
49 | encode : Message -> String
50 | encode { topic, event, payload, ref } =
51 | JE.object
52 | [ ( "topic", JE.string topic )
53 | , ( "event", JE.string event )
54 | , ( "ref", Maybe.map JE.int ref |> Maybe.withDefault JE.null )
55 | , ( "payload", payload )
56 | ]
57 | |> JE.encode 0
58 |
59 |
60 | decode : String -> Result String Message
61 | decode msg =
62 | let
63 | decoder =
64 | JD.map4 Message
65 | (JD.field "topic" JD.string)
66 | (JD.field "event" JD.string)
67 | (JD.field "payload" JD.value)
68 | (JD.field "ref" (JD.oneOf [ JD.map Just JD.int, JD.null Nothing ]))
69 | in
70 | JD.decodeString decoder msg
71 |
--------------------------------------------------------------------------------
/assets/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See http://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: "js/app.js"
6 |
7 | // To use a separate vendor.js bundle, specify two files path
8 | // http://brunch.io/docs/config#-files-
9 | // joinTo: {
10 | // "js/app.js": /^js/,
11 | // "js/vendor.js": /^(?!js)/
12 | // }
13 | //
14 | // To change the order of concatenation of files, explicitly mention here
15 | // order: {
16 | // before: [
17 | // "vendor/js/jquery-2.1.1.js",
18 | // "vendor/js/bootstrap.min.js"
19 | // ]
20 | // }
21 | },
22 | stylesheets: {
23 | joinTo: "css/app.css"
24 | },
25 | templates: {
26 | joinTo: "js/app.js"
27 | }
28 | },
29 |
30 | conventions: {
31 | // This option sets where we should place non-css and non-js assets in.
32 | // By default, we set this to "/assets/static". Files in this directory
33 | // will be copied to `paths.public`, which is "priv/static" by default.
34 | assets: /^(static)/
35 | },
36 |
37 | // Phoenix paths configuration
38 | paths: {
39 | // Dependencies and current project directories to watch
40 | watched: ["static", "css", "js", "vendor", "elm"],
41 | // Where to compile files to
42 | public: "../priv/static"
43 | },
44 |
45 | // Configure your plugins
46 | plugins: {
47 | babel: {
48 | // Do not use ES6 compiler in vendor code
49 | ignore: [/vendor/]
50 | },
51 | elmBrunch: {
52 | mainModules: ["elm/src/Main.elm"],
53 | makeParameters: [""],
54 | outputFolder: "js/"
55 | },
56 | },
57 |
58 | modules: {
59 | autoRequire: {
60 | "js/app.js": ["js/app"]
61 | }
62 | },
63 |
64 | npm: {
65 | enabled: true
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :poker_ex_client, PokerExClientWeb.Endpoint,
10 | http: [port: 8081],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
15 | cd: Path.expand("../assets", __DIR__)]]
16 |
17 | # ## SSL Support
18 | #
19 | # In order to use HTTPS in development, a self-signed
20 | # certificate can be generated by running the following
21 | # command from your terminal:
22 | #
23 | # openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem
24 | #
25 | # The `http:` config above can be replaced with:
26 | #
27 | # https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"],
28 | #
29 | # If desired, both `http:` and `https:` keys can be
30 | # configured to run both http and https servers on
31 | # different ports.
32 |
33 | # Watch static and templates for browser reloading.
34 | config :poker_ex_client, PokerExClientWeb.Endpoint,
35 | live_reload: [
36 | patterns: [
37 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
38 | ~r{priv/gettext/.*(po)$},
39 | ~r{lib/poker_ex_client_web/views/.*(ex)$},
40 | ~r{lib/poker_ex_client_web/templates/.*(eex)$}
41 | ]
42 | ]
43 |
44 | # Do not include metadata nor timestamps in development logs
45 | config :logger, :console, format: "[$level] $message\n"
46 |
47 | # Set a higher stacktrace during development. Avoid configuring such
48 | # in production as building large stacktraces may be expensive.
49 | config :phoenix, :stacktrace_depth, 20
50 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :poker_ex_client
3 |
4 | socket "/socket", PokerExClientWeb.UserSocket
5 |
6 | # Serve at "/" the static files from "priv/static" directory.
7 | #
8 | # You should set gzip to true if you are running phoenix.digest
9 | # when deploying your static files in production.
10 | plug Plug.Static,
11 | at: "/", from: :poker_ex_client, gzip: false,
12 | only: ~w(css fonts images js favicon.ico robots.txt)
13 |
14 | # Code reloading can be explicitly enabled under the
15 | # :code_reloader configuration of your endpoint.
16 | if code_reloading? do
17 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
18 | plug Phoenix.LiveReloader
19 | plug Phoenix.CodeReloader
20 | end
21 |
22 | plug Plug.RequestId
23 | plug Plug.Logger
24 |
25 | plug Plug.Parsers,
26 | parsers: [:urlencoded, :multipart, :json],
27 | pass: ["*/*"],
28 | json_decoder: Poison
29 |
30 | plug Plug.MethodOverride
31 | plug Plug.Head
32 |
33 | # The session will be stored in the cookie and signed,
34 | # this means its contents can be read but not tampered with.
35 | # Set :encryption_salt if you would also like to encrypt it.
36 | plug Plug.Session,
37 | store: :cookie,
38 | key: "_poker_ex_client_key",
39 | signing_salt: "1oZ0QX72"
40 |
41 | plug PokerExClientWeb.Router
42 |
43 | @doc """
44 | Callback invoked for dynamically configuring the endpoint.
45 |
46 | It receives the endpoint configuration and checks if
47 | configuration should be loaded from the system environment.
48 | """
49 | def init(_key, config) do
50 | if config[:load_from_system_env] do
51 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
52 | {:ok, Keyword.put(config, :http, [:inet6, port: port])}
53 | else
54 | {:ok, config}
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/poker_ex_client_web.ex:
--------------------------------------------------------------------------------
1 | defmodule PokerExClientWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use PokerExClientWeb, :controller
9 | use PokerExClientWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: PokerExClientWeb
23 | import Plug.Conn
24 | import PokerExClientWeb.Router.Helpers
25 | import PokerExClientWeb.Gettext
26 | end
27 | end
28 |
29 | def view do
30 | quote do
31 | use Phoenix.View, root: "lib/poker_ex_client_web/templates",
32 | namespace: PokerExClientWeb
33 |
34 | # Import convenience functions from controllers
35 | import Phoenix.Controller, only: [get_flash: 2, view_module: 1]
36 |
37 | # Use all HTML functionality (forms, tags, etc)
38 | use Phoenix.HTML
39 |
40 | import PokerExClientWeb.Router.Helpers
41 | import PokerExClientWeb.ErrorHelpers
42 | import PokerExClientWeb.Gettext
43 | end
44 | end
45 |
46 | def router do
47 | quote do
48 | use Phoenix.Router
49 | import Plug.Conn
50 | import Phoenix.Controller
51 | end
52 | end
53 |
54 | def channel do
55 | quote do
56 | use Phoenix.Channel
57 | import PokerExClientWeb.Gettext
58 | end
59 | end
60 |
61 | @doc """
62 | When used, dispatch to the appropriate controller/view/etc.
63 | """
64 | defmacro __using__(which) when is_atom(which) do
65 | apply(__MODULE__, which, [])
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/assets/elm/src/Types/Room/Messages.elm:
--------------------------------------------------------------------------------
1 | module Types.Room.Messages exposing (RoomExternalMsg(..), RoomMessageType(..), RoomMsg(..))
2 |
3 | import Data.Player as Player exposing (Player)
4 | import Json.Decode as Decode exposing (Value)
5 | import Json.Encode as Encode
6 | import Time exposing (Time)
7 |
8 |
9 | type RoomMsg
10 | = JoinRoom Player -- START BUTTON ACTION MESSAGES
11 | | LeaveRoom Player
12 | | ActionPressed
13 | | BankPressed
14 | | AccountPressed
15 | | ChatPressed
16 | | OpenRaisePressed
17 | | MobileToolbarPressed -- END BUTTON ACTION MESSAGES
18 | | Join -- START JOIN ACTION MESSAGES
19 | | JoinedChannel
20 | | JoinFailed Value
21 | | SetJoinValue String
22 | | ConnectedToPlayerChannel
23 | | ChipInfo Encode.Value -- END JOIN ACTION MESSAGES
24 | | IncreaseRaise Int -- START RAISE ACTION MESSAGES
25 | | DecreaseRaise Int
26 | | SetRaise String -- END RAISE ACTION MESSAGES
27 | | ActionMsg String Encode.Value -- ACTION MSG
28 | | SetBankInfo Value -- BANK ACTION
29 | | SetAddAmount String -- BANK ACTION
30 | | Update Value -- START MESSAGES FROM GAME SERVER
31 | | GameStarted Value
32 | | WinnerMessage Value
33 | | Clear Value
34 | | NewMessage Value
35 | | PresentWinningHand Value -- END MESSAGES FROM GAME SERVER
36 | | NewChatMsg Value -- START CHAT MESSAGES
37 | | SetChatMsg String
38 | | CloseModal
39 | | SubmitChat -- END CHAT MESSAGES
40 | | SocketOpened -- START SOCKET MESSAGES
41 | | SocketClosed
42 | | SocketClosedAbnormally
43 | | Rejoined Value -- END SOCKET MESSAGES
44 | | Blur -- START MODAL-RELATED MESSAGES
45 | | ClearErrorMessage Time
46 | | ClearRoomMessage Time
47 | | ClearWinningHandModal Time
48 | | CloseRaiseModal
49 | | CloseWinningHandModal -- END MODAL-RELATED MESSAGES
50 |
51 |
52 | type RoomExternalMsg
53 | = NoOp
54 |
55 |
56 | type RoomMessageType
57 | = RoomMessage String
58 | | ErrorMessage String
59 |
--------------------------------------------------------------------------------
/assets/elm/src/Views/Chat.elm:
--------------------------------------------------------------------------------
1 | module Views.Chat exposing (Chat, view)
2 |
3 | import Html as Html exposing (..)
4 | import Html.Attributes as Attrs exposing (..)
5 | import Html.Events as Events
6 | import Json.Encode as Encode exposing (Value)
7 |
8 |
9 | type alias PlayerName =
10 | String
11 |
12 |
13 | type alias Message =
14 | String
15 |
16 |
17 | type alias ChatMessage =
18 | { playerName : String
19 | , message : String
20 | }
21 |
22 |
23 | type alias Chat =
24 | List ChatMessage
25 |
26 |
27 | view : Chat -> String -> (String -> msg) -> msg -> msg -> Html msg
28 | view chat currentMsg inputMsg submitMsg closeModalMsg =
29 | div [ class "chat-wrapper" ]
30 | [ span [ class "modal-header red-text" ] [ text "Chat" ]
31 | , i [ class "close-chat material-icons", Events.onClick closeModalMsg ] [ text "close" ]
32 | , ul [ class "chat-container collection", id "chat" ]
33 | (List.map viewMessage chat)
34 | , Html.form [ class "chat-input-container", Events.onSubmit submitMsg ]
35 | [ chatInput currentMsg inputMsg
36 | , a [ class "btn blue white-text chat-submit-btn", Events.onClick submitMsg ] [ text "Send" ]
37 | , a [ class "btn-floating green white-text chat-submit-mobile", Events.onClick submitMsg ]
38 | [ i [ class "material-icons" ]
39 | [ text "send" ]
40 | ]
41 | ]
42 | ]
43 |
44 |
45 | viewMessage : ChatMessage -> Html msg
46 | viewMessage message =
47 | li [ class "collection-item message-container" ]
48 | [ span [ class "chat-player-name red-text" ]
49 | [ text <| message.playerName ++ " says: " ]
50 | , span [ class "chat-message-item" ]
51 | [ text message.message ]
52 | ]
53 |
54 |
55 | chatInput : String -> (String -> msg) -> Html msg
56 | chatInput currentMsg inputMsg =
57 | input
58 | [ type_ "text"
59 | , class "chat-input"
60 | , value currentMsg
61 | , placeholder "Say something"
62 | , Events.onInput inputMsg
63 | ]
64 | []
65 |
--------------------------------------------------------------------------------
/assets/elm/src/Widgets/Dropdown.elm:
--------------------------------------------------------------------------------
1 | module Widgets.Dropdown exposing (Config, Context, view)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (class, classList, style)
5 | import Html.Events exposing (onWithOptions)
6 | import Json.Decode as Decode
7 | import Types.Dropdowns as DropdownType exposing (DropdownItem, DropdownNavbarLink)
8 |
9 |
10 | type alias Context =
11 | { selectedItem : DropdownItem
12 | , isOpen : Bool
13 | }
14 |
15 |
16 | type alias Config msg =
17 | { topLevelHtml : Html msg
18 | , clickedMsg : msg
19 | , itemPickedMsg : DropdownNavbarLink -> msg
20 | }
21 |
22 |
23 | view : Config msg -> Context -> List DropdownNavbarLink -> Html msg
24 | view config context data =
25 | let
26 | length =
27 | List.length data * 3
28 |
29 | displayStyles =
30 | if context.isOpen then
31 | [ ( "transform", "scaleY(1)" )
32 | , ( "transform-origin", "top" )
33 | , ( "transition", "transform 1s ease-in-out" )
34 | , ( "max-height", toString length ++ "em" )
35 | , ( "transition", "max-height 0.5s ease-in-out" )
36 | ]
37 | else
38 | [ ( "transform", "scaleY(0)" )
39 | , ( "transform-origin", "top" )
40 | , ( "transition", "transform 1s ease-in-out" )
41 | , ( "max-height", "0" )
42 | , ( "transition", "max-height 0.5s ease-in-out" )
43 | ]
44 | in
45 | ul
46 | [ style displayStyles
47 | , classList [ ( "dropdown-menu", context.isOpen ), ( "collection", True ) ]
48 | , class "nav-dropdown"
49 | ]
50 | (List.map (viewItem config) data)
51 |
52 |
53 | viewItem : Config msg -> DropdownNavbarLink -> Html msg
54 | viewItem config item =
55 | li [ onClick (config.itemPickedMsg item), class "collection-item nav-dropdown-item" ]
56 | [ span [ class "cursor-pointer" ] [ text <| toString item ] ]
57 |
58 |
59 |
60 | -- Helper to cancel click anywhere --
61 |
62 |
63 | onClick : msg -> Attribute msg
64 | onClick message =
65 | onWithOptions
66 | "click"
67 | { stopPropagation = True
68 | , preventDefault = False
69 | }
70 | (Decode.succeed message)
71 |
--------------------------------------------------------------------------------
/assets/elm/src/Views/Footer.elm:
--------------------------------------------------------------------------------
1 | module Views.Footer exposing (..)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (class, href)
5 |
6 |
7 | view : Html msg
8 | view =
9 | footer [ class "page-footer blue-grey darken-3 white-text bottom" ]
10 | [ div [ class "container" ]
11 | [ div [ class "row footer-row" ]
12 | [ div [ class "col m6 l6 s12 center-align-small" ]
13 | [ h5 [ class "white-text" ] [ text "POKEREX" ]
14 | , p [ class "grey-text text-lighten-4" ]
15 | [ text "PokerEx was designed and developed by Zack Kayser. "
16 | , text "Connect with me on Twitter at @kayserzl or on "
17 | , a [ href "https://www.linkedin.com/in/zack-kayser-93b96b88" ] [ text "LinkedIn. " ]
18 | , text "Feel free to check out the open-source code on "
19 | , a [ href "https://github.com/zkayser/pokerex_client" ] [ text "Github" ]
20 | , text "! "
21 | , text "You can also check out the server-side Elixir/Phoenix project "
22 | , a [ href "https://github.com/zkayser/poker_ex" ] [ text "here" ]
23 | , text "."
24 | ]
25 | ]
26 | , div [ class "col m4 l4 offset-l2 s12" ]
27 | [ h5 [ class "white-text center-align-small" ] [ text "Links" ]
28 | , ul [ class "text-center" ]
29 | [ li [] [ a [ href "https://github.com/zkayser/pokerex_client" ] [ text "Github" ] ]
30 | , li [] [ a [ href "https://github.com/zkayser/poker_ex" ] [ text "Server Side Elixir Project" ] ]
31 | , li [] [ a [ href "http://elm-lang.org/" ] [ text "Elm-lang home page" ] ]
32 | , li [] [ a [ href "http://www.phoenixframework.org/" ] [ text "Phoenix Framework" ] ]
33 | , li [] [ a [ href "http://elixir-lang.org/" ] [ text "Elixir Lang Home Page" ] ]
34 | ]
35 | ]
36 | ]
37 | ]
38 | , div [ class "footer-copyright" ]
39 | [ div [ class "container" ] [ text "©Zack Kayser, 2017" ] ]
40 | ]
41 |
--------------------------------------------------------------------------------
/assets/elm/src/Route.elm:
--------------------------------------------------------------------------------
1 | module Route exposing (..)
2 |
3 | import Html exposing (Attribute)
4 | import Html.Attributes as Attr
5 | import Navigation exposing (Location)
6 | import UrlParser as Url exposing ((>), Parser, oneOf, parseHash, s)
7 |
8 |
9 | type Route
10 | = Home
11 | | Login
12 | | Logout
13 | | Register
14 | | Room String String
15 | | Rooms
16 | | Profile String
17 | | ForgotPassword
18 | | ResetPassword String
19 |
20 |
21 | route : Parser (Route -> a) a
22 | route =
23 | oneOf
24 | [ Url.map Home (s "")
25 | , Url.map Login (s "login")
26 | , Url.map Logout (s "logout")
27 | , Url.map Register (s "register")
28 | , Url.map Room (s "rooms" > Url.string > Url.string)
29 | , Url.map Rooms (s "rooms")
30 | , Url.map Profile (s "profile" > Url.string)
31 | , Url.map ForgotPassword (s "forgot-password")
32 | , Url.map ResetPassword (s "reset-password" > Url.string)
33 | ]
34 |
35 |
36 | fromLocation : Location -> Maybe Route
37 | fromLocation location =
38 | if String.isEmpty location.hash then
39 | Just Home
40 | else
41 | parseHash route location
42 |
43 |
44 | href : Route -> Attribute msg
45 | href route =
46 | Attr.href (routeToString route)
47 |
48 |
49 | modifyUrl : Route -> Cmd msg
50 | modifyUrl =
51 | routeToString >> Navigation.modifyUrl
52 |
53 |
54 | routeToString : Route -> String
55 | routeToString page =
56 | let
57 | pieces =
58 | case page of
59 | Home ->
60 | []
61 |
62 | Login ->
63 | [ "login" ]
64 |
65 | Logout ->
66 | [ "logout" ]
67 |
68 | Register ->
69 | [ "register" ]
70 |
71 | Room roomType roomTitle ->
72 | [ "rooms", roomType, roomTitle ]
73 |
74 | Rooms ->
75 | [ "rooms" ]
76 |
77 | Profile user ->
78 | [ "profile", user ]
79 |
80 | ForgotPassword ->
81 | [ "forgot-password" ]
82 |
83 | ResetPassword resetToken ->
84 | [ "reset-password", resetToken ]
85 | in
86 | "#/" ++ String.join "/" pieces
87 |
--------------------------------------------------------------------------------
/assets/elm/src/Views/Bank.elm:
--------------------------------------------------------------------------------
1 | module Views.Bank exposing (view)
2 |
3 | import Data.Player as Player exposing (Player, Username)
4 | import Html as Html exposing (..)
5 | import Html.Attributes as Attrs exposing (..)
6 | import Html.Events as Events exposing (onClick, onInput, onSubmit)
7 | import Json.Encode as Encode exposing (Value)
8 |
9 |
10 | type alias Model r =
11 | { r
12 | | addAmount : Int
13 | , player : Player
14 | , chipsAvailable : Int
15 | }
16 |
17 |
18 | view : Model r -> ( String -> msg, String -> Value -> msg, msg ) -> Html msg
19 | view model ( setMsg, submitMsg, closeMsg ) =
20 | div [ class "bank-container center-align" ]
21 | [ span [ class "modal-header red-text" ] [ viewTitle model ]
22 | , h4 [ class "red-text" ] [ text <| "You currently have " ++ toString model.chipsAvailable ++ " chips available." ]
23 | , div [ class "bank-form-container" ]
24 | [ div [ class "bank-form" ]
25 | [ Html.form [ onSubmit (submitMsg "action_add_chips" (encodePayload model)) ]
26 | [ div [ class "input-field" ]
27 | [ input
28 | [ placeholder "How many chips would you like to add?"
29 | , type_ "number"
30 | , class "validate"
31 | , onInput setMsg
32 | ]
33 | []
34 | ]
35 | ]
36 | , button
37 | [ type_ "submit"
38 | , onClick (submitMsg "action_add_chips" (encodePayload model))
39 | , class "btn blue white-text"
40 | ]
41 | [ text "Add chips" ]
42 | ]
43 | ]
44 | , i [ class "close-modal material-icons small", onClick closeMsg ] [ text "close" ]
45 | ]
46 |
47 |
48 | viewTitle : Model r -> Html msg
49 | viewTitle model =
50 | if model.addAmount == 0 then
51 | text "Add chips?"
52 | else
53 | text <| "Add " ++ toString model.addAmount ++ " chips?"
54 |
55 |
56 | encodePayload : Model r -> Value
57 | encodePayload model =
58 | Encode.object
59 | [ ( "player", Player.encodeUsername model.player.username )
60 | , ( "amount", Encode.int model.addAmount )
61 | ]
62 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
2 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
3 | "file_system": {:hex, :file_system, "0.2.1", "c4bec8f187d2aabace4beb890f0d4e468f65ca051593db768e533a274d0df587", [:mix], [], "hexpm"},
4 | "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], [], "hexpm"},
5 | "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], [], "hexpm"},
6 | "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
7 | "phoenix_html": {:hex, :phoenix_html, "2.10.4", "d4f99c32d5dc4918b531fdf163e1fd7cf20acdd7703f16f5d02d4db36de803b7", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
8 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.2", "dfd31cc1eb091533b4419bbdb67bec9767bb26c9fe09602e6cca313fab5302d0", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
9 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], [], "hexpm"},
10 | "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
11 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
12 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}}
13 |
--------------------------------------------------------------------------------
/assets/css/profile.css:
--------------------------------------------------------------------------------
1 | .profile-pane-container {
2 | display: flex;
3 | }
4 |
5 | .profile-pane-container > * {
6 | flex: 1;
7 | min-height: 74.5vh;
8 | padding: 1rem;
9 | }
10 |
11 | .profile-pane-break {
12 | display: none;
13 | }
14 |
15 | @media (max-width: 860px) {
16 | .profile-pane-container {
17 | flex-direction: column;
18 | }
19 |
20 | .profile-pane-container > * {
21 | min-height: 0;
22 | }
23 |
24 | .profile-pane.game-info-pane {
25 | order: 1;
26 | }
27 |
28 | .profile-pane.profile-info-pane {
29 | order: 3;
30 | }
31 |
32 | .profile-pane-break {
33 | display: block;
34 | flex: 1;
35 | order: 2;
36 | height: 1px;
37 | padding: 0;
38 | margin: 1rem;
39 | }
40 | }
41 |
42 | @media (max-width: 435px) {
43 | .fb-invite-btn {
44 | font-size: 0.75rem;
45 | }
46 | }
47 |
48 | .profile-greeting {
49 | text-align: center;
50 | }
51 |
52 | .chip-restore {
53 | text-align: center;
54 | }
55 |
56 | .fb-invite-container {
57 | margin-top: 2rem;
58 | text-align: center;
59 | }
60 |
61 | .delete-profile-container {
62 | margin-top: 2rem;
63 | text-align: center;
64 | }
65 |
66 | .game-info-header {
67 | text-align: center;
68 | text-decoration: underline;
69 | }
70 |
71 | .game-info-text {
72 | text-align: center;
73 | font-size: 1.25rem;
74 | font-weight: 400;
75 | }
76 |
77 | .current-games a .game-info-text {
78 | text-align: center;
79 | }
80 |
81 | .current-games-tab-container {
82 | box-shadow: 0rem 0.01rem 0.01rem 0.01rem black;
83 | z-index: 100;
84 | }
85 |
86 | .create-game-tab-container {
87 | box-shadow: 0rem 0.01rem 0.01rem 0.01rem black;
88 | z-index: 100;
89 | }
90 |
91 | .game-form-header {
92 | text-align: center;
93 | text-decoration: underline;
94 | }
95 |
96 | .invite-btn {
97 | width: 11rem;
98 | display: flex;
99 | align-items: center;
100 | }
101 |
102 | .invite-btn i {
103 | margin-right: 1rem;
104 | }
105 |
106 | .player-list-elem {
107 | display: flex;
108 | align-items: center;
109 | justify-content: space-between;
110 | }
111 |
112 | .player-list-collection {
113 | padding-top: 1.5rem;
114 | }
115 |
116 | .active-sub-tab {
117 | background-color: var(--teal);
118 | color: white;
119 | }
120 |
121 | .submit-game-btn-container {
122 | display: flex;
123 | justify-content: center;
124 | align-items: center;
125 | margin-bottom: 1rem;
126 | }
--------------------------------------------------------------------------------
/assets/js/socket.js:
--------------------------------------------------------------------------------
1 | // NOTE: The contents of this file will only be executed if
2 | // you uncomment its entry in "assets/js/app.js".
3 |
4 | // To use Phoenix channels, the first step is to import Socket
5 | // and connect at the socket path in "lib/web/endpoint.ex":
6 | import {Socket} from "phoenix"
7 |
8 | let socket = new Socket("/socket", {params: {token: window.userToken}})
9 |
10 | // When you connect, you'll often need to authenticate the client.
11 | // For example, imagine you have an authentication plug, `MyAuth`,
12 | // which authenticates the session and assigns a `:current_user`.
13 | // If the current user exists you can assign the user's token in
14 | // the connection for use in the layout.
15 | //
16 | // In your "lib/web/router.ex":
17 | //
18 | // pipeline :browser do
19 | // ...
20 | // plug MyAuth
21 | // plug :put_user_token
22 | // end
23 | //
24 | // defp put_user_token(conn, _) do
25 | // if current_user = conn.assigns[:current_user] do
26 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id)
27 | // assign(conn, :user_token, token)
28 | // else
29 | // conn
30 | // end
31 | // end
32 | //
33 | // Now you need to pass this token to JavaScript. You can do so
34 | // inside a script tag in "lib/web/templates/layout/app.html.eex":
35 | //
36 | //
37 | //
38 | // You will need to verify the user token in the "connect/2" function
39 | // in "lib/web/channels/user_socket.ex":
40 | //
41 | // def connect(%{"token" => token}, socket) do
42 | // # max_age: 1209600 is equivalent to two weeks in seconds
43 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
44 | // {:ok, user_id} ->
45 | // {:ok, assign(socket, :user, user_id)}
46 | // {:error, reason} ->
47 | // :error
48 | // end
49 | // end
50 | //
51 | // Finally, pass the token on connect as below. Or remove it
52 | // from connect if you don't care about authentication.
53 |
54 | socket.connect()
55 |
56 | // Now that you are connected, you can join channels with a topic:
57 | let channel = socket.channel("topic:subtopic", {})
58 | channel.join()
59 | .receive("ok", resp => { console.log("Joined successfully", resp) })
60 | .receive("error", resp => { console.log("Unable to join", resp) })
61 |
62 | export default socket
63 |
--------------------------------------------------------------------------------
/assets/css/room.css:
--------------------------------------------------------------------------------
1 | .room-container {
2 | min-height: 88vh;
3 | display: -webkit-box;
4 | display: -ms-flexbox;
5 | display: flex;
6 | -webkit-box-orient: vertical;
7 | -webkit-box-direction: normal;
8 | -ms-flex-direction: column;
9 | flex-direction: column;
10 | -webkit-box-align: center;
11 | -ms-flex-align: center;
12 | align-items: center;
13 | -webkit-box-pack: center;
14 | -ms-flex-pack: center;
15 | justify-content: center;
16 | position: relative;
17 | }
18 |
19 | .room-message-container {
20 | position: fixed;
21 | z-index: 100;
22 | min-width: 25%;
23 | right: 0%;
24 | top: 15%;
25 | display: -webkit-box;
26 | display: -ms-flexbox;
27 | display: flex;
28 | -webkit-box-orient: vertical;
29 | -webkit-box-direction: normal;
30 | -ms-flex-direction: column;
31 | flex-direction: column;
32 | -webkit-box-align: baseline;
33 | -ms-flex-align: baseline;
34 | align-items: baseline;
35 | }
36 |
37 | .message {
38 | color: white;
39 | font-size: 1.25rem;
40 | margin: 0.5rem 0;
41 | padding: 1rem;
42 | -webkit-box-shadow: 0 0 1rem 0 black;
43 | box-shadow: 0 0 1rem 0 black;
44 | -webkit-animation: slideInMessage 0.75s ease-in-out;
45 | animation: slideInMessage 0.75s ease-in-out;
46 | }
47 |
48 | @media (max-width: 600px) {
49 | .room-message-container {
50 | position: fixed;
51 | min-width: 100%;
52 | max-width: 100%;
53 | bottom: 0%;
54 | }
55 | }
56 |
57 | .error-message {
58 | background-color: var(--bright-red);
59 | }
60 |
61 | .room-message {
62 | background-color: var(--green-accent);
63 | }
64 |
65 | @-webkit-keyframes slideInMessage {
66 | 0% {
67 | -webkit-transform: translateY(100rem);
68 | transform: translateY(100rem);
69 | opacity: 0;
70 | }
71 | 50% {
72 | opacity: 0;
73 | }
74 | 75% {
75 | opacity: 0;
76 | }
77 | 100% {
78 | -webkit-transform: translateY(0);
79 | transform: translateY(0);
80 | opacity: 1;
81 | }
82 | }
83 |
84 | @keyframes slideInMessage {
85 | 0% {
86 | -webkit-transform: translateY(100rem);
87 | transform: translateY(100rem);
88 | opacity: 0;
89 | }
90 | 50% {
91 | opacity: 0;
92 | }
93 | 75% {
94 | opacity: 0;
95 | }
96 | 100% {
97 | -webkit-transform: translateY(0);
98 | transform: translateY(0);
99 | opacity: 1;
100 | }
101 | }
--------------------------------------------------------------------------------
/assets/elm/src/Page/Room/Helpers.elm:
--------------------------------------------------------------------------------
1 | module Page.Room.Helpers exposing (..)
2 |
3 | import Data.Card as Card exposing (Card)
4 | import Data.Player as Player exposing (Player)
5 | import Data.Room as Room exposing (..)
6 | import Data.RoomPage as RoomPage exposing (RoomPage)
7 | import Dict exposing (Dict)
8 |
9 |
10 | type alias Model =
11 | RoomPage
12 |
13 |
14 | handWhereIs : Player.Username -> List Room.PlayerHand -> Player -> List Card
15 | handWhereIs username playerHands player =
16 | let
17 | theHand =
18 | case List.filter (\playerHand -> Player.equals username playerHand.player) playerHands of
19 | [] ->
20 | Nothing
21 |
22 | [ playerHand ] ->
23 | Just playerHand
24 |
25 | _ ->
26 | Nothing
27 |
28 | handForPlayer =
29 | case theHand of
30 | Just hand ->
31 | if Player.equals hand.player player.username then
32 | hand.hand
33 | else
34 | [ { rank = Card.RankError, suit = Card.SuitError }, { rank = Card.RankError, suit = Card.SuitError } ]
35 |
36 | _ ->
37 | [ { rank = Card.RankError, suit = Card.SuitError }, { rank = Card.RankError, suit = Card.SuitError } ]
38 | in
39 | handForPlayer
40 |
41 |
42 | getChips : Model -> Dict String Int -> Int
43 | getChips model dict =
44 | case Dict.get (Player.usernameToString model.player.username) dict of
45 | Nothing ->
46 | 0
47 |
48 | Just chips ->
49 | chips
50 |
51 |
52 | getIsActive : Model -> Bool
53 | getIsActive model =
54 | case model.roomModel.active of
55 | Nothing ->
56 | False
57 |
58 | Just username ->
59 | Player.equals model.player.username username
60 |
61 |
62 | possibleActions : List String
63 | possibleActions =
64 | [ "action_raise", "action_check", "action_call", "action_fold", "action_add_chips" ]
65 |
66 |
67 | joinValToInt : String -> Int
68 | joinValToInt stringAmount =
69 | case String.toInt stringAmount of
70 | Ok value ->
71 | value
72 |
73 | Err _ ->
74 | 0
75 |
76 |
77 | formatTitle : String -> String
78 | formatTitle title =
79 | String.split "%20" title
80 | |> String.join "_"
81 | |> String.split " "
82 | |> String.join "_"
83 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Player.elm:
--------------------------------------------------------------------------------
1 | module Data.Player exposing (..)
2 |
3 | import Data.AuthToken as AuthToken exposing (AuthToken)
4 | import Data.Profile as Profile exposing (Profile)
5 | import Html exposing (Html)
6 | import Json.Decode as Decode exposing (Decoder)
7 | import Json.Decode.Pipeline as Pipeline exposing (decode, optional, required)
8 | import Json.Encode as Encode exposing (Value)
9 | import Json.Encode.Extra as EncodeExtra
10 | import UrlParser
11 |
12 |
13 | type alias Player =
14 | { email : String
15 | , token : AuthToken
16 | , username : Username
17 | , chips : Int
18 | }
19 |
20 |
21 | type alias TablePlayer =
22 | { name : Username
23 | , chips : Int
24 | }
25 |
26 |
27 |
28 | -- Initialization --
29 |
30 |
31 | defaultPlayer : Player
32 | defaultPlayer =
33 | { email = ""
34 | , token = AuthToken.AuthToken ""
35 | , username = Username ""
36 | , chips = 0
37 | }
38 |
39 |
40 |
41 | -- Serialization --
42 |
43 |
44 | decoder : Decoder Player
45 | decoder =
46 | decode Player
47 | |> optional "email" Decode.string ""
48 | |> required "token" AuthToken.decoder
49 | |> required "username" usernameDecoder
50 | |> required "chips" Decode.int
51 |
52 |
53 | encode : Player -> Value
54 | encode player =
55 | Encode.object
56 | [ ( "email", Encode.string player.email )
57 | , ( "token", AuthToken.encode player.token )
58 | , ( "username", encodeUsername player.username )
59 | , ( "chips", Encode.int player.chips )
60 | ]
61 |
62 |
63 | tablePlayerDecoder : Decoder TablePlayer
64 | tablePlayerDecoder =
65 | decode TablePlayer
66 | |> required "name" usernameDecoder
67 | |> required "chips" Decode.int
68 |
69 |
70 |
71 | -- Identifiers --
72 |
73 |
74 | type Username
75 | = Username String
76 |
77 |
78 | usernameToString : Username -> String
79 | usernameToString (Username username) =
80 | username
81 |
82 |
83 | usernameDecoder : Decoder Username
84 | usernameDecoder =
85 | Decode.map Username Decode.string
86 |
87 |
88 | encodeUsername : Username -> Value
89 | encodeUsername (Username username) =
90 | Encode.string username
91 |
92 |
93 | usernameToHtml : Username -> Html msg
94 | usernameToHtml (Username username) =
95 | Html.text username
96 |
97 |
98 | equals : Username -> Username -> Bool
99 | equals usernameOne usernameTwo =
100 | usernameOne == usernameTwo
101 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we often load configuration from external
4 | # sources, such as your system environment. For this reason,
5 | # you won't find the :http configuration below, but set inside
6 | # PokerExClientWeb.Endpoint.init/2 when load_from_system_env is
7 | # true. Any dynamic configuration should be done there.
8 | #
9 | # Don't forget to configure the url host to something meaningful,
10 | # Phoenix uses this information when generating URLs.
11 | #
12 | # Finally, we also include the path to a cache manifest
13 | # containing the digested version of static files. This
14 | # manifest is generated by the mix phx.digest task
15 | # which you typically run after static files are built.
16 | config :poker_ex_client, PokerExClientWeb.Endpoint,
17 | load_from_system_env: true,
18 | url: [scheme: "https", host: "poker-ex.herokuapp.com", port: 80],
19 | force_ssl: [rewrite_on: [:x_forwarded_proto]],
20 | cache_static_manifest: "priv/static/cache_manifest.json"
21 |
22 | # Do not print debug messages in production
23 | config :logger, level: :info
24 |
25 | # ## SSL Support
26 | #
27 | # To get SSL working, you will need to add the `https` key
28 | # to the previous section and set your `:url` port to 443:
29 | #
30 | # config :poker_ex_client, PokerExClientWeb.Endpoint,
31 | # ...
32 | # url: [host: "example.com", port: 443],
33 | # https: [:inet6,
34 | # port: 443,
35 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
36 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
37 | #
38 | # Where those two env variables return an absolute path to
39 | # the key and cert in disk or a relative path inside priv,
40 | # for example "priv/ssl/server.key".
41 | #
42 | # We also recommend setting `force_ssl`, ensuring no data is
43 | # ever sent via http, always redirecting to https:
44 | #
45 | # config :poker_ex_client, PokerExClientWeb.Endpoint,
46 | # force_ssl: [hsts: true]
47 | #
48 | # Check `Plug.SSL` for all available options in `force_ssl`.
49 |
50 | # ## Using releases
51 | #
52 | # If you are doing OTP releases, you need to instruct Phoenix
53 | # to start the server for all endpoints:
54 | #
55 | # config :phoenix, :serve_endpoints, true
56 | #
57 | # Alternatively, you can configure exactly which server to
58 | # start per endpoint:
59 | #
60 | # config :poker_ex_client, PokerExClientWeb.Endpoint, server: true
61 | #
62 |
63 | # Finally import the config/prod.secret.exs
64 | # which should be versioned separately.
65 | # import_config "prod.secret.exs"
66 |
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Push.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Push exposing (Push, init, map, onError, onOk, withPayload)
2 |
3 | {-| A message to push informations to a channel.
4 |
5 |
6 | # Definition
7 |
8 | @docs Push
9 |
10 |
11 | # Helpers
12 |
13 | @docs init, withPayload, onOk, onError, map
14 |
15 | -}
16 |
17 | import Json.Encode exposing (Value)
18 |
19 |
20 | {-| The message abstraction
21 | -}
22 | type alias Push msg =
23 | PhoenixPush msg
24 |
25 |
26 | type alias PhoenixPush msg =
27 | { topic : String
28 | , event : String
29 | , payload : Value
30 | , onOk : Maybe (Value -> msg)
31 | , onError : Maybe (Value -> msg)
32 | }
33 |
34 |
35 | type alias Topic =
36 | String
37 |
38 |
39 | type alias Event =
40 | String
41 |
42 |
43 | {-| Initialize a message with a topic and an event.
44 |
45 | init "room:lobby" "new_msg"
46 |
47 | -}
48 | init : Topic -> Event -> Push msg
49 | init topic event =
50 | PhoenixPush topic event (Json.Encode.object []) Nothing Nothing
51 |
52 |
53 | {-| Attach a payload to a message
54 |
55 | payload =
56 | Json.Encode.object [("msg", "Hello Phoenix")]
57 |
58 | init "room:lobby" "new_msg"
59 | |> withPayload
60 |
61 | -}
62 | withPayload : Value -> Push msg -> Push msg
63 | withPayload payload push =
64 | { push | payload = payload }
65 |
66 |
67 | {-| Callback if the server replies with an "ok" status.
68 |
69 | type Msg = MessageArrived | ...
70 |
71 | payload =
72 | Json.Encode.object [("msg", "Hello Phoenix")]
73 |
74 | init "room:lobby" "new_msg"
75 | |> withPayload
76 | |> onOk (\_ -> MessageArrived)
77 |
78 | -}
79 | onOk : (Value -> msg) -> Push msg -> Push msg
80 | onOk cb push =
81 | { push | onOk = Just cb }
82 |
83 |
84 | {-| Callback if the server replies with an "error" status.
85 |
86 | type Msg = MessageFailed Value | ...
87 |
88 | payload =
89 | Json.Encode.object [("msg", "Hello Phoenix")]
90 |
91 | init "room:lobby" "new_msg"
92 | |> withPayload
93 | |> onError MessageFailed
94 |
95 | -}
96 | onError : (Value -> msg) -> Push msg -> Push msg
97 | onError cb push =
98 | { push | onError = Just cb }
99 |
100 |
101 | {-| Applies the function on the onOk and onError callback
102 | -}
103 | map : (a -> b) -> Push a -> Push b
104 | map func push =
105 | let
106 | f =
107 | Maybe.map ((<<) func)
108 | in
109 | { push | onOk = f push.onOk, onError = f push.onError }
110 |
--------------------------------------------------------------------------------
/assets/js/app.js:
--------------------------------------------------------------------------------
1 | // Brunch automatically concatenates all files in your
2 | // watched paths. Those paths can be configured at
3 | // config.paths.watched in "brunch-config.js".
4 | //
5 | // However, those files will only be executed if
6 | // explicitly imported. The only exception are files
7 | // in vendor, which are never wrapped in imports and
8 | // therefore are always executed.
9 |
10 | // Import dependencies
11 | //
12 | // If you no longer want to use a dependency, remember
13 | // to also remove its path from "config.paths.watched".
14 | import "phoenix_html"
15 |
16 | import Elm from "./main.js";
17 |
18 | // FB SDK Setup/Functions
19 | const fbId = document.querySelector("[data-fb-id]").getAttribute('data-fb-id');
20 |
21 | window.fbAsyncInit = () => {
22 | FB.init({
23 | appId : `${fbId}`,
24 | xfbml : true,
25 | version : 'v2.9'
26 | });
27 | FB.AppEvents.logPageView();
28 | };
29 |
30 | (function(d, s, id){
31 | var js, fjs = d.getElementsByTagName(s)[0];
32 | if (d.getElementById(id)) {return;}
33 | js = d.createElement(s); js.id = id;
34 | js.src = "//connect.facebook.net/en_US/sdk.js";
35 | fjs.parentNode.insertBefore(js, fjs);
36 | }(document, 'script', 'facebook-jssdk'));
37 |
38 | const ELM_DIV = document.getElementById("elm-div");
39 | let elmApp = Elm.Main.embed(ELM_DIV, {
40 | session: localStorage.session,
41 | socketUrl: document.querySelector("[data-ws-url]").getAttribute('data-ws-url'),
42 | apiUrl: document.querySelector("[data-api-url]").getAttribute('data-api-url')
43 | });
44 |
45 | elmApp.ports.storeSession.subscribe((session) => {
46 | localStorage.session = session;
47 | });
48 |
49 | elmApp.ports.scrollChatToTop.subscribe(() => {
50 | let chat = document.getElementById('chat');
51 | chat ? chat.scrollTo(0, 0) : null;
52 | });
53 |
54 | elmApp.ports.logout.subscribe(() => {
55 | localStorage.removeItem('session');
56 | });
57 |
58 | elmApp.ports.triggerFBInviteRequest.subscribe(() => {
59 | if (window.FB) {
60 | FB.ui({method: 'apprequests',
61 | message: 'Join me for a game of Poker on PokerEX!'
62 | }, (response) => { "ok" });
63 | }
64 | });
65 |
66 | elmApp.ports.loginWithFB.subscribe(() => {
67 | FB.login((response) => {
68 | if (response.authResponse) {
69 | FB.api("/me", (response) => {
70 | elmApp.ports.onFBLogin.send(response);
71 | });
72 | }
73 | });
74 | });
75 |
76 | window.addEventListener("storage", (event) => {
77 | if (event.storageArea === localStorage && event.key === "session") {
78 | app.ports.onSessionChange.send(event.newValue);
79 | }
80 | }, false);
81 |
--------------------------------------------------------------------------------
/assets/css/controls.css:
--------------------------------------------------------------------------------
1 | .controls-container {
2 | background-color: rgb(244, 67, 54);
3 | /*position: absolute;*/
4 | bottom: 0;
5 | display: -webkit-box;
6 | display: -ms-flexbox;
7 | display: flex;
8 | -webkit-box-pack: justify;
9 | -ms-flex-pack: justify;
10 | justify-content: space-between;
11 | width: 100%;
12 | color: white;
13 | padding: 1rem 0 1rem 0;
14 | }
15 |
16 | @media (max-width: 600px) {
17 | .controls-container {
18 | position: absolute;
19 | right: 0;
20 | display: none;
21 | }
22 | }
23 |
24 | .controls-container li {
25 | list-style-type: none;
26 | -webkit-box-flex: 1;
27 | -ms-flex: 1;
28 | flex: 1;
29 | display: -webkit-box;
30 | display: -ms-flexbox;
31 | display: flex;
32 | -webkit-box-pack: center;
33 | -ms-flex-pack: center;
34 | justify-content: center;
35 | cursor: pointer;
36 | }
37 |
38 | .controls-disabled {
39 | opacity: 0.4;
40 | justify-content: center;
41 | align-items: center;
42 | }
43 |
44 | .controls-container li a {
45 | text-decoration: none;
46 | color: white;
47 | font-size: 1.5rem;
48 | width: 100%;
49 | text-align: center;
50 | }
51 |
52 | @-webkit-keyframes highlight-active {
53 | from {
54 | background-color: rgb(244, 67, 54);
55 | }
56 | to {
57 | background-color: #ff8a80;
58 | }
59 | }
60 |
61 | @keyframes highlight-active {
62 | from {
63 | background-color: rgb(244, 67, 54);
64 | }
65 | to {
66 | background-color: #ff8a80;
67 | }
68 | }
69 |
70 | .control-active {
71 | -webkit-animation: 2s linear 0s infinite alternate highlight-active;
72 | animation: 2s linear 0s infinite alternate highlight-active;
73 | }
74 |
75 | .mobile-controls-container {
76 | display: none;
77 | }
78 |
79 | @media (max-width: 600px) {
80 | .mobile-controls-container {
81 | display: inline;
82 | position: fixed;
83 | bottom: 22%;
84 | left: 10%;
85 | }
86 | }
87 |
88 | .mobile-menu-item a {
89 | cursor: pointer;
90 | }
91 |
92 | .actions-container-mobile {
93 | display: none;
94 | }
95 |
96 | @media (max-width: 600px) {
97 | .actions-container-mobile {
98 | display: flex;
99 | justify-content: space-evenly;
100 | position: fixed;
101 | bottom: 10%;
102 | right: 10%;
103 | }
104 |
105 | .actions-container-mobile > a {
106 | flex: 1;
107 | margin: 0 1rem 0 1rem;
108 | -webkit-animation: fade-controls-in 0.5s ease-in;
109 | animation: fade-controls-in 0.5s ease-in;
110 | }
111 | }
112 |
113 | @keyframes fade-controls-in {
114 | 0% { -webkit-transform: scale(0, 0); transform: scale(0, 0); }
115 | 100% { -webkit-transform: scale(1, 1); transform: scale(1, 1); }
116 | }
117 |
--------------------------------------------------------------------------------
/assets/css/header.css:
--------------------------------------------------------------------------------
1 | .nav-container {
2 | display: -webkit-box;
3 | display: -ms-flexbox;
4 | display: flex;
5 | -webkit-box-orient: horizontal;
6 | -webkit-box-direction: normal;
7 | -ms-flex-direction: row;
8 | flex-direction: row;
9 | -webkit-box-align: center;
10 | -ms-flex-align: center;
11 | align-items: center;
12 | -webkit-box-pack: center;
13 | -ms-flex-pack: center;
14 | justify-content: center;
15 | -ms-flex-wrap: nowrap;
16 | flex-wrap: nowrap;
17 | padding-top: 4em;
18 | padding-bottom: 4em;
19 | }
20 |
21 | .nav-dropdown {
22 | width: 100%;
23 | margin-top: 0;
24 | padding-top: 0;
25 | margin-bottom: 0;
26 | border: 0;
27 | }
28 |
29 | .logo-container {
30 | -webkit-box-flex: 2;
31 | -ms-flex: 2;
32 | flex: 2;
33 | display: -webkit-box;
34 | display: -ms-flexbox;
35 | display: flex;
36 | -webkit-box-align: center;
37 | -ms-flex-align: center;
38 | align-items: center;
39 | -webkit-box-pack: center;
40 | -ms-flex-pack: center;
41 | justify-content: center;
42 | }
43 |
44 | .logo {
45 | font-size: 2.25em;
46 | }
47 |
48 | .filler {
49 | -webkit-box-flex: 1;
50 | -ms-flex: 1;
51 | flex: 1;
52 | height: inherit;
53 | display: -webkit-box;
54 | display: -ms-flexbox;
55 | display: flex;
56 | -webkit-box-pack: center;
57 | -ms-flex-pack: center;
58 | justify-content: center;
59 | }
60 |
61 | .nav-dropdown-item {
62 | width: 100%;
63 | text-align: center;
64 | color: var(--teal);
65 | }
66 |
67 | .nav-links {
68 | height: inherit;
69 | -webkit-box-flex: 1;
70 | -ms-flex: 1;
71 | flex: 1;
72 | display: -webkit-box;
73 | display: -ms-flexbox;
74 | display: flex;
75 | -webkit-box-pack: center;
76 | -ms-flex-pack: center;
77 | justify-content: center;
78 | }
79 |
80 | .dropdown-menu {
81 | -webkit-transition: max-height 1s;
82 | -o-transition: max-height 1s;
83 | transition: max-height 1s;
84 | }
85 |
86 | .cursor-pointer {
87 | cursor: pointer;
88 | }
89 |
90 | nav ul a {
91 | font-size: 1.4em;
92 | }
93 |
94 | .active-link a {
95 | color: #1de9b6;
96 | background-color: #004d40;
97 | text-decoration: underline;
98 | -webkit-transition: -webkit-transform 0.5s ease-out;
99 | transition: -webkit-transform 0.5s ease-out;
100 | -o-transition: transform 0.5s ease-out;
101 | transition: transform 0.5s ease-out;
102 | transition: transform 0.5s ease-out, -webkit-transform 0.5s ease-out;
103 | -webkit-transform: scale(1.25);
104 | -ms-transform: scale(1.25);
105 | transform: scale(1.25);
106 | }
107 |
108 | nav ul a:hover {
109 | background-color: #004d40;
110 | }
111 |
112 | .material-icons.nav-dropdown-btn {
113 | font-size: 3em;
114 | margin-right: 0.5em;
115 | cursor: pointer;
116 | }
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Presence.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Presence exposing (Presence, create, map, onChange, onJoins, onLeaves)
2 |
3 | {-| Presence is an extension for channels to support the Presence feature of Phoenix.
4 |
5 |
6 | # Definition
7 |
8 | @docs Presence
9 |
10 |
11 | # Helpers
12 |
13 | @docs init, withPayload, on, onJoin, onRequestJoin, onJoinError, onError, onDisconnect, onRejoin, onLeave, onLeaveError, withDebug, map
14 | @docs init, withPayload, on, onJoin, onRequestJoin, onJoinError, onError, onDisconnect, onRejoin, onLeave, onLeaveError, withDebug, withPresence, map
15 |
16 | -}
17 |
18 | import Dict exposing (Dict)
19 | import Json.Decode as JD exposing (Decoder, Value)
20 |
21 |
22 | -- Presence
23 |
24 |
25 | {-| Representation of a Presence configuration
26 | -}
27 | type alias Presence msg =
28 | PhoenixPresence msg
29 |
30 |
31 | type alias PhoenixPresence msg =
32 | { onChange : Maybe (Dict String (List Value) -> msg)
33 | , onJoins : Maybe (Dict String (List Value) -> msg)
34 | , onLeaves : Maybe (Dict String (List Value) -> msg)
35 | }
36 |
37 |
38 | {-| Create a Presence configuration
39 | -}
40 | create : PhoenixPresence msg
41 | create =
42 | { onChange = Nothing
43 | , onJoins = Nothing
44 | , onLeaves = Nothing
45 | }
46 |
47 |
48 | {-| This will be called each time the Presence state changes. The `Dict` contains as keys your presence keys and
49 | as values a list of the payloads you sent from the server.
50 | If you have on the elixir side `Presence.track(socket, user_name, %{online_at: now()})`
51 | then an example would be a Dict with
52 |
53 | { "user1": [{online_at: 1491493666123}]
54 | , "user2": [{online_at: 1491492646123}, {online_at: 1491492646624}]
55 | }
56 |
57 | -}
58 | onChange : (Dict String (List Value) -> msg) -> PhoenixPresence msg -> PhoenixPresence msg
59 | onChange func presence =
60 | { presence | onChange = Just func }
61 |
62 |
63 | {-| This will be called each time user some user joins. This callback is useful to have special events
64 | if a user joins. To obtain a list of all users use `onChange`.
65 | -}
66 | onJoins : (Dict String (List Value) -> msg) -> PhoenixPresence msg -> PhoenixPresence msg
67 | onJoins func presence =
68 | { presence | onJoins = Just func }
69 |
70 |
71 | {-| This will be called each time user some user leaves. This callback is useful to have special events
72 | if a user leaves. To obtain a list of all users use `onChange`.
73 | -}
74 | onLeaves : (Dict String (List Value) -> msg) -> PhoenixPresence msg -> PhoenixPresence msg
75 | onLeaves func presence =
76 | { presence | onLeaves = Just func }
77 |
78 |
79 | {-| Maps the callbacks
80 | -}
81 | map : (a -> b) -> PhoenixPresence a -> PhoenixPresence b
82 | map func pres =
83 | let
84 | f =
85 | Maybe.map ((<<) func)
86 | in
87 | { pres | onChange = f pres.onChange, onJoins = f pres.onJoins, onLeaves = f pres.onLeaves }
88 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Room.elm:
--------------------------------------------------------------------------------
1 | module Data.Room exposing (..)
2 |
3 | import Data.Card as Card exposing (Card)
4 | import Data.Player as Player exposing (Player, TablePlayer, Username, tablePlayerDecoder, usernameDecoder)
5 | import Dict as Dict exposing (Dict)
6 | import Json.Decode as Decode exposing (Decoder)
7 | import Json.Decode.Pipeline exposing (decode, optional, required)
8 |
9 |
10 | type alias Room =
11 | { active : Maybe Username
12 | , currentBigBlind : Maybe Int
13 | , currentSmallBlind : Maybe Int
14 | , state : String
15 | , paid : ChipTracker
16 | , toCall : Int
17 | , players : List TablePlayer
18 | , chipRoll : ChipTracker
19 | , seating : List Seating
20 | , playerHands : List PlayerHand
21 | , round : ChipTracker
22 | , pot : Int
23 | , table : Table
24 | , leaving : List Username
25 | }
26 |
27 |
28 | type alias ChipTracker =
29 | Dict String Int
30 |
31 |
32 | type alias Seating =
33 | { name : Username
34 | , position : Int
35 | }
36 |
37 |
38 | type alias PlayerHand =
39 | { player : Username
40 | , hand : List Card
41 | }
42 |
43 |
44 | type alias Table =
45 | List Card
46 |
47 |
48 | decoder : Decoder Room
49 | decoder =
50 | decode Room
51 | |> required "active" (Decode.nullable usernameDecoder)
52 | |> required "current_big_blind" (Decode.nullable Decode.int)
53 | |> required "current_small_blind" (Decode.nullable Decode.int)
54 | |> required "state" Decode.string
55 | |> required "paid" chipTrackerDecoder
56 | |> required "to_call" Decode.int
57 | |> required "players" (Decode.list tablePlayerDecoder)
58 | |> required "chip_roll" chipTrackerDecoder
59 | |> required "seating" (Decode.list seatingDecoder)
60 | |> required "player_hands" (Decode.list playerHandDecoder)
61 | |> required "round" chipTrackerDecoder
62 | |> required "pot" Decode.int
63 | |> required "table" (Decode.list Card.decoder)
64 | |> optional "leaving" (Decode.list usernameDecoder) []
65 |
66 |
67 | chipTrackerDecoder : Decoder ChipTracker
68 | chipTrackerDecoder =
69 | Decode.dict Decode.int
70 |
71 |
72 | seatingDecoder : Decoder Seating
73 | seatingDecoder =
74 | decode Seating
75 | |> required "name" usernameDecoder
76 | |> required "position" Decode.int
77 |
78 |
79 | playerHandDecoder : Decoder PlayerHand
80 | playerHandDecoder =
81 | decode PlayerHand
82 | |> required "player" usernameDecoder
83 | |> required "hand" (Decode.list Card.decoder)
84 |
85 |
86 | defaultRoom : Room
87 | defaultRoom =
88 | { active = Nothing
89 | , currentBigBlind = Nothing
90 | , currentSmallBlind = Nothing
91 | , state = "idle"
92 | , paid = Dict.empty
93 | , toCall = 0
94 | , players = []
95 | , chipRoll = Dict.empty
96 | , seating = []
97 | , playerHands = []
98 | , round = Dict.empty
99 | , pot = 0
100 | , table = []
101 | , leaving = []
102 | }
103 |
--------------------------------------------------------------------------------
/assets/elm/src/Widgets/PlayerToolbar.elm:
--------------------------------------------------------------------------------
1 | module Widgets.PlayerToolbar exposing (Config, view, viewMobile, viewMobileMenu)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (class)
5 | import Html.Events exposing (onClick)
6 |
7 |
8 | type alias Config msg =
9 | { joinLeaveMsg : msg
10 | , btnText : String
11 | , actionPressedMsg : msg
12 | , isActive : Bool
13 | , bankPressedMsg : msg
14 | , accountPressedMsg : msg
15 | , chatPressedMsg : msg
16 | , mobileToolbarPressed : msg
17 | , closeModalMsg : msg
18 | , isLeaving : Bool
19 | }
20 |
21 |
22 | view : Config msg -> Html msg
23 | view config =
24 | case config.isLeaving of
25 | True ->
26 | div [ class "controls-container controls-disabled" ]
27 | [ h4 [ class "controls-msg" ]
28 | [ text "You have left the game"]
29 | ]
30 | False ->
31 | div [ class "controls-container" ]
32 | [ li [ class "control-item" ]
33 | [ a [ onClick config.joinLeaveMsg ] [ text config.btnText ] ]
34 | , li [ class "control-item" ]
35 | [ viewActionBtn config ]
36 | , li [ class "control-item" ]
37 | [ a [ onClick config.accountPressedMsg ] [ text "Account" ] ]
38 | , li [ class "control-item" ]
39 | [ a [ onClick config.chatPressedMsg ] [ text "Chat" ] ]
40 | , li [ class "control-item" ]
41 | [ a [ onClick config.bankPressedMsg ] [ text "Bank" ] ]
42 | ]
43 |
44 |
45 | viewActionBtn : Config msg -> Html msg
46 | viewActionBtn config =
47 | if config.isActive then
48 | a [ onClick config.actionPressedMsg, class "control-active" ] [ text "Actions" ]
49 | else
50 | a [] [ text "" ]
51 |
52 |
53 | viewMobile : Config msg -> Html msg
54 | viewMobile config =
55 | div
56 | [ class "mobile-controls-container btn-floating btn-large white-text red waves-effect"
57 | , onClick config.mobileToolbarPressed
58 | ]
59 | [ i [ class "material-icons" ] [ text "add" ] ]
60 |
61 |
62 | viewMobileMenu : Config msg -> Html msg
63 | viewMobileMenu config =
64 | let
65 | baseClasses =
66 | "collection-item mobile-menu-item green-text"
67 | in
68 | ul [ class "collection" ]
69 | [ li [ class baseClasses ]
70 | [ a [ onClick config.joinLeaveMsg ] [ text config.btnText ]
71 | ]
72 | , li [ class baseClasses ]
73 | [ a [ onClick config.accountPressedMsg ] [ text "Account" ]
74 | ]
75 | , li [ class baseClasses, onClick config.chatPressedMsg ]
76 | [ a [ onClick config.chatPressedMsg ] [ text "Chat" ]
77 | ]
78 | , li [ class baseClasses, onClick config.bankPressedMsg ]
79 | [ a [ onClick config.bankPressedMsg ] [ text "Bank" ]
80 | ]
81 | , i [ class "material-icons close-modal close-mobile-menu", onClick config.closeModalMsg ]
82 | [ text "close" ]
83 | ]
--------------------------------------------------------------------------------
/assets/elm/src/Page/Room/SocketConfig.elm:
--------------------------------------------------------------------------------
1 | module Page.Room.SocketConfig exposing (..)
2 |
3 | import Data.AuthToken as AuthToken
4 | import Data.Player as Player exposing (Player)
5 | import Data.RoomPage as RoomPage exposing (RoomPage)
6 | import Data.Session as Session exposing (Session)
7 | import Json.Encode as Encode
8 | import Page.Room.Helpers as Helpers exposing (..)
9 | import Phoenix.Channel as Channel exposing (Channel)
10 | import Phoenix.Socket as Socket exposing (Socket)
11 | import Types.Room.Messages as Messages exposing (..)
12 |
13 |
14 | type alias Msg =
15 | RoomMsg
16 |
17 |
18 | type alias Model =
19 | RoomPage
20 |
21 |
22 | socket : Session -> String -> Socket Msg
23 | socket session socketUrl =
24 | let
25 | params =
26 | case session.player of
27 | Just player ->
28 | let
29 | token =
30 | AuthToken.authTokenToString player.token
31 | in
32 | [ ( "guardian_token", token ) ]
33 |
34 | Nothing ->
35 | []
36 | in
37 | Socket.init socketUrl
38 | |> Socket.withParams params
39 | |> Socket.onOpen SocketOpened
40 | |> Socket.onClose (\_ -> SocketClosed)
41 | |> Socket.onAbnormalClose (\_ -> SocketClosedAbnormally)
42 |
43 |
44 | room : Model -> Channel Msg
45 | room model =
46 | Channel.init ("games:" ++ model.room)
47 | |> Channel.withPayload (Encode.object [ ( "type", Encode.string model.roomType ), ( "amount", Encode.int <| joinValToInt model.joinValue ) ])
48 | |> roomChannel
49 |
50 |
51 | privateRoom : String -> Player -> Channel Msg
52 | privateRoom roomTitle player =
53 | Channel.init ("games:" ++ roomTitle)
54 | |> Channel.withPayload (Encode.object [ ( "type", Encode.string "private" ), ( "amount", Encode.int 0 ) ])
55 | |> roomChannel
56 |
57 |
58 | roomChannel : Channel Msg -> Channel Msg
59 | roomChannel channel =
60 | channel
61 | |> Channel.onJoin (\_ -> JoinedChannel)
62 | |> Channel.onJoinError (\json -> JoinFailed json)
63 | |> Channel.onRejoin (\json -> Rejoined json)
64 | |> Channel.on "update" (\payload -> Update payload)
65 | |> Channel.on "game_started" (\payload -> GameStarted payload)
66 | |> Channel.on "winner_message" (\payload -> WinnerMessage payload)
67 | |> Channel.on "present_winning_hand" (\payload -> PresentWinningHand payload)
68 | |> Channel.on "clear_ui" Clear
69 | |> Channel.on "bank_info" (\payload -> SetBankInfo payload)
70 | |> Channel.on "new_chat_msg" (\payload -> NewChatMsg payload)
71 | |> Channel.on "new_message" (\payload -> NewMessage payload)
72 |
73 |
74 | playerInfoChannel : Player -> Channel Msg
75 | playerInfoChannel player =
76 | Channel.init ("players:" ++ Player.usernameToString player.username)
77 | |> Channel.onJoin (\_ -> ConnectedToPlayerChannel)
78 | |> Channel.on "chip_info" (\payload -> ChipInfo payload)
79 |
80 |
81 |
82 | -- Channel helpers
83 |
84 |
85 | subscribeToChannels : Player -> String -> String -> List (Channel Msg)
86 | subscribeToChannels player roomTitle roomType =
87 | case roomType of
88 | "private" ->
89 | [ playerInfoChannel player, privateRoom roomTitle player ]
90 |
91 | _ ->
92 | [ playerInfoChannel player ]
93 |
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Internal/Helpers.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Internal.Helpers exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import Json.Decode as Decode exposing (Value)
5 | import Task exposing (Task)
6 |
7 |
8 | getIn : comparable -> comparable_ -> Dict comparable (Dict comparable_ value) -> Maybe value
9 | getIn a b dict =
10 | Dict.get a dict
11 | |> Maybe.andThen (Dict.get b)
12 |
13 |
14 | updateIn : comparable -> comparable_ -> (Maybe value -> Maybe value) -> Dict comparable (Dict comparable_ value) -> Dict comparable (Dict comparable_ value)
15 | updateIn a b update dict =
16 | let
17 | update_ maybeDict =
18 | let
19 | dict_ =
20 | Dict.update b update (Maybe.withDefault Dict.empty maybeDict)
21 | in
22 | if Dict.isEmpty dict_ then
23 | Nothing
24 | else
25 | Just dict_
26 | in
27 | Dict.update a update_ dict
28 |
29 |
30 | insertIn : comparable -> comparable_ -> value -> Dict comparable (Dict comparable_ value) -> Dict comparable (Dict comparable_ value)
31 | insertIn a b value dict =
32 | let
33 | update_ maybeValue =
34 | case maybeValue of
35 | Nothing ->
36 | Just (Dict.singleton b value)
37 |
38 | Just dict_ ->
39 | Just (Dict.insert b value dict_)
40 | in
41 | Dict.update a update_ dict
42 |
43 |
44 | removeIn : comparable -> comparable_ -> Dict comparable (Dict comparable_ value) -> Dict comparable (Dict comparable_ value)
45 | removeIn a b dict =
46 | let
47 | remove maybeDict_ =
48 | case maybeDict_ of
49 | Nothing ->
50 | Nothing
51 |
52 | Just dict_ ->
53 | let
54 | newDict =
55 | Dict.remove b dict_
56 | in
57 | if Dict.isEmpty newDict then
58 | Nothing
59 | else
60 | Just newDict
61 | in
62 | Dict.update a remove dict
63 |
64 |
65 | add : a -> Maybe (List a) -> Maybe (List a)
66 | add value maybeList =
67 | case maybeList of
68 | Nothing ->
69 | Just [ value ]
70 |
71 | Just list ->
72 | Just (value :: list)
73 |
74 |
75 | decodeReplyPayload : Value -> Maybe (Result Value Value)
76 | decodeReplyPayload value =
77 | let
78 | result =
79 | Decode.decodeValue (Decode.field "status" Decode.string |> Decode.andThen statusInfo)
80 | value
81 | in
82 | case result of
83 | Err err ->
84 | let
85 | _ =
86 | Debug.log err
87 | in
88 | Nothing
89 |
90 | Ok payload ->
91 | Just payload
92 |
93 |
94 | statusInfo : String -> Decode.Decoder (Result Value Value)
95 | statusInfo status =
96 | case status of
97 | "ok" ->
98 | Decode.map Ok (Decode.field "response" Decode.value)
99 |
100 | "error" ->
101 | Decode.map Err (Decode.field "response" Decode.value)
102 |
103 | _ ->
104 | Decode.fail (status ++ " is a not supported status")
105 |
106 |
107 | (&>) : Task b a -> Task b c -> Task b c
108 | (&>) t1 t2 =
109 | Task.andThen (\_ -> t2) t1
110 |
111 |
112 | (<&>) : Task b a -> (a -> Task b c) -> Task b c
113 | (<&>) x f =
114 | Task.andThen f x
115 |
--------------------------------------------------------------------------------
/assets/elm/src/Views/Header.elm:
--------------------------------------------------------------------------------
1 | module Views.Header exposing (..)
2 |
3 | import Data.Player as Player exposing (Player)
4 | import Data.Session as Session exposing (Session)
5 | import Html exposing (..)
6 | import Html.Attributes exposing (..)
7 | import Html.Events exposing (Options, onClick)
8 | import Html.Lazy exposing (lazy2)
9 | import Route exposing (Route)
10 | import Types.Dropdowns as DropdownType exposing (DropdownItem, DropdownMsg, DropdownNavbarLink, OpenDropdown)
11 | import Types.Page as Page exposing (Page)
12 | import Util exposing (onClickStopPropagation)
13 | import Views.Helpers as Helpers exposing (ActivePage)
14 | import Widgets.Dropdown as Dropdown
15 |
16 |
17 | type alias NavDropdownInfo r =
18 | { r
19 | | openDropdown : OpenDropdown
20 | , selectedItem : DropdownItem
21 | }
22 |
23 |
24 | viewNavBarLinks : msg -> Session -> ActivePage -> List (Html msg)
25 | viewNavBarLinks msg session page =
26 | case session.player of
27 | Just player ->
28 | [ li [] [ a [ onClick msg ] [ text "Signout" ] ]
29 | , navBarLink (page == Helpers.Profile)
30 | (Route.Profile <| Player.usernameToString player.username)
31 | [ text "Profile" ]
32 | , navBarLink (page == Helpers.Rooms) Route.Rooms [ text "Rooms" ]
33 | ]
34 |
35 | Nothing ->
36 | [ navBarLink (page == Helpers.Login) Route.Login [ text "Login" ]
37 | , navBarLink (page == Helpers.Registration) Route.Register [ text "Signup" ]
38 | ]
39 |
40 |
41 | navBarLink : Bool -> Route -> List (Html msg) -> Html msg
42 | navBarLink isActive route linkContent =
43 | li [ classList [ ( "active-link", isActive ) ] ]
44 | [ a [ Route.href route ] linkContent ]
45 |
46 |
47 | activePageFrom : Page -> ActivePage
48 | activePageFrom page =
49 | case page of
50 | Page.Login _ ->
51 | Helpers.Login
52 |
53 | Page.Register _ ->
54 | Helpers.Registration
55 |
56 | Page.Home _ ->
57 | Helpers.Home
58 |
59 | Page.Room _ ->
60 | Helpers.Room
61 |
62 | Page.Rooms _ ->
63 | Helpers.Rooms
64 |
65 | Page.Profile _ ->
66 | Helpers.Profile
67 |
68 | _ ->
69 | Helpers.Other
70 |
71 |
72 |
73 | -- NavDropdown --
74 |
75 |
76 | navDropdownConfig : Dropdown.Config DropdownMsg
77 | navDropdownConfig =
78 | { topLevelHtml =
79 | i
80 | [ class "material-icons nav-dropdown-btn hide-on-large-only"
81 | , onClick (DropdownType.Toggle DropdownType.NavBarDropdown)
82 | ]
83 | [ text "reorder" ]
84 | , clickedMsg = DropdownType.Toggle DropdownType.NavBarDropdown
85 | , itemPickedMsg = DropdownType.NavItemPicked
86 | }
87 |
88 |
89 | navDropdownContext : NavDropdownInfo r -> Dropdown.Context
90 | navDropdownContext model =
91 | { selectedItem = model.selectedItem
92 | , isOpen = model.openDropdown == DropdownType.NavBarDropdown
93 | }
94 |
95 |
96 | navLinks : Session -> List DropdownNavbarLink
97 | navLinks session =
98 | case session.player of
99 | Just player ->
100 | [ DropdownType.Logout, DropdownType.Profile, DropdownType.Rooms ]
101 |
102 | Nothing ->
103 | [ DropdownType.Login, DropdownType.Register ]
104 |
105 |
106 |
107 | --This id should come from index.Html
108 | --You can use it to scroll to the top of the page (by ID)
109 | --when switching pages in the pagination sense
110 |
111 |
112 | bodyId : String
113 | bodyId =
114 | "page-body"
115 |
--------------------------------------------------------------------------------
/assets/elm/src/Request/Player.elm:
--------------------------------------------------------------------------------
1 | module Request.Player exposing (..)
2 |
3 | import Data.AuthToken as AuthToken exposing (AuthToken, withAuthorization)
4 | import Data.PasswordReset as PasswordReset exposing (PasswordReset)
5 | import Data.Player as Player exposing (Player, Username, encodeUsername)
6 | import Http
7 | import HttpBuilder exposing (RequestBuilder, withExpect, withQueryParams)
8 | import Json.Decode as Decode
9 | import Json.Encode as Encode
10 | import Json.Encode.Extra as EncodeExtra
11 | import Ports
12 |
13 |
14 | storeSession : Player -> Cmd msg
15 | storeSession player =
16 | Player.encode player
17 | |> Encode.encode 0
18 | |> Just
19 | |> Ports.storeSession
20 |
21 |
22 | type alias Registration r =
23 | { r
24 | | username : String
25 | , password : String
26 | , email : String
27 | , firstName : String
28 | , lastName : String
29 | , blurb : String
30 | , apiUrl : String
31 | }
32 |
33 |
34 |
35 | type alias ResetPassword r =
36 | { r
37 | | resetToken : String
38 | , newPassword : String
39 | , apiUrl : String
40 | }
41 |
42 |
43 | login : { r | username : String, password : String, apiUrl : String } -> Http.Request Player
44 | login { username, password, apiUrl } =
45 | let
46 | player =
47 | Encode.object
48 | [ ( "username", Encode.string username )
49 | , ( "password", Encode.string password )
50 | ]
51 |
52 | body =
53 | Encode.object [ ( "player", player ) ]
54 | |> Http.jsonBody
55 | in
56 | Decode.field "player" Player.decoder
57 | |> Http.post (apiUrl ++ "/sessions") body
58 |
59 |
60 | facebookLogin : Encode.Value -> String -> Http.Request Player
61 | facebookLogin playerData apiUrl =
62 | Decode.field "player" Player.decoder
63 | |> Http.post (apiUrl ++ "/auth") (playerData |> Http.jsonBody)
64 |
65 |
66 | passwordReset : { r | email : String, apiUrl : String } -> Http.Request PasswordReset
67 | passwordReset data =
68 | let
69 | apiUrl =
70 | data.apiUrl
71 |
72 | body =
73 | Encode.object [ ( "email", Encode.string data.email ) ]
74 | |> Http.jsonBody
75 | in
76 | Decode.field "data" PasswordReset.decoder
77 | |> Http.post (apiUrl ++ "/forgot_password") body
78 |
79 |
80 | resetPassword : ResetPassword r -> Http.Request Player
81 | resetPassword data =
82 | let
83 | apiUrl =
84 | data.apiUrl
85 |
86 | body =
87 | Encode.object
88 | [ ( "password", Encode.string data.newPassword )
89 | , ( "reset_token", Encode.string data.resetToken )
90 | ]
91 | |> Http.jsonBody
92 | in
93 | Decode.field "player" Player.decoder
94 | |> Http.post (apiUrl ++ "/reset_password") body
95 |
96 |
97 | register : Registration r -> Http.Request Player
98 | register registration =
99 | let
100 | apiUrl =
101 | registration.apiUrl
102 |
103 | registrationAttrs =
104 | Encode.object
105 | [ ( "name", Encode.string registration.username )
106 | , ( "email", Encode.string registration.email )
107 | , ( "first_name", Encode.string registration.firstName )
108 | , ( "last_name", Encode.string registration.lastName )
109 | , ( "blurb", Encode.string registration.blurb )
110 | , ( "password", Encode.string registration.password )
111 | ]
112 |
113 | body =
114 | Encode.object [ ( "registration", registrationAttrs ) ]
115 | |> Http.jsonBody
116 | in
117 | Decode.field "player" Player.decoder
118 | |> Http.post (apiUrl ++ "/registrations") body
119 |
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Internal/Channel.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Internal.Channel exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import Json.Decode as Decode exposing (Value)
5 | import Phoenix.Channel as Channel
6 | import Phoenix.Internal.Helpers as Helpers
7 | import Phoenix.Internal.Message as Message exposing (Message)
8 | import Phoenix.Internal.Presence as Presence exposing (PresenceState)
9 |
10 |
11 | {-| The current channel state. This is completely handled by the effect manager.
12 | -}
13 | type State
14 | = Closed
15 | | Joining
16 | | Joined
17 | | Errored
18 | | Disconnected
19 |
20 |
21 | type alias InternalChannel msg =
22 | { state : State, presenceState : PresenceState, channel : Channel.Channel msg }
23 |
24 |
25 | type alias Endpoint =
26 | String
27 |
28 |
29 | type alias Topic =
30 | String
31 |
32 |
33 | type alias Event =
34 | String
35 |
36 |
37 | map : (a -> b) -> InternalChannel a -> InternalChannel b
38 | map func { state, presenceState, channel } =
39 | InternalChannel state presenceState (Channel.map func channel)
40 |
41 |
42 | joinMessage : InternalChannel msg -> Message
43 | joinMessage { channel } =
44 | let
45 | base =
46 | Message.init channel.topic "phx_join"
47 | in
48 | case channel.payload of
49 | Nothing ->
50 | base
51 |
52 | Just payload_ ->
53 | Message.payload payload_ base
54 |
55 |
56 | leaveMessage : InternalChannel msg -> Message
57 | leaveMessage { channel } =
58 | Message.init channel.topic "phx_leave"
59 |
60 |
61 |
62 | -- GETTER, SETTER, UPDATER
63 |
64 |
65 | type alias InternalChannelsDict msg =
66 | Dict Endpoint (Dict Topic (InternalChannel msg))
67 |
68 |
69 | get : Endpoint -> Topic -> InternalChannelsDict msg -> Maybe (InternalChannel msg)
70 | get endpoint topic channelsDict =
71 | Helpers.getIn endpoint topic channelsDict
72 |
73 |
74 | getState : Endpoint -> Topic -> InternalChannelsDict msg -> Maybe State
75 | getState endpoint topic channelsDict =
76 | get endpoint topic channelsDict
77 | |> Maybe.map (\{ state } -> state)
78 |
79 |
80 | {-| Inserts the state, identity if channel for given endpoint topic doesn_t exist
81 | -}
82 | insertState : Endpoint -> Topic -> State -> InternalChannelsDict msg -> InternalChannelsDict msg
83 | insertState endpoint topic state dict =
84 | let
85 | update =
86 | Maybe.map (updateState state)
87 | in
88 | Helpers.updateIn endpoint topic update dict
89 |
90 |
91 | updatePresenceState : PresenceState -> InternalChannel msg -> InternalChannel msg
92 | updatePresenceState presenceState internalChannel =
93 | -- TODO: debug?
94 | InternalChannel internalChannel.state presenceState internalChannel.channel
95 |
96 |
97 | updateState : State -> InternalChannel msg -> InternalChannel msg
98 | updateState state internalChannel =
99 | if internalChannel.channel.debug then
100 | let
101 | _ =
102 | case ( state, internalChannel.state ) of
103 | ( Closed, Closed ) ->
104 | state
105 |
106 | ( Joining, Joining ) ->
107 | state
108 |
109 | ( Joined, Joined ) ->
110 | state
111 |
112 | ( Errored, Errored ) ->
113 | state
114 |
115 | ( Disconnected, Disconnected ) ->
116 | state
117 |
118 | _ ->
119 | Debug.log ("Channel \"" ++ internalChannel.channel.topic ++ "\"") state
120 | in
121 | InternalChannel state internalChannel.presenceState internalChannel.channel
122 | else
123 | InternalChannel state internalChannel.presenceState internalChannel.channel
124 |
125 |
126 | updatePayload : Maybe Value -> InternalChannel msg -> InternalChannel msg
127 | updatePayload payload { state, presenceState, channel } =
128 | InternalChannel state presenceState { channel | payload = payload }
129 |
130 |
131 | updateOn : Dict Topic (Value -> msg) -> InternalChannel msg -> InternalChannel msg
132 | updateOn on { state, presenceState, channel } =
133 | InternalChannel state presenceState { channel | on = on }
134 |
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Internal/Presence.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Internal.Presence exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import Json.Decode as JD exposing (Decoder, Value)
5 |
6 |
7 | type alias PresenceState =
8 | Dict PresenceKey PresenceStateMetaWrapper
9 |
10 |
11 | type alias PresenceDiff =
12 | { leaves : PresenceState
13 | , joins : PresenceState
14 | }
15 |
16 |
17 | type alias PresenceKey =
18 | String
19 |
20 |
21 | type alias PresenceStateMetaWrapper =
22 | { metas : List PresenceStateMetaValue }
23 |
24 |
25 | type alias PresenceStateMetaValue =
26 | { phx_ref : PresenceRef, payload : Value }
27 |
28 |
29 | type alias PresenceRef =
30 | String
31 |
32 |
33 | getPresenceState : PresenceState -> Dict String (List Value)
34 | getPresenceState presenceState =
35 | let
36 | getMetas { metas } =
37 | metas
38 |
39 | getPayload presenceKey presenceStateMetaWrapper =
40 | List.map .payload (getMetas presenceStateMetaWrapper)
41 | in
42 | Dict.map getPayload presenceState
43 |
44 |
45 | syncPresenceDiff : PresenceDiff -> PresenceState -> PresenceState
46 | syncPresenceDiff presenceDiff presenceState =
47 | let
48 | mergeJoins joins state =
49 | let
50 | mergeMetaWrappers joinMetaWrapper stateMetaWrapper =
51 | PresenceStateMetaWrapper (joinMetaWrapper.metas ++ stateMetaWrapper.metas)
52 |
53 | addedStep key joinMetaWrapper addedMetaWrappers =
54 | Dict.insert key joinMetaWrapper addedMetaWrappers
55 |
56 | retainedStep key joinMetaWrapper stateMetaWrapper addedMetaWrappers =
57 | Dict.insert key (mergeMetaWrappers joinMetaWrapper stateMetaWrapper) addedMetaWrappers
58 |
59 | unchangedStep key stateMetaWrapper addedMetaWrappers =
60 | Dict.insert key stateMetaWrapper addedMetaWrappers
61 | in
62 | Dict.merge addedStep retainedStep unchangedStep joins state Dict.empty
63 |
64 | mergeLeaves leaves state =
65 | let
66 | mergeMetaWrappers leaves stateKey stateMetaWrapper =
67 | case Dict.get stateKey leaves of
68 | Nothing ->
69 | stateMetaWrapper
70 |
71 | Just leaveMetaWrapper ->
72 | let
73 | leaveRefs =
74 | List.map .phx_ref leaveMetaWrapper.metas
75 | in
76 | stateMetaWrapper.metas
77 | |> List.filter
78 | (\metaValue ->
79 | not (List.any (\phx_ref -> metaValue.phx_ref == phx_ref) leaveRefs)
80 | )
81 | |> PresenceStateMetaWrapper
82 | in
83 | state
84 | |> Dict.map (mergeMetaWrappers leaves)
85 | |> Dict.filter (\_ metaWrapper -> metaWrapper.metas /= [])
86 | in
87 | presenceState
88 | |> mergeJoins presenceDiff.joins
89 | |> mergeLeaves presenceDiff.leaves
90 |
91 |
92 | decodePresenceDiff : Value -> Result String PresenceDiff
93 | decodePresenceDiff payload =
94 | JD.decodeValue presenceDiffDecoder payload
95 |
96 |
97 | decodePresenceState : Value -> Result String PresenceState
98 | decodePresenceState payload =
99 | JD.decodeValue presenceStateDecoder payload
100 |
101 |
102 | presenceDiffDecoder : Decoder PresenceDiff
103 | presenceDiffDecoder =
104 | JD.map2 PresenceDiff
105 | (JD.field "leaves" <| presenceStateDecoder)
106 | (JD.field "joins" <| presenceStateDecoder)
107 |
108 |
109 | presenceStateDecoder : Decoder PresenceState
110 | presenceStateDecoder =
111 | JD.dict presenceStateMetaWrapperDecoder
112 |
113 |
114 | presenceStateMetaWrapperDecoder : Decoder PresenceStateMetaWrapper
115 | presenceStateMetaWrapperDecoder =
116 | JD.map PresenceStateMetaWrapper
117 | (JD.field "metas" <| JD.list presenceStateMetaValueDecoder)
118 |
119 |
120 | presenceStateMetaValueDecoder : Decoder PresenceStateMetaValue
121 | presenceStateMetaValueDecoder =
122 | let
123 | createFinalRecord phxRef payload =
124 | JD.succeed (PresenceStateMetaValue phxRef payload)
125 |
126 | decodeWithPhxRef phxRef =
127 | JD.andThen (createFinalRecord phxRef) JD.value
128 | in
129 | JD.andThen decodeWithPhxRef (JD.field "phx_ref" JD.string)
130 |
--------------------------------------------------------------------------------
/assets/elm/src/Widgets/Pagination.elm:
--------------------------------------------------------------------------------
1 | module Widgets.Pagination exposing (Config, paginate)
2 |
3 | import Html as Html exposing (..)
4 | import Html.Attributes exposing (class)
5 | import Html.Events exposing (onClick)
6 |
7 |
8 | type alias Model r =
9 | { r
10 | | totalPages : Int
11 | , page : Int
12 | }
13 |
14 |
15 | type alias Config msg =
16 | { onClickMsg : String -> msg
17 | , linksToShow : Int
18 | }
19 |
20 |
21 | paginate : Model r -> Config msg -> Html msg
22 | paginate model config =
23 | let
24 | paginationHtml =
25 | case model.totalPages of
26 | 1 ->
27 | text ""
28 |
29 | _ ->
30 | ul [ class "pagination pagination-list" ]
31 | (List.map (\text -> viewPaginationItem model config text) (paginationText model config))
32 | in
33 | paginationHtml
34 |
35 |
36 | viewPaginationItem : Model r -> Config msg -> String -> Html msg
37 | viewPaginationItem model config paginationText =
38 | let
39 | active =
40 | case String.toInt paginationText of
41 | Ok num ->
42 | if model.page == num then
43 | "active"
44 | else
45 | ""
46 |
47 | Err _ ->
48 | ""
49 |
50 | disabledStart =
51 | if model.page == 1 && paginationText == "keyboard_arrow_left" then
52 | "disabled-page-icon"
53 | else
54 | ""
55 |
56 | disabledEnd =
57 | if model.page == model.totalPages && paginationText == "keyboard_arrow_right" then
58 | "disabled-page-icon"
59 | else
60 | ""
61 |
62 | firstPage =
63 | if paginationText == "First" && model.page == 1 then
64 | "active"
65 | else
66 | ""
67 |
68 | lastPage =
69 | if paginationText == "Last" && model.page == model.totalPages then
70 | "active"
71 | else
72 | ""
73 |
74 | extraClasses =
75 | String.join " " [ active, disabledStart, disabledEnd, firstPage, lastPage ]
76 |
77 | eventAttrs =
78 | case paginationText of
79 | "keyboard_arrow_left" ->
80 | if disabledStart == "" then
81 | [ onClick (config.onClickMsg paginationText) ]
82 | else
83 | []
84 |
85 | "keyboard_arrow_right" ->
86 | if disabledEnd == "" then
87 | [ onClick (config.onClickMsg paginationText) ]
88 | else
89 | []
90 |
91 | _ ->
92 | [ onClick (config.onClickMsg paginationText) ]
93 | in
94 | li [ class (String.trim extraClasses) ]
95 | [ a eventAttrs (viewItemText paginationText) ]
96 |
97 |
98 | viewItemText : String -> List (Html msg)
99 | viewItemText paginationText =
100 | if List.member paginationText [ "keyboard_arrow_left", "keyboard_arrow_right" ] then
101 | [ i [ class "material-icons" ] [ text paginationText ] ]
102 | else
103 | [ text paginationText ]
104 |
105 |
106 | paginationText : Model r -> Config msg -> List String
107 | paginationText model config =
108 | let
109 | currentInterval =
110 | getPaginationIntervalFor model.page model.totalPages config.linksToShow
111 | in
112 | [ "keyboard_arrow_left" ]
113 | ++ [ if model.totalPages > config.linksToShow then
114 | "First"
115 | else
116 | ""
117 | ]
118 | ++ List.map (\num -> toString num) currentInterval
119 | ++ [ if model.totalPages > config.linksToShow then
120 | "Last"
121 | else
122 | ""
123 | ]
124 | ++ [ "keyboard_arrow_right" ]
125 |
126 |
127 | getPaginationIntervalFor : Int -> Int -> Int -> List Int
128 | getPaginationIntervalFor currentPage totalPages numLinksToShow =
129 | case ( totalPages <= numLinksToShow, totalPages <= currentPage, (currentPage + numLinksToShow) >= totalPages ) of
130 | ( True, _, _ ) ->
131 | List.range 1 totalPages
132 |
133 | ( _, True, _ ) ->
134 | List.range (currentPage - numLinksToShow) currentPage
135 |
136 | ( _, _, True ) ->
137 | List.range (totalPages - numLinksToShow) totalPages
138 |
139 | _ ->
140 | List.range currentPage (currentPage + numLinksToShow)
141 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/Home.elm:
--------------------------------------------------------------------------------
1 | module Page.Home exposing (..)
2 |
3 | import Data.Player as Player exposing (Player, usernameToString)
4 | import Data.Session as Session exposing (Session)
5 | import Html exposing (..)
6 | import Html.Attributes exposing (class, style)
7 | import Route as Route exposing (Route)
8 |
9 |
10 | -- MODEL --
11 |
12 |
13 | type alias Model =
14 | { greeting : String }
15 |
16 |
17 | initialModel : Model
18 | initialModel =
19 | { greeting = "Welcome to PokerEx" }
20 |
21 |
22 |
23 | -- VIEW --
24 |
25 |
26 | view : Session -> Model -> Html msg
27 | view session model =
28 | let
29 | personalizedGreeting =
30 | case session.player of
31 | Just player ->
32 | ", " ++ (usernameToString <| player.username)
33 |
34 | Nothing ->
35 | "!"
36 | in
37 | main_ []
38 | [ div [ class "hero valign-wrapper" ]
39 | [ h1 [ class "welcome", style [ ( "text-align", "center" ) ] ]
40 | [ text (model.greeting ++ personalizedGreeting) ]
41 | , br [] []
42 | , viewButton session.player
43 | ]
44 | , div [ class "row landing" ]
45 | [ div [ class "col s12 m12 l4 landing-item" ]
46 | [ div [ class "card teal darken-2 landing-item" ]
47 | [ div [ class "card-content white-text text-center" ]
48 | [ span [ class "card-title center-align" ]
49 | [ text "Create your own poker room and invite friends" ]
50 | , p [ class "medium-text" ]
51 | [ text """Sign up to create your own private poker rooms. Invite friends
52 | or send invitations to other PokerEx players. Once you make a
53 | private poker room, your game and table state will be kept alive
54 | until you decide to close the room."""
55 | ]
56 | ]
57 | ]
58 | ]
59 | , div [ class "col s12 m12 l4 landing-item" ]
60 | [ div [ class "card cyan darken-3 landing-item" ]
61 | [ div [ class "card-content white-text text-center" ]
62 | [ span [ class "card-title center-align" ]
63 | [ text "What is PokerEx?" ]
64 | , p [ class "pop-text" ]
65 | [ text """PokerEx is an online poker environment
66 | designed to deliver an engaging, real-time
67 | experience."""
68 | ]
69 | ]
70 | ]
71 | ]
72 | , div [ class "col s12 m12 l4 landing-item" ]
73 | [ div [ class "card indigo darken-4 landing-item" ]
74 | [ div [ class "card-content white-text text-center" ]
75 | [ span [ class "card-title center-align" ]
76 | [ text "Join our public tables for a quick round" ]
77 | , p [ class "medium-text" ]
78 | [ text """Once you join us on PokerEx, you can sit down and play at
79 | any one of our public rooms. Public rooms are a perfect fit
80 | if you are looking to play a short round or two and get on
81 | your way."""
82 | ]
83 | ]
84 | ]
85 | ]
86 | ]
87 | ]
88 |
89 |
90 | viewButton : Maybe Player -> Html msg
91 | viewButton player =
92 | case player of
93 | Just player ->
94 | a [ class "waves-effect waves-light btn-large white-text margin-bottom-small", Route.href (Route.Profile (Player.usernameToString player.username)) ]
95 | [ i [ class "material-icons left large-text" ]
96 | [ text "account_box" ]
97 | , text "Go to your account"
98 | ]
99 |
100 | _ ->
101 | a [ class "waves-effect waves-light btn-large white-text margin-bottom-small", Route.href Route.Register ]
102 | [ i [ class "material-icons left large-text" ]
103 | [ text "add_circle" ]
104 | , text "Join PokerEx Now!"
105 | ]
106 |
107 |
108 |
109 | -- UPDATE --
110 |
111 |
112 | type Msg
113 | = DoNothing -- There is no reason yet for a Msg that does anything here.
114 |
115 |
116 | type ExternalMsg
117 | = NoOp
118 |
119 |
120 | update : Msg -> Model -> ( ( Model, Cmd Msg ), ExternalMsg )
121 | update msg model =
122 | case msg of
123 | _ ->
124 | ( ( model, Cmd.none ), NoOp )
125 |
--------------------------------------------------------------------------------
/assets/elm/src/Data/Card.elm:
--------------------------------------------------------------------------------
1 | module Data.Card exposing (..)
2 |
3 | import Html exposing (Html, img)
4 | import Html.Attributes exposing (class, id, src)
5 | import Json.Decode as Decode exposing (Decoder)
6 | import Json.Decode.Pipeline as Pipeline exposing (decode, required)
7 | import Json.Encode as Encode exposing (Value)
8 |
9 |
10 | type alias Card =
11 | { rank : Rank
12 | , suit : Suit
13 | }
14 |
15 |
16 | type Suit
17 | = Hearts
18 | | Spades
19 | | Clubs
20 | | Diamonds
21 | | SuitError
22 |
23 |
24 | type Rank
25 | = Two
26 | | Three
27 | | Four
28 | | Five
29 | | Six
30 | | Seven
31 | | Eight
32 | | Nine
33 | | Ten
34 | | Jack
35 | | Queen
36 | | King
37 | | Ace
38 | | RankError
39 |
40 |
41 | stringToSuit : String -> Suit
42 | stringToSuit stringSuit =
43 | case stringSuit of
44 | "hearts" ->
45 | Hearts
46 |
47 | "diamonds" ->
48 | Diamonds
49 |
50 | "spades" ->
51 | Spades
52 |
53 | "clubs" ->
54 | Clubs
55 |
56 | _ ->
57 | SuitError
58 |
59 |
60 | suitToString : Card -> String
61 | suitToString card =
62 | case card.suit of
63 | Hearts ->
64 | "hearts"
65 |
66 | Diamonds ->
67 | "diamonds"
68 |
69 | Spades ->
70 | "spades"
71 |
72 | Clubs ->
73 | "clubs"
74 |
75 | _ ->
76 | "error"
77 |
78 |
79 | stringToRank : String -> Rank
80 | stringToRank stringRank =
81 | case stringRank of
82 | "two" ->
83 | Two
84 |
85 | "three" ->
86 | Three
87 |
88 | "four" ->
89 | Four
90 |
91 | "five" ->
92 | Five
93 |
94 | "six" ->
95 | Six
96 |
97 | "seven" ->
98 | Seven
99 |
100 | "eight" ->
101 | Eight
102 |
103 | "nine" ->
104 | Nine
105 |
106 | "ten" ->
107 | Ten
108 |
109 | "jack" ->
110 | Jack
111 |
112 | "queen" ->
113 | Queen
114 |
115 | "king" ->
116 | King
117 |
118 | "ace" ->
119 | Ace
120 |
121 | _ ->
122 | RankError
123 |
124 |
125 | rankToString : Card -> String
126 | rankToString card =
127 | case card.rank of
128 | Two ->
129 | "2"
130 |
131 | Three ->
132 | "3"
133 |
134 | Four ->
135 | "4"
136 |
137 | Five ->
138 | "5"
139 |
140 | Six ->
141 | "6"
142 |
143 | Seven ->
144 | "7"
145 |
146 | Eight ->
147 | "8"
148 |
149 | Nine ->
150 | "9"
151 |
152 | Ten ->
153 | "10"
154 |
155 | Jack ->
156 | "jack"
157 |
158 | Queen ->
159 | "queen"
160 |
161 | King ->
162 | "king"
163 |
164 | Ace ->
165 | "ace"
166 |
167 | _ ->
168 | "error"
169 |
170 |
171 |
172 | -- Needs to be changed in prod
173 |
174 |
175 | rootCardsAssetsUrl : String
176 | rootCardsAssetsUrl =
177 | "https://poker-ex.herokuapp.com/images/cards/"
178 |
179 |
180 |
181 | -- Card back also needs to be changed in prod
182 |
183 |
184 | sourceUrlForCardImage : Card -> String
185 | sourceUrlForCardImage card =
186 | case ( card.rank, card.suit ) of
187 | ( RankError, SuitError ) ->
188 | "https://poker-ex.herokuapp.com/images/card-back.svg.png"
189 |
190 | _ ->
191 | rootCardsAssetsUrl ++ rankToString card ++ "_of_" ++ suitToString card ++ ".svg"
192 |
193 |
194 | tableCardImageFor : Card -> Html msg
195 | tableCardImageFor card =
196 | img [ src (sourceUrlForCardImage card), id "table-card" ] []
197 |
198 |
199 | playerHandCardImageFor : Int -> Card -> Html msg
200 | playerHandCardImageFor index card =
201 | case ( card.rank, card.suit ) of
202 | ( RankError, SuitError ) ->
203 | img
204 | [ src (sourceUrlForCardImage card)
205 | , id ("player-hand-card-" ++ toString index)
206 | , class "card-back"
207 | ]
208 | []
209 |
210 | _ ->
211 | img [ src (sourceUrlForCardImage card), id ("player-hand-card-" ++ toString index) ] []
212 |
213 |
214 | rankDecoder : Decoder Rank
215 | rankDecoder =
216 | Decode.string
217 | |> Decode.andThen (\str -> Decode.succeed (stringToRank str))
218 |
219 |
220 | suitDecoder : Decoder Suit
221 | suitDecoder =
222 | Decode.string
223 | |> Decode.andThen (\str -> Decode.succeed (stringToSuit str))
224 |
225 |
226 | decoder : Decoder Card
227 | decoder =
228 | decode Card
229 | |> required "rank" rankDecoder
230 | |> required "suit" suitDecoder
231 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/Register.elm:
--------------------------------------------------------------------------------
1 | module Page.Register exposing (..)
2 |
3 | import Data.Configuration exposing (Configuration)
4 | import Data.Player as Player exposing (Player)
5 | import Data.Session as Session exposing (Session)
6 | import Html exposing (..)
7 | import Html.Attributes exposing (..)
8 | import Html.Events exposing (..)
9 | import Http
10 | import Json.Decode as Decode exposing (Decoder, decodeString, field, string)
11 | import Json.Decode.Pipeline as Pipeline exposing (decode, optional)
12 | import Ports
13 | import Request.Player exposing (storeSession)
14 | import Route
15 | import Validate exposing (..)
16 | import Views.Form as Form
17 | import Widgets.FacebookLogin as FBLogin
18 |
19 |
20 | -- Model --
21 |
22 |
23 | type alias Model =
24 | { errors : List Error
25 | , username : String
26 | , password : String
27 | , firstName : String
28 | , lastName : String
29 | , blurb : String
30 | , email : String
31 | , apiUrl : String
32 | }
33 |
34 |
35 | type alias Error =
36 | ( Field, String )
37 |
38 |
39 | initialModel : Configuration -> Model
40 | initialModel envConfig =
41 | { errors = []
42 | , username = ""
43 | , password = ""
44 | , firstName = ""
45 | , lastName = ""
46 | , blurb = ""
47 | , email = ""
48 | , apiUrl = envConfig.apiUrl
49 | }
50 |
51 |
52 |
53 | -- VIEW --
54 |
55 |
56 | view : Session -> Model -> Html Msg
57 | view session model =
58 | div [ class "auth-page", style [ ( "text-align", "center" ) ] ]
59 | [ div [ class "auth-form card-panel z-depth-4 rounded" ]
60 | [ Form.viewErrors model.errors
61 | , viewForm model
62 | , FBLogin.viewFBLogin LoginWithFB
63 | ]
64 | ]
65 |
66 |
67 | viewForm : Model -> Html Msg
68 | viewForm model =
69 | Html.form [ onSubmit SubmitForm ]
70 | [ inputFor model.username "Username" Username "text"
71 | , inputFor model.password "Password" Password "password"
72 | , inputFor model.firstName "First Name" FirstName "text"
73 | , inputFor model.lastName "Last Name" LastName "text"
74 | , inputFor model.blurb "Message" Blurb "text"
75 | , inputFor model.email "Email" Email "text"
76 | , button [ class "btn blue waves-effect" ]
77 | [ text "Sign Up" ]
78 | ]
79 |
80 |
81 | inputFor : String -> String -> (String -> RegistrationAttr) -> String -> Html Msg
82 | inputFor val holder attr inputType =
83 | input
84 | [ placeholder holder
85 | , type_ inputType
86 | , value val
87 | , autocomplete False
88 | , onInput (\s -> Set (attr s))
89 | ]
90 | []
91 |
92 |
93 |
94 | -- UPDATE --
95 |
96 |
97 | type Msg
98 | = SubmitForm
99 | | Set RegistrationAttr
100 | | RegistrationCompleted (Result Http.Error Player)
101 | | LoginWithFB
102 |
103 |
104 | type ExternalMsg
105 | = NoOp
106 | | SetPlayer Player
107 |
108 |
109 | type RegistrationAttr
110 | = Username String
111 | | Password String
112 | | FirstName String
113 | | LastName String
114 | | Blurb String
115 | | Email String
116 |
117 |
118 | type Field
119 | = Name
120 | | Pass
121 | | E_mail
122 | | Server
123 |
124 |
125 | validate : Model -> List Error
126 | validate =
127 | Validate.all
128 | [ .username >> ifBlank ( Name, "Username can't be blank." )
129 | , .password >> ifBlank ( Pass, "Password can't be blank." )
130 | , .email >> ifBlank ( E_mail, "Email can't be blank." )
131 | ]
132 |
133 |
134 | update : Msg -> Model -> ( ( Model, Cmd Msg ), ExternalMsg )
135 | update msg model =
136 | case msg of
137 | SubmitForm ->
138 | case validate model of
139 | [] ->
140 | ( ( model, Http.send RegistrationCompleted (Request.Player.register model) ), NoOp )
141 |
142 | errors ->
143 | ( ( { model | errors = errors }, Cmd.none ), NoOp )
144 |
145 | Set (Username name) ->
146 | ( ( { model | username = name }, Cmd.none ), NoOp )
147 |
148 | Set (Password password) ->
149 | ( ( { model | password = password }, Cmd.none ), NoOp )
150 |
151 | Set (FirstName firstName) ->
152 | ( ( { model | firstName = firstName }, Cmd.none ), NoOp )
153 |
154 | Set (LastName lastName) ->
155 | ( ( { model | lastName = lastName }, Cmd.none ), NoOp )
156 |
157 | Set (Blurb blurb) ->
158 | ( ( { model | blurb = blurb }, Cmd.none ), NoOp )
159 |
160 | Set (Email email) ->
161 | ( ( { model | email = email }, Cmd.none ), NoOp )
162 |
163 | RegistrationCompleted (Err error) ->
164 | ( ( { model | errors = [ ( Server, toString error ) ] }, Cmd.none ), NoOp )
165 |
166 | RegistrationCompleted (Ok player) ->
167 | ( ( model, Cmd.batch [ storeSession player, Route.modifyUrl Route.Home ] ), SetPlayer player )
168 |
169 | LoginWithFB ->
170 | ( ( model, Ports.loginWithFB () ), NoOp )
171 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/Login.elm:
--------------------------------------------------------------------------------
1 | module Page.Login exposing (ExternalMsg(..), Model, Msg, initialModel, update, view)
2 |
3 | import Data.Configuration exposing (Configuration)
4 | import Data.Player as Player exposing (Player)
5 | import Data.Session as Session exposing (Session)
6 | import Html exposing (..)
7 | import Html.Attributes exposing (..)
8 | import Html.Events exposing (..)
9 | import Http
10 | import Json.Decode as Decode exposing (Decoder, decodeString, field, string)
11 | import Json.Decode.Pipeline as Pipeline exposing (decode, optional)
12 | import Ports
13 | import Request.Player exposing (storeSession)
14 | import Route
15 | import Validate exposing (..)
16 | import Views.Form as Form
17 | import Widgets.FacebookLogin as FBLogin
18 |
19 |
20 | -- Model --
21 |
22 |
23 | type alias Model =
24 | { errors : List Error
25 | , username : String
26 | , password : String
27 | , apiUrl : String
28 | }
29 |
30 |
31 | type alias Error =
32 | ( Field, String )
33 |
34 |
35 | initialModel : Configuration -> Model
36 | initialModel envConfig =
37 | { errors = []
38 | , username = ""
39 | , password = ""
40 | , apiUrl = envConfig.apiUrl
41 | }
42 |
43 |
44 |
45 | -- View --
46 |
47 |
48 | view : Session -> Model -> Html Msg
49 | view session model =
50 | div [ class "auth-page", style [ ( "text-align", "center" ) ] ]
51 | [ div [ class "auth-form card-panel z-depth-4 rounded" ]
52 | [ Form.viewErrors model.errors
53 | , viewForm model
54 | , FBLogin.viewFBLogin LoginWithFb
55 | , br [] []
56 | , div [ class "forgot-password-link grey-text", onClick GoToForgotPassword ] [ text "Forgot password?" ]
57 | ]
58 | ]
59 |
60 |
61 | viewForm : Model -> Html Msg
62 | viewForm model =
63 | Html.form [ onSubmit SubmitForm ]
64 | [ input
65 | [ placeholder "Username"
66 | , type_ "text"
67 | , value model.username
68 | , onInput SetUsername
69 | , autocomplete False
70 | ]
71 | []
72 | , input
73 | [ placeholder "Password"
74 | , type_ "password"
75 | , value model.password
76 | , onInput SetPassword
77 | , autocomplete False
78 | ]
79 | []
80 | , button [ class "btn waves-effect blue" ]
81 | [ text "Login" ]
82 | ]
83 |
84 |
85 |
86 | -- Update --
87 |
88 |
89 | type Msg
90 | = SubmitForm
91 | | SetUsername String
92 | | SetPassword String
93 | | LoginCompleted (Result Http.Error Player)
94 | | LoginWithFb
95 | | GoToForgotPassword
96 |
97 |
98 | type ExternalMsg
99 | = NoOp
100 | | SetPlayer Player
101 |
102 |
103 | update : Msg -> Model -> ( ( Model, Cmd Msg ), ExternalMsg )
104 | update msg model =
105 | case msg of
106 | SubmitForm ->
107 | case validate model of
108 | [] ->
109 | ( ( { model | errors = [] }, Http.send LoginCompleted (Request.Player.login model) ), NoOp )
110 |
111 | errors ->
112 | ( ( { model | errors = errors }, Cmd.none ), NoOp )
113 |
114 | SetUsername name ->
115 | ( ( { model | username = name }, Cmd.none ), NoOp )
116 |
117 | SetPassword pass ->
118 | ( ( { model | password = pass }, Cmd.none ), NoOp )
119 |
120 | LoginCompleted (Err error) ->
121 | let
122 | errorMessages =
123 | case error of
124 | Http.BadStatus response ->
125 | response.body
126 | |> decodeString (field "errors" errorsDecoder)
127 | |> Result.withDefault []
128 |
129 | _ ->
130 | [ "unable to process login" ]
131 | in
132 | ( ( { model | errors = List.map (\errorMessage -> ( Form, errorMessage )) errorMessages }, Cmd.none ), NoOp )
133 |
134 | LoginCompleted (Ok player) ->
135 | ( ( model, Cmd.batch [ storeSession player, Route.modifyUrl Route.Home ] ), SetPlayer player )
136 |
137 | LoginWithFb ->
138 | ( ( model, Ports.loginWithFB () ), NoOp )
139 |
140 | GoToForgotPassword ->
141 | ( ( model, Route.modifyUrl Route.ForgotPassword ), NoOp )
142 |
143 |
144 |
145 | -- VALIDATION --
146 |
147 |
148 | type Field
149 | = Form
150 | | Username
151 | | Password
152 |
153 |
154 | validate : Model -> List Error
155 | validate =
156 | Validate.all
157 | [ .username >> ifBlank ( Username, "Username can't be blank." )
158 | , .password >> ifBlank ( Password, "Password can't be blank." )
159 | ]
160 |
161 |
162 | errorsDecoder : Decoder (List String)
163 | errorsDecoder =
164 | decode (\username password -> List.concat [ username, password ])
165 | |> optionalError "username"
166 | |> optionalError "password"
167 |
168 |
169 | optionalError : String -> Decoder (List String -> a) -> Decoder a
170 | optionalError fieldName =
171 | let
172 | errorToString errorMessage =
173 | String.join " " [ fieldName, errorMessage ]
174 | in
175 | optional fieldName (Decode.list (Decode.map errorToString string)) []
176 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/ResetPassword.elm:
--------------------------------------------------------------------------------
1 | module Page.ResetPassword exposing (..)
2 |
3 | import Data.Configuration as Configuration exposing (Configuration)
4 | import Data.Player as Player exposing (Player)
5 | import Html exposing (..)
6 | import Html.Attributes exposing (..)
7 | import Html.Events exposing (onClick, onInput, onSubmit)
8 | import Http
9 | import Request.Player as Request
10 | import Route
11 | import Time exposing (Time)
12 | import Widgets.Toast as Toast
13 |
14 |
15 | type alias Model =
16 | { resetToken : String
17 | , newPassword : String
18 | , errors : List String
19 | , messages : List String
20 | , apiUrl : String
21 | }
22 |
23 |
24 | type Msg
25 | = SetPassword String
26 | | SubmitForm
27 | | ClearMessage Time
28 | | ClearError Time
29 | | ServerResult (Result Http.Error Player)
30 |
31 |
32 | type ExternalMsg
33 | = NoOp
34 | | SetPlayer Player
35 |
36 |
37 | initialModel : String -> Configuration -> Model
38 | initialModel resetToken envConfig =
39 | { resetToken = resetToken
40 | , newPassword = ""
41 | , errors = []
42 | , messages = []
43 | , apiUrl = envConfig.apiUrl
44 | }
45 |
46 |
47 | view : Model -> Html Msg
48 | view model =
49 | div
50 | []
51 | [ h1 [ class "page-title teal-text", style [ ( "text-align", "center" ) ] ] [ text "Reset Password" ]
52 | , Toast.viewMessages model
53 | , div [ class "auth-page", style [ ( "text-align", "center" ) ] ]
54 | [ div [ class "auth-form card-panel z-depth-4 rounded" ]
55 | [ viewForm model ]
56 | ]
57 | ]
58 |
59 |
60 | viewForm : Model -> Html Msg
61 | viewForm model =
62 | Html.form [ onSubmit SubmitForm ]
63 | [ input
64 | [ placeholder "Enter your new password"
65 | , type_ "password"
66 | , value model.newPassword
67 | , onInput SetPassword
68 | ]
69 | []
70 | , button [ class "btn waves-effect blue", type_ "button", onClick SubmitForm ]
71 | [ text "Reset Password" ]
72 | ]
73 |
74 |
75 | update : Msg -> Model -> ( ( Model, Cmd Msg ), ExternalMsg )
76 | update msg model =
77 | case msg of
78 | SetPassword password ->
79 | handleSetPassword model password
80 |
81 | SubmitForm ->
82 | handleSubmitForm model
83 |
84 | ClearError _ ->
85 | handleClearError model
86 |
87 | ClearMessage _ ->
88 | handleClearMessage model
89 |
90 | ServerResult result ->
91 | handleServerResult model result
92 |
93 |
94 | handleSetPassword : Model -> String -> ( ( Model, Cmd Msg ), ExternalMsg )
95 | handleSetPassword model password =
96 | ( ( { model | newPassword = password }, Cmd.none ), NoOp )
97 |
98 |
99 | handleSubmitForm : Model -> ( ( Model, Cmd Msg ), ExternalMsg )
100 | handleSubmitForm model =
101 | let
102 | ( newModel, cmd ) =
103 | case String.length model.newPassword >= 6 of
104 | True ->
105 | ( model, Http.send ServerResult (Request.resetPassword model) )
106 |
107 | False ->
108 | ( { model | errors = "Password must be at least 6 characters" :: model.errors }, Cmd.none )
109 | in
110 | ( ( newModel, cmd ), NoOp )
111 |
112 |
113 | handleClearMessage : Model -> ( ( Model, Cmd Msg ), ExternalMsg )
114 | handleClearMessage model =
115 | case List.tail model.messages of
116 | Just newList ->
117 | ( ( { model | messages = newList }, Cmd.none ), NoOp )
118 |
119 | Nothing ->
120 | ( ( { model | messages = [] }, Cmd.none ), NoOp )
121 |
122 |
123 | handleClearError : Model -> ( ( Model, Cmd Msg ), ExternalMsg )
124 | handleClearError model =
125 | case List.tail model.errors of
126 | Just newList ->
127 | ( ( { model | errors = newList }, Cmd.none ), NoOp )
128 |
129 | Nothing ->
130 | ( ( { model | errors = [] }, Cmd.none ), NoOp )
131 |
132 |
133 | handleServerResult : Model -> Result Http.Error Player -> ( ( Model, Cmd Msg ), ExternalMsg )
134 | handleServerResult model result =
135 | case result of
136 | Ok player ->
137 | ( ( model, Cmd.batch [ Request.storeSession player, Route.modifyUrl Route.Home ] ), SetPlayer player )
138 |
139 | Err reason ->
140 | let
141 | errorMessage =
142 | "Your request failed. Your reset token may have expired, or you may have an invalid token."
143 | ++ " Try initiating a new password reset request from the login page and try again."
144 | in
145 | ( ( { model | errors = errorMessage :: model.errors }, Cmd.none ), NoOp )
146 |
147 |
148 |
149 | -- SUBSCRIPTIONS --
150 |
151 |
152 | subscriptions : Model -> Sub Msg
153 | subscriptions model =
154 | let
155 | clearMessages =
156 | case model.messages of
157 | [] ->
158 | Sub.none
159 |
160 | _ ->
161 | Time.every 5000 ClearMessage
162 |
163 | clearErrors =
164 | case model.errors of
165 | [] ->
166 | Sub.none
167 |
168 | _ ->
169 | Time.every 5000 ClearError
170 | in
171 | Sub.batch [ clearErrors, clearMessages ]
172 |
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Internal/Socket.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Internal.Socket exposing (..)
2 |
3 | import Dict exposing (Dict)
4 | import Phoenix.Internal.Message as Message exposing (Message)
5 | import Phoenix.Socket as Socket
6 | import Process
7 | import String
8 | import Task exposing (Task)
9 | import WebSocket.LowLevel as WS
10 |
11 |
12 | type alias Endpoint =
13 | String
14 |
15 |
16 | type alias Ref =
17 | Int
18 |
19 |
20 | {-| The underlying low-level InternalSocket connection.
21 | -}
22 | type Connection
23 | = Closed
24 | | Opening Int Process.Id
25 | | Connected WS.WebSocket Int
26 |
27 |
28 | type alias InternalSocket msg =
29 | { connection : Connection, socket : Socket.Socket msg }
30 |
31 |
32 | internalSocket : Socket.Socket msg -> InternalSocket msg
33 | internalSocket socket =
34 | { connection = Closed, socket = socket }
35 |
36 |
37 |
38 | -- INSPECT
39 |
40 |
41 | isOpening : InternalSocket msg -> Bool
42 | isOpening internalSocket =
43 | case internalSocket.connection of
44 | Opening _ _ ->
45 | True
46 |
47 | _ ->
48 | False
49 |
50 |
51 |
52 | -- MODIFY
53 |
54 |
55 | opening : Int -> Process.Id -> InternalSocket msg -> InternalSocket msg
56 | opening backoff pid socket =
57 | { socket | connection = Opening backoff pid }
58 |
59 |
60 | connected : WS.WebSocket -> InternalSocket msg -> InternalSocket msg
61 | connected ws socket =
62 | { socket | connection = Connected ws 0 }
63 |
64 |
65 | increaseRef : InternalSocket msg -> InternalSocket msg
66 | increaseRef socket =
67 | case socket.connection of
68 | Connected ws ref ->
69 | { socket | connection = Connected ws (ref + 1) }
70 |
71 | _ ->
72 | socket
73 |
74 |
75 | update : Socket.Socket msg -> InternalSocket msg -> InternalSocket msg
76 | update nextSocket { connection, socket } =
77 | let
78 | updatedConnection =
79 | if nextSocket.params /= socket.params then
80 | resetBackoff connection
81 | else
82 | connection
83 | in
84 | InternalSocket updatedConnection nextSocket
85 |
86 |
87 | resetBackoff : Connection -> Connection
88 | resetBackoff connection =
89 | case connection of
90 | Opening backoff pid ->
91 | Opening 0 pid
92 |
93 | _ ->
94 | connection
95 |
96 |
97 |
98 | -- PUSH
99 |
100 |
101 | push : Message -> InternalSocket msg -> Task x (Maybe Ref)
102 | push message { connection, socket } =
103 | case connection of
104 | Connected ws ref ->
105 | let
106 | message_ =
107 | if socket.debug then
108 | Debug.log "Send" (Message.ref ref message)
109 | else
110 | Message.ref ref message
111 | in
112 | WS.send ws (Message.encode message_)
113 | |> Task.map
114 | (\maybeBadSend ->
115 | case maybeBadSend of
116 | Nothing ->
117 | Just ref
118 |
119 | Just badSend ->
120 | if socket.debug then
121 | let
122 | _ =
123 | Debug.log "BadSend" badSend
124 | in
125 | Nothing
126 | else
127 | Nothing
128 | )
129 |
130 | _ ->
131 | Task.succeed Nothing
132 |
133 |
134 |
135 | -- OPEN CONNECTIONs
136 |
137 |
138 | open : InternalSocket msg -> WS.Settings -> Task WS.BadOpen WS.WebSocket
139 | open { socket } settings =
140 | let
141 | query =
142 | socket.params
143 | |> List.map (\( key, val ) -> key ++ "=" ++ val)
144 | |> String.join "&"
145 |
146 | url =
147 | if String.contains "?" socket.endpoint then
148 | socket.endpoint ++ "&" ++ query
149 | else
150 | socket.endpoint ++ "?" ++ query
151 | in
152 | WS.open url settings
153 |
154 |
155 | after : Float -> Task x ()
156 | after backoff =
157 | if backoff < 1 then
158 | Task.succeed ()
159 | else
160 | Process.sleep backoff
161 |
162 |
163 |
164 | -- CLOSE CONNECTIONS
165 |
166 |
167 | close : InternalSocket msg -> Task x ()
168 | close { connection } =
169 | case connection of
170 | Opening _ pid ->
171 | Process.kill pid
172 |
173 | Connected socket _ ->
174 | WS.close socket
175 |
176 | Closed ->
177 | Task.succeed ()
178 |
179 |
180 |
181 | -- HELPERS
182 |
183 |
184 | get : Endpoint -> Dict Endpoint (InternalSocket msg) -> Maybe (InternalSocket msg)
185 | get endpoint dict =
186 | Dict.get endpoint dict
187 |
188 |
189 | getRef : Endpoint -> Dict Endpoint (InternalSocket msg) -> Maybe Ref
190 | getRef endpoint dict =
191 | get endpoint dict |> Maybe.andThen ref
192 |
193 |
194 | ref : InternalSocket msg -> Maybe Ref
195 | ref { connection } =
196 | case connection of
197 | Connected _ ref_ ->
198 | Just ref_
199 |
200 | _ ->
201 | Nothing
202 |
203 |
204 | debugLogMessage : InternalSocket msg -> a -> a
205 | debugLogMessage { socket } msg =
206 | if socket.debug then
207 | Debug.log "Received" msg
208 | else
209 | msg
210 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/ForgotPassword.elm:
--------------------------------------------------------------------------------
1 | module Page.ForgotPassword exposing (..)
2 |
3 | import Data.Configuration exposing (Configuration)
4 | import Data.PasswordReset as PasswordReset exposing (PasswordReset)
5 | import Html exposing (..)
6 | import Html.Attributes exposing (..)
7 | import Html.Events exposing (onClick, onInput, onSubmit)
8 | import Http
9 | import Request.Player as Request
10 | import Time exposing (Time)
11 | import Validate exposing (..)
12 | import Widgets.Toast as Toast
13 |
14 |
15 | type alias Model =
16 | { email : String
17 | , errors : List String
18 | , messages : List String
19 | , apiUrl : String
20 | }
21 |
22 |
23 | type Msg
24 | = SendPasswordReset
25 | | SubmitForm
26 | | SetEmail String
27 | | ServerResult (Result Http.Error PasswordReset)
28 | | ClearMessage Time
29 | | ClearError Time
30 |
31 |
32 | type ExternalMsg
33 | = NoOp
34 |
35 |
36 | initialModel : Configuration -> Model
37 | initialModel envConfig =
38 | { email = "", errors = [], messages = [], apiUrl = envConfig.apiUrl }
39 |
40 |
41 | view : Model -> Html Msg
42 | view model =
43 | div
44 | []
45 | [ h1 [ class "page-title teal-text", style [ ( "text-align", "center" ) ] ] [ text "Forgot Password" ]
46 | , Toast.viewMessages model
47 | , div [ class "auth-page", style [ ( "text-align", "center" ) ] ]
48 | [ div [ class "auth-form card-panel z-depth-4 rounded" ]
49 | [ viewForm model ]
50 | ]
51 | ]
52 |
53 |
54 | viewForm : Model -> Html Msg
55 | viewForm model =
56 | Html.form [ onSubmit SubmitForm ]
57 | [ input
58 | [ placeholder "Enter your email"
59 | , type_ "text"
60 | , value model.email
61 | , onInput SetEmail
62 | ]
63 | []
64 | , button [ class "btn waves-effect blue", type_ "button", onClick SubmitForm ]
65 | [ text "Send Password Reset" ]
66 | ]
67 |
68 |
69 | update : Msg -> Model -> ( ( Model, Cmd Msg ), ExternalMsg )
70 | update msg model =
71 | case msg of
72 | SendPasswordReset ->
73 | handleSendPasswordReset model
74 |
75 | SubmitForm ->
76 | handleSubmitForm model
77 |
78 | SetEmail email ->
79 | handleSetEmail model email
80 |
81 | ServerResult result ->
82 | handleServerResult model result
83 |
84 | ClearMessage _ ->
85 | handleClearMessage model
86 |
87 | ClearError _ ->
88 | handleClearError model
89 |
90 |
91 | handleSendPasswordReset : Model -> ( ( Model, Cmd Msg ), ExternalMsg )
92 | handleSendPasswordReset model =
93 | ( ( model, Cmd.none ), NoOp )
94 |
95 |
96 | handleSubmitForm : Model -> ( ( Model, Cmd Msg ), ExternalMsg )
97 | handleSubmitForm model =
98 | case validate model of
99 | [] ->
100 | ( ( { model | errors = [] }, Http.send ServerResult (Request.passwordReset model) ), NoOp )
101 |
102 | errors ->
103 | ( ( { model | errors = errors }, Cmd.none ), NoOp )
104 |
105 |
106 | handleSetEmail : Model -> String -> ( ( Model, Cmd Msg ), ExternalMsg )
107 | handleSetEmail model email =
108 | ( ( { model | email = email }, Cmd.none ), NoOp )
109 |
110 |
111 | handleClearMessage : Model -> ( ( Model, Cmd Msg ), ExternalMsg )
112 | handleClearMessage model =
113 | case List.tail model.messages of
114 | Just newList ->
115 | ( ( { model | messages = newList }, Cmd.none ), NoOp )
116 |
117 | Nothing ->
118 | ( ( { model | messages = [] }, Cmd.none ), NoOp )
119 |
120 |
121 | handleClearError : Model -> ( ( Model, Cmd Msg ), ExternalMsg )
122 | handleClearError model =
123 | case List.tail model.errors of
124 | Just newList ->
125 | ( ( { model | errors = newList }, Cmd.none ), NoOp )
126 |
127 | Nothing ->
128 | ( ( { model | errors = [] }, Cmd.none ), NoOp )
129 |
130 |
131 | handleServerResult : Model -> Result Http.Error PasswordReset -> ( ( Model, Cmd Msg ), ExternalMsg )
132 | handleServerResult model result =
133 | case result of
134 | Ok passwordReset ->
135 | let
136 | newModel =
137 | case passwordReset.type_ of
138 | PasswordReset.Success ->
139 | { model | messages = passwordReset.message :: model.messages }
140 |
141 | PasswordReset.Error ->
142 | { model | errors = passwordReset.message :: model.errors }
143 | in
144 | ( ( newModel, Cmd.none ), NoOp )
145 |
146 | Err _ ->
147 | let
148 | errorMessage =
149 | "Something went wrong with your request. Please submit the form and try again"
150 | in
151 | ( ( { model | errors = errorMessage :: model.errors }, Cmd.none ), NoOp )
152 |
153 |
154 |
155 | -- VALIDATION --
156 |
157 |
158 | validate : Model -> List String
159 | validate =
160 | Validate.all
161 | [ .email >> ifBlank "You must enter an email" ]
162 |
163 |
164 |
165 | -- SUBSCRIPTIONS --
166 |
167 |
168 | subscriptions : Model -> Sub Msg
169 | subscriptions model =
170 | let
171 | clearMessages =
172 | case model.messages of
173 | [] ->
174 | Sub.none
175 |
176 | _ ->
177 | Time.every 5000 ClearMessage
178 |
179 | clearErrors =
180 | case model.errors of
181 | [] ->
182 | Sub.none
183 |
184 | _ ->
185 | Time.every 5000 ClearError
186 | in
187 | Sub.batch [ clearErrors, clearMessages ]
188 |
--------------------------------------------------------------------------------
/assets/elm/vendor/Phoenix/Socket.elm:
--------------------------------------------------------------------------------
1 | module Phoenix.Socket
2 | exposing
3 | ( AbnormalClose
4 | , Socket
5 | , heartbeatIntervallSeconds
6 | , init
7 | , map
8 | , onAbnormalClose
9 | , onClose
10 | , onNormalClose
11 | , onOpen
12 | , reconnectTimer
13 | , withDebug
14 | , withParams
15 | , withoutHeartbeat
16 | )
17 |
18 | {-| A socket declares to which endpoint a socket connection should be established.
19 |
20 |
21 | # Definition
22 |
23 | @docs Socket, AbnormalClose
24 |
25 |
26 | # Helpers
27 |
28 | @docs init, withParams, heartbeatIntervallSeconds, withoutHeartbeat, reconnectTimer, withDebug, onAbnormalClose, onNormalClose, onOpen, onClose, map
29 |
30 | -}
31 |
32 | import Time exposing (Time)
33 |
34 |
35 | {-| Representation of a Socket connection
36 | -}
37 | type alias Socket msg =
38 | PhoenixSocket msg
39 |
40 |
41 | {-| Representation of an Abnormal close.
42 | -}
43 | type alias AbnormalClose =
44 | { reconnectAttempt : Int
45 | , reconnectWait : Time
46 | }
47 |
48 |
49 | type alias PhoenixSocket msg =
50 | { endpoint : String
51 | , params : List ( String, String )
52 | , heartbeatIntervall : Time
53 | , withoutHeartbeat : Bool
54 | , reconnectTimer : Int -> Float
55 | , debug : Bool
56 | , onOpen : Maybe msg
57 | , onClose : Maybe ({ code : Int, reason : String, wasClean : Bool } -> msg)
58 | , onAbnormalClose : Maybe (AbnormalClose -> msg)
59 | , onNormalClose : Maybe msg
60 | }
61 |
62 |
63 | {-| Initialize a Socket connection with an endpoint.
64 |
65 | init "ws://localhost:4000/socket/websocket"
66 |
67 | -}
68 | init : String -> Socket msg
69 | init endpoint =
70 | { endpoint = endpoint
71 | , params = []
72 | , heartbeatIntervall = 30 * Time.second
73 | , withoutHeartbeat = False
74 | , reconnectTimer = defaultReconnectTimer
75 | , debug = False
76 | , onOpen = Nothing
77 | , onClose = Nothing
78 | , onAbnormalClose = Nothing
79 | , onNormalClose = Nothing
80 | }
81 |
82 |
83 | {-| Attach parameters to the socket connecton. You can use this to do authentication on the socket level. This will be the first argument (as a map) in your `connect/2` callback on the server.
84 |
85 | init "ws://localhost:4000/socket/websocket"
86 | |> withParams [("token", "GYMXZwXzKFzfxyGntVkYt7uAJnscVnFJ")]
87 |
88 | -}
89 | withParams : List ( String, String ) -> Socket msg -> Socket msg
90 | withParams params socket =
91 | { socket | params = params }
92 |
93 |
94 | {-| The client regularly sends a heartbeat to the server. With this function you can specify the intervall in which the heartbeats are send. By default it_s 30 seconds.
95 |
96 | init "ws://localhost:4000/socket/websocket"
97 | |> heartbeatIntervallSeconds 60
98 |
99 | -}
100 | heartbeatIntervallSeconds : Int -> Socket msg -> Socket msg
101 | heartbeatIntervallSeconds intervall socket =
102 | { socket | heartbeatIntervall = toFloat intervall * Time.second }
103 |
104 |
105 | {-| The client regularly sends a heartbeat to the sever. With this function you can disable the heartbeat.
106 |
107 | init "ws://localhost:4000/socket/websocket"
108 | |> withoutHeartbeat
109 |
110 | -}
111 | withoutHeartbeat : Socket msg -> Socket msg
112 | withoutHeartbeat socket =
113 | { socket | withoutHeartbeat = True }
114 |
115 |
116 | {-| The effect manager will try to establish a socket connection. If it fails it will try again with a specified backoff. By default the effect manager will use the following exponential backoff strategy:
117 |
118 | defaultReconnectTimer failedAttempts =
119 | if backoff < 1 then
120 | 0
121 | else
122 | toFloat (10 * 2 ^ failedAttempts)
123 |
124 | With this function you can specify a custom strategy.
125 |
126 | -}
127 | reconnectTimer : (Int -> Time) -> Socket msg -> Socket msg
128 | reconnectTimer timerFunc socket =
129 | { socket | reconnectTimer = timerFunc }
130 |
131 |
132 | {-| Enable debug logs for the socket. Every incoming and outgoing message will be printed.
133 | -}
134 | withDebug : Socket msg -> Socket msg
135 | withDebug socket =
136 | { socket | debug = True }
137 |
138 |
139 | {-| Set a callback which will be called if the socket connection gets open.
140 | -}
141 | onOpen : msg -> Socket msg -> Socket msg
142 | onOpen onOpen socket =
143 | { socket | onOpen = Just onOpen }
144 |
145 |
146 | {-| Set a callback which will be called if the socket connection got closed abnormal, i.e., if the server declined the socket authentication. So this callback is useful for updating query params like access tokens.
147 |
148 | type Msg =
149 | RefreshAccessToken | ...
150 |
151 | init "ws://localhost:4000/socket/websocket"
152 | |> withParams [ ( "accessToken", "abc123" ) ]
153 | |> onAbnormalClose RefreshAccessToken
154 |
155 | -}
156 | onAbnormalClose : (AbnormalClose -> msg) -> Socket msg -> Socket msg
157 | onAbnormalClose onAbnormalClose_ socket =
158 | { socket | onAbnormalClose = Just onAbnormalClose_ }
159 |
160 |
161 | {-| Set a callback which will be called if the socket connection got closed normal. Useful if you have to do some additional clean up.
162 | -}
163 | onNormalClose : msg -> Socket msg -> Socket msg
164 | onNormalClose onNormalClose_ socket =
165 | { socket | onNormalClose = Just onNormalClose_ }
166 |
167 |
168 | {-| Set a callback which will be called if the socket connection got closed. You can learn more about the code [here](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent).
169 | -}
170 | onClose : ({ code : Int, reason : String, wasClean : Bool } -> msg) -> Socket msg -> Socket msg
171 | onClose onClose_ socket =
172 | { socket | onClose = Just onClose_ }
173 |
174 |
175 | defaultReconnectTimer : Int -> Time
176 | defaultReconnectTimer failedAttempts =
177 | if failedAttempts < 1 then
178 | 0
179 | else
180 | toFloat (min 15000 (1000 * failedAttempts))
181 |
182 |
183 | {-| Composes each callback with the function `a -> b`.
184 | -}
185 | map : (a -> b) -> Socket a -> Socket b
186 | map func socket =
187 | { socket
188 | | onOpen = Maybe.map func socket.onOpen
189 | , onClose = Maybe.map ((<<) func) socket.onClose
190 | , onNormalClose = Maybe.map func socket.onNormalClose
191 | , onAbnormalClose = Maybe.map ((<<) func) socket.onAbnormalClose
192 | }
193 |
--------------------------------------------------------------------------------
/assets/static/images/cards/2_of_spades.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
148 |
--------------------------------------------------------------------------------
/assets/static/images/cards/3_of_spades.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
155 |
--------------------------------------------------------------------------------
/assets/static/images/cards/4_of_spades.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
164 |
--------------------------------------------------------------------------------
/assets/elm/src/Page/Room.elm:
--------------------------------------------------------------------------------
1 | module Page.Room exposing (..)
2 |
3 | import Data.Configuration exposing (Configuration)
4 | import Data.Player as Player exposing (Player)
5 | import Data.Room as Room exposing (Room)
6 | import Data.RoomPage as RoomPage exposing (RoomPage)
7 | import Data.Session as Session exposing (Session)
8 | import Html exposing (Html, div, text)
9 | import Html.Attributes exposing (..)
10 | import Html.Events exposing (onClick, onInput, onSubmit)
11 | import Page.Room.Helpers as Helpers exposing (..)
12 | import Page.Room.SocketConfig as SocketConfig exposing (..)
13 | import Page.Room.UpdateHelpers as Updaters exposing (..)
14 | import Page.Room.ViewHelpers exposing (..)
15 | import Phoenix
16 | import Time exposing (Time)
17 | import Types.Room.Messages as Messages exposing (..)
18 | import Types.Room.ModalState as ModalState exposing (..)
19 | import Views.Actions as Actions
20 | import Widgets.PlayerToolbar as PlayerToolbar
21 |
22 |
23 | -- Types
24 |
25 |
26 | type alias Msg =
27 | RoomMsg
28 |
29 |
30 | type alias ExternalMsg =
31 | RoomExternalMsg
32 |
33 |
34 | type alias Model =
35 | RoomPage
36 |
37 |
38 | type alias MessageType =
39 | RoomMessageType
40 |
41 |
42 |
43 | -- INITIALIZATION --
44 |
45 |
46 | initialModel :
47 | Player
48 | -> String
49 | -> String
50 | -> Configuration
51 | -> RoomPage
52 | initialModel player roomTitle roomType envConfig =
53 | { room = formatTitle roomTitle
54 | , roomModel = Room.defaultRoom
55 | , roomType = roomType
56 | , roomMessages = []
57 | , players = []
58 | , player = player
59 | , joined = False
60 | , joinValue = "0"
61 | , channelSubscriptions = subscribeToChannels player (formatTitle roomTitle) roomType
62 | , modalRendered = Closed
63 | , errorMessages = []
64 | , raiseAmount = 0
65 | , raiseInterval = 5
66 | , chipsAvailable = player.chips
67 | , chipsAvailableForJoin = player.chips
68 | , addAmount = 0
69 | , chat = []
70 | , currentChatMsg = ""
71 | , socketUrl = envConfig.socketUrl
72 | , apiUrl = envConfig.apiUrl
73 | }
74 |
75 |
76 |
77 | -- VIEW --
78 |
79 |
80 | view : Session -> Model -> Html Msg
81 | view session model =
82 | let
83 | mobileToolbarView =
84 | case model.modalRendered of
85 | BottomModalOpen _ ->
86 | text ""
87 |
88 | _ ->
89 | PlayerToolbar.viewMobile (toolbarConfig model)
90 | in
91 | div [ class "room-container" ]
92 | [ div [ class "table-container" ]
93 | (viewTableCenter model.roomModel :: viewTableCards model.roomModel :: viewPlayers session model)
94 | , PlayerToolbar.view (toolbarConfig model)
95 | , mobileToolbarView
96 | , Actions.viewMobile (actionsViewConfig model)
97 | , maybeViewModal model
98 | , viewMessages model
99 | ]
100 |
101 |
102 |
103 | -- UPDATE --
104 |
105 |
106 | update : Msg -> Model -> ( ( Model, Cmd Msg ), ExternalMsg )
107 | update msg model =
108 | case msg of
109 | JoinedChannel ->
110 | handleJoinedChannel model
111 |
112 | Join ->
113 | handleJoin model
114 |
115 | JoinFailed value ->
116 | handleJoinFailed model value
117 |
118 | SetJoinValue amount ->
119 | handleSetJoinValue model amount
120 |
121 | Update payload ->
122 | handleUpdate model payload
123 |
124 | GameStarted payload ->
125 | handleUpdate model payload
126 |
127 | WinnerMessage payload ->
128 | handleWinnerMessage model payload
129 |
130 | PresentWinningHand payload ->
131 | handlePresentWinningHand model payload
132 |
133 | SetBankInfo payload ->
134 | handleSetBankInfo model payload
135 |
136 | Clear _ ->
137 | handleClear model
138 |
139 | ConnectedToPlayerChannel ->
140 | ( ( model, Cmd.none ), NoOp )
141 |
142 | ChipInfo payload ->
143 | handleChipInfo model payload
144 |
145 | ActionPressed ->
146 | ( ( { model | modalRendered = BottomModalOpen Actions }, Cmd.none ), NoOp )
147 |
148 | ActionMsg action val ->
149 | handleActionMsg model action val
150 |
151 | NewChatMsg value ->
152 | handleNewChatMsg model value
153 |
154 | NewMessage value ->
155 | handleNewMessage model value
156 |
157 | BankPressed ->
158 | handleBankPressed model
159 |
160 | AccountPressed ->
161 | handleAccountPressed model
162 |
163 | MobileToolbarPressed ->
164 | ( ( { model | modalRendered = BottomModalOpen MobileMenu }, Cmd.none ), NoOp )
165 |
166 | ChatPressed ->
167 | ( ( { model | modalRendered = BottomModalOpen ModalState.Chat }, Cmd.none ), NoOp )
168 |
169 | CloseRaiseModal ->
170 | ( ( { model | modalRendered = BottomModalOpen Actions }, Cmd.none ), NoOp )
171 |
172 | IncreaseRaise amount ->
173 | handleIncreaseRaise model amount
174 |
175 | DecreaseRaise amount ->
176 | handleDecreaseRaise model amount
177 |
178 | SetRaise amount ->
179 | handleSetRaise model amount
180 |
181 | SetAddAmount amount ->
182 | handleSetAddAmount model amount
183 |
184 | SetChatMsg message ->
185 | handleSetChatMsg model message
186 |
187 | SubmitChat ->
188 | handleSubmitChat model
189 |
190 | SocketOpened ->
191 | ( ( model, Cmd.none ), NoOp )
192 |
193 | SocketClosed ->
194 | ( ( model, Cmd.none ), NoOp )
195 |
196 | SocketClosedAbnormally ->
197 | ( ( model, Cmd.none ), NoOp )
198 |
199 | Rejoined _ ->
200 | handleRejoin model
201 |
202 | JoinRoom player ->
203 | handleJoinRoom model player
204 |
205 | Blur ->
206 | ( ( { model | modalRendered = Closed }, Cmd.none ), NoOp )
207 |
208 | OpenRaisePressed ->
209 | ( ( { model | modalRendered = RaiseModalOpen }, Cmd.none ), NoOp )
210 |
211 | ClearErrorMessage _ ->
212 | clearErrorMessage model
213 |
214 | ClearRoomMessage _ ->
215 | clearRoomMessage model
216 |
217 | ClearWinningHandModal _ ->
218 | clearWinningHandModal model
219 |
220 | CloseWinningHandModal ->
221 | clearWinningHandModal model
222 |
223 | CloseModal ->
224 | ( ( { model | modalRendered = Closed }, Cmd.none ), NoOp )
225 |
226 | LeaveRoom player ->
227 | handleLeaveRoom player model
228 |
229 |
230 |
231 | -- SUBSCRIPTIONS --
232 |
233 |
234 | subscriptions : Model -> Session -> Sub Msg
235 | subscriptions model session =
236 | let
237 | phoenixSubscriptions =
238 | [ Phoenix.connect (socket session model.socketUrl) model.channelSubscriptions ]
239 |
240 | withBlur =
241 | case model.modalRendered of
242 | WinningHandModal _ ->
243 | Time.every 5000 ClearWinningHandModal
244 |
245 | _ ->
246 | Sub.none
247 |
248 | withClearError =
249 | case model.errorMessages of
250 | [] ->
251 | Sub.none
252 |
253 | _ ->
254 | Time.every 3000 ClearErrorMessage
255 |
256 | withClearRoomMessage =
257 | case model.roomMessages of
258 | [] ->
259 | Sub.none
260 |
261 | _ ->
262 | Time.every 3000 ClearRoomMessage
263 | in
264 | Sub.batch (phoenixSubscriptions ++ [ withBlur, withClearError, withClearRoomMessage ])
265 |
--------------------------------------------------------------------------------
/assets/elm/src/Views/Actions.elm:
--------------------------------------------------------------------------------
1 | module Views.Actions exposing (..)
2 |
3 | import Data.Player as Player exposing (Username)
4 | import Html exposing (..)
5 | import Html.Attributes as Attrs exposing (..)
6 | import Html.Events exposing (on, onClick, targetValue)
7 | import Json.Decode as Decode
8 | import Json.Encode as Encode exposing (Value)
9 |
10 |
11 | type Action
12 | = Raise
13 | | Fold
14 | | Check
15 | | Call
16 |
17 |
18 | type Platform
19 | = Mobile
20 | | Desktop
21 |
22 |
23 | type alias ActionsModel msg =
24 | { isActive : Bool
25 | , chips : Int
26 | , paidInRound : Int
27 | , toCall : Int
28 | , player : Username
29 | , actionMsg : String -> Value -> msg
30 | , openRaiseMsg : msg
31 | , closeModalMsg : msg
32 | , closeRaiseMsg : msg
33 | , increaseRaiseMsg : Int -> msg
34 | , decreaseRaiseMsg : Int -> msg
35 | , setRaiseMsg : String -> msg
36 | , raiseAmount : Int
37 | , raiseMax : Int
38 | , raiseMin : Int
39 | , raiseInterval : Int
40 | }
41 |
42 |
43 | view : ActionsModel msg -> Html msg
44 | view actionsModel =
45 | div [ class "actions-container" ]
46 | [ span [ class "modal-header red-text" ] [ text "Actions" ]
47 | , div [ class "row" ]
48 | [ div [ class "col s6" ]
49 | [ viewActionBtn actionsModel Call Desktop ]
50 | , div [ class "col s6" ]
51 | [ viewActionBtn actionsModel Raise Desktop ]
52 | ]
53 | , div [ class "row" ]
54 | [ div [ class "col s6" ]
55 | [ viewActionBtn actionsModel Check Desktop ]
56 | , div [ class "col s6" ]
57 | [ viewActionBtn actionsModel Fold Desktop ]
58 | , i [ class "material-icons close-modal", onClick actionsModel.closeModalMsg ]
59 | [ text "close" ]
60 | ]
61 | ]
62 |
63 |
64 | viewMobile : ActionsModel msg -> Html msg
65 | viewMobile actionsModel =
66 | div [ class "actions-container-mobile" ]
67 | [ viewActionBtn actionsModel Call Mobile
68 | , viewActionBtn actionsModel Raise Mobile
69 | , viewActionBtn actionsModel Check Mobile
70 | , viewActionBtn actionsModel Fold Mobile
71 | ]
72 |
73 |
74 | encodeUsernamePayload : Username -> Value
75 | encodeUsernamePayload username =
76 | Encode.object
77 | [ ( "player", Player.encodeUsername username ) ]
78 |
79 |
80 | actionBtnClass : String -> Platform -> String
81 | actionBtnClass color platform =
82 | let
83 | extraClasses =
84 | case platform of
85 | Desktop ->
86 | ""
87 |
88 | Mobile ->
89 | "btn-floating"
90 | in
91 | "waves-effect waves-effect-light btn white-text " ++ color ++ " " ++ extraClasses
92 |
93 |
94 | viewActionBtn : ActionsModel msg -> Action -> Platform -> Html msg
95 | viewActionBtn actionsModel action platform =
96 | let
97 | ( color, message, btnText ) =
98 | getAttributesFor actionsModel action
99 | in
100 | case canCallAction actionsModel action of
101 | True ->
102 | a [ class (actionBtnClass color platform), onClick message ] [ childrenForBtn btnText platform actionsModel.toCall ]
103 |
104 | False ->
105 | text ""
106 |
107 |
108 | childrenForBtn : String -> Platform -> Int -> Html msg
109 | childrenForBtn btnText platform toCall =
110 | case platform of
111 | Desktop ->
112 | text btnText
113 |
114 | Mobile ->
115 | getIconForMobileBtn btnText toCall
116 |
117 |
118 | getIconForMobileBtn : String -> Int -> Html msg
119 | getIconForMobileBtn btnText toCall =
120 | case toCall of
121 | 0 ->
122 | case btnText of
123 | "Raise" ->
124 | i [ class "material-icons" ] [ text "arrow_upward" ]
125 |
126 | "Check" ->
127 | i [ class "material-icons" ] [ text "check" ]
128 |
129 | _ ->
130 | text ""
131 |
132 | callAmount ->
133 | case btnText of
134 | "Raise" ->
135 | i [ class "material-icons" ] [ text "arrow_upward" ]
136 |
137 | "Check" ->
138 | i [ class "material-icons" ] [ text "check" ]
139 |
140 | "Fold" ->
141 | i [ class "material-icons" ] [ text "block" ]
142 |
143 | _ ->
144 | text <| toString callAmount
145 |
146 |
147 | raiseContent : ActionsModel msg -> Html msg
148 | raiseContent actionsModel =
149 | div [ class "raise-modal" ]
150 | [ div [ class "raise-modal-header" ]
151 | [ h3 [ class "red-text" ]
152 | [ text <| "Raise to " ++ toString actionsModel.raiseAmount ]
153 | ]
154 | , input
155 | [ type_ "number"
156 | , Attrs.min <| toString actionsModel.raiseMin
157 | , Attrs.max <| toString actionsModel.raiseMax
158 | , Attrs.placeholder "Enter your raise here"
159 | , onRangeChange actionsModel.setRaiseMsg
160 | ]
161 | []
162 | , div [ class "raise-buttons-container" ]
163 | [ a [ onClick <| actionsModel.decreaseRaiseMsg actionsModel.raiseInterval, class "btn btn-large waves-effect raise-btn" ]
164 | [ i [ class "large material-icons" ] [ text "remove" ] ]
165 | , a [ class "btn btn-large green waves-effect white-text raise-submit", onClick <| raiseMsgWith actionsModel "action_raise" ]
166 | [ text "Raise" ]
167 | , a [ onClick <| actionsModel.increaseRaiseMsg actionsModel.raiseInterval, class "btn btn-large waves-effect raise-btn" ]
168 | [ i [ class "large material-icons" ] [ text "add" ] ]
169 | ]
170 | , div [ class "raise-modal-close-row" ]
171 | [ i [ class "material-icons close-modal", onClick actionsModel.closeRaiseMsg ] [ text "close" ] ]
172 | ]
173 |
174 |
175 | actionMsgWith : ActionsModel msg -> String -> msg
176 | actionMsgWith actionsModel pushMessage =
177 | actionsModel.actionMsg pushMessage (encodeUsernamePayload actionsModel.player)
178 |
179 |
180 | raiseMsgWith : ActionsModel msg -> String -> msg
181 | raiseMsgWith actionsModel pushMessage =
182 | actionsModel.actionMsg pushMessage
183 | (Encode.object <|
184 | [ ( "player", Player.encodeUsername actionsModel.player )
185 | , ( "amount", Encode.int actionsModel.raiseAmount )
186 | ]
187 | )
188 |
189 |
190 | canCallAction : ActionsModel msg -> Action -> Bool
191 | canCallAction actionsModel action =
192 | if actionsModel.isActive == False then
193 | False
194 | else
195 | case action of
196 | Call ->
197 | actionsModel.paidInRound < actionsModel.toCall
198 |
199 | Check ->
200 | actionsModel.paidInRound == actionsModel.toCall
201 |
202 | Raise ->
203 | actionsModel.chips > actionsModel.toCall
204 |
205 | Fold ->
206 | actionsModel.paidInRound < actionsModel.toCall
207 |
208 |
209 | onRangeChange : (String -> msg) -> Attribute msg
210 | onRangeChange msg =
211 | on "change" <| Decode.map msg targetValue
212 |
213 |
214 | getAttributesFor : ActionsModel msg -> Action -> ( String, msg, String )
215 | getAttributesFor actionsModel action =
216 | case action of
217 | Call ->
218 | ( "blue darken-3", actionMsgWith actionsModel "action_call", "Call " ++ toString actionsModel.toCall )
219 |
220 | Raise ->
221 | ( "red darken-3", actionsModel.openRaiseMsg, "Raise" )
222 |
223 | Check ->
224 | ( "green accent-3", actionMsgWith actionsModel "action_check", "Check" )
225 |
226 | Fold ->
227 | ( "teal darken-4", actionMsgWith actionsModel "action_fold", "Fold" )
228 |
--------------------------------------------------------------------------------