step for pin code login #1
7 changed files with 198 additions and 76 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -1 +1,6 @@
|
|||
localbuild.sh
|
||||
**/.*
|
||||
/localbuild.sh
|
||||
/client/build
|
||||
/server/build
|
||||
/server/priv/static/client.js
|
||||
/server/priv/static/index.html
|
||||
|
|
|
|||
|
|
@ -6,9 +6,15 @@ FROM ghcr.io/gleam-lang/gleam:${GLEAM_VERSION}-erlang-alpine AS builder
|
|||
COPY ./server/priv /quizterm/server/priv
|
||||
COPY ./server/src /quizterm/server/src
|
||||
COPY ./server/gleam.toml /quizterm/server/
|
||||
COPY ./client/src /quizterm/client/src
|
||||
COPY ./client/gleam.toml /quizterm/client/
|
||||
|
||||
RUN cd /quizterm/server && gleam deps download
|
||||
|
||||
# Compile client code and move generated javascript to server project
|
||||
RUN cd /quizterm/client \
|
||||
&& gleam run -m lustre/dev build --outdir=/quizterm/server/priv/static
|
||||
|
||||
# Compile the server code
|
||||
RUN cd /quizterm/server \
|
||||
&& gleam export erlang-shipment
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
import gleam/int
|
||||
import gleam/json
|
||||
import gleam/list
|
||||
import gleam/option.{type Option, None, Some}
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import lustre
|
||||
import lustre/attribute
|
||||
import lustre/attribute.{class}
|
||||
import lustre/effect.{type Effect}
|
||||
import lustre/element.{type Element}
|
||||
import lustre/element/html
|
||||
import lustre/event
|
||||
import plinth/browser/document
|
||||
import plinth/browser/element as plinth_element
|
||||
import shared
|
||||
import shared.{type Room}
|
||||
|
||||
pub fn main() {
|
||||
let initial_items =
|
||||
|
|
@ -27,57 +31,130 @@ pub fn main() {
|
|||
}
|
||||
|
||||
type Model {
|
||||
Model(rooms: List(String), name: String)
|
||||
Model(
|
||||
rooms: List(Room),
|
||||
name: Option(String),
|
||||
pin: Option(String),
|
||||
typed: String,
|
||||
)
|
||||
}
|
||||
|
||||
fn init(items: List(String)) -> #(Model, Effect(Msg)) {
|
||||
let model = Model(rooms: items, name: "")
|
||||
fn init(items: List(Room)) -> #(Model, Effect(State)) {
|
||||
let model = Model(rooms: items, name: None, pin: None, typed: "")
|
||||
|
||||
#(model, effect.none())
|
||||
}
|
||||
|
||||
type Msg {
|
||||
UserTypedNewItem(String)
|
||||
UserAddedItem
|
||||
type State {
|
||||
SelectRoom(String)
|
||||
Initial
|
||||
KeyIn(String)
|
||||
}
|
||||
|
||||
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
|
||||
fn update(model: Model, msg: State) -> #(Model, Effect(msg)) {
|
||||
case msg {
|
||||
UserAddedItem -> {
|
||||
Initial ->
|
||||
case model.name {
|
||||
"" -> #(model, effect.none())
|
||||
name -> {
|
||||
let updated_items = [name, ..model.rooms]
|
||||
|
||||
#(Model(rooms: updated_items, name: ""), effect.none())
|
||||
_ -> #(model, effect.none())
|
||||
}
|
||||
SelectRoom(text) -> {
|
||||
#(Model(..model, name: Some(text)), effect.none())
|
||||
}
|
||||
KeyIn(newpin) -> {
|
||||
case string.length(newpin) < 4 {
|
||||
False -> #(Model(..model, pin: Some(newpin)), effect.none())
|
||||
True -> #(Model(..model, typed: newpin), effect.none())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UserTypedNewItem(text) -> #(Model(..model, name: text), effect.none())
|
||||
}
|
||||
}
|
||||
|
||||
fn view(model: Model) -> Element(Msg) {
|
||||
let styles = [
|
||||
#("max-width", "30ch"),
|
||||
#("margin", "0 auto"),
|
||||
#("display", "flex"),
|
||||
#("flex-direction", "column"),
|
||||
#("gap", "1em"),
|
||||
]
|
||||
|
||||
html.div([attribute.styles(styles)], [
|
||||
html.h1([], [html.text("Select your QuizRoom")]),
|
||||
fn view(model: Model) -> Element(State) {
|
||||
case model.name, model.pin {
|
||||
None, _ ->
|
||||
html.div([], [
|
||||
html.div([class("terminal-header")], [
|
||||
html.div([class("terminal-status")], [
|
||||
html.span([class("status-blink")], [html.text("●")]),
|
||||
html.text(" SYSTEM READY"),
|
||||
html.span([class("ml-8")], [
|
||||
html.text("<< Please Log On to use QuizTerm. >>"),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
view_room_list(model.rooms),
|
||||
])
|
||||
Some(_), None -> {
|
||||
html.div([], [
|
||||
html.div([class("terminal-header")], [
|
||||
html.div([class("terminal-status")], [
|
||||
html.span([class("status-blink")], [html.text("●")]),
|
||||
html.text(" SYSTEM READY"),
|
||||
html.span([class("ml-8")], [
|
||||
html.text("<< Please Log On to use QuizTerm. >>"),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
pin(),
|
||||
])
|
||||
}
|
||||
Some(_), Some(_) -> {
|
||||
html.div([], [
|
||||
html.div([class("terminal-header")], [
|
||||
html.div([class("terminal-status")], [
|
||||
html.span([class("status-blink")], [html.text("●")]),
|
||||
html.text(" SYSTEM READY"),
|
||||
html.span([class("ml-8")], [
|
||||
html.text("<< Please Log On to use QuizTerm. >>"),
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
html.text("FETCHING USERS FOR ROOM"),
|
||||
])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pin() -> Element(State) {
|
||||
html.div([attribute.class("terminal-section")], [
|
||||
html.div([attribute.class("terminal-label mb-4")], [
|
||||
html.text("Select room to play in"),
|
||||
]),
|
||||
html.div([attribute.class("participants-grid")], [
|
||||
html.text("Enter PIN code for room"),
|
||||
html.input([
|
||||
attribute.type_("password"),
|
||||
event.on_input(KeyIn),
|
||||
attribute.autofocus(True),
|
||||
]),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
||||
fn view_room_list(items: List(String)) -> Element(Msg) {
|
||||
case items {
|
||||
[] -> html.p([], [html.text("No items in your list yet.")])
|
||||
fn view_room_list(items: List(Room)) -> Element(State) {
|
||||
html.div([attribute.class("terminal-section")], [
|
||||
html.div([attribute.class("terminal-label mb-4")], [
|
||||
html.text("Select room to play in"),
|
||||
]),
|
||||
html.div([attribute.class("participants-grid")], case items {
|
||||
[] -> [html.text("No items in your list yet.")]
|
||||
_ -> {
|
||||
html.ul([], list.map(items, fn(item) { html.li([], [html.text(item)]) }))
|
||||
list.index_map(items, fn(item, index) {
|
||||
content_cell(index, item, SelectRoom)
|
||||
})
|
||||
}
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
fn content_cell(
|
||||
number: Int,
|
||||
room: Room,
|
||||
on_click: fn(String) -> msg,
|
||||
) -> Element(msg) {
|
||||
html.div([class("participant-login"), event.on_click(on_click(room.name))], [
|
||||
html.div([class("participant-name")], [
|
||||
html.text("► " <> "[#" <> int.to_string(number) <> "] Team " <> room.name),
|
||||
]),
|
||||
])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
import gleam/dynamic/decode
|
||||
import gleam/json
|
||||
|
||||
pub type GroceryItem {
|
||||
GroceryItem(name: String, quantity: Int)
|
||||
pub type Room {
|
||||
Room(id: String, name: String, pin: String)
|
||||
}
|
||||
|
||||
pub fn grocery_list_decoder() -> decode.Decoder(List(String)) {
|
||||
decode.list(decode.string)
|
||||
}
|
||||
|
||||
fn grocery_item_to_json(grocery_item: GroceryItem) -> json.Json {
|
||||
let GroceryItem(name:, quantity:) = grocery_item
|
||||
json.object([#("name", json.string(name)), #("quantity", json.int(quantity))])
|
||||
pub fn grocery_list_decoder() -> decode.Decoder(List(Room)) {
|
||||
let room_decoder = {
|
||||
use name <- decode.field("name", decode.string)
|
||||
use id <- decode.field("id", decode.string)
|
||||
use pin <- decode.field("key", decode.string)
|
||||
decode.success(Room(id:, name:, pin: ))
|
||||
}
|
||||
|
||||
pub fn grocery_list_to_json(items: List(GroceryItem)) -> json.Json {
|
||||
json.array(items, grocery_item_to_json)
|
||||
decode.list(room_decoder)
|
||||
}
|
||||
|
|
@ -121,6 +121,12 @@ body {
|
|||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.participant-login {
|
||||
border: 2px dashed #005500;
|
||||
padding: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.participant-hidden {
|
||||
border: 0px dashed #005500;
|
||||
padding: 1rem;
|
||||
|
|
@ -139,6 +145,12 @@ body {
|
|||
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.participant-login:hover {
|
||||
border: 2px solid #00ff00;
|
||||
background: rgba(0, 255, 0, 0.05);
|
||||
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.participant-name {
|
||||
color: #00ff00;
|
||||
font-weight: bold;
|
||||
|
|
|
|||
|
|
@ -7,9 +7,26 @@
|
|||
<script src="/client.js" type="module"></script>
|
||||
<script id="model" type="application/json">
|
||||
[
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
{
|
||||
"id": "abt",
|
||||
"name": "Billettering",
|
||||
"key": "T5X6"
|
||||
},
|
||||
{
|
||||
"id": "slg",
|
||||
"name": "Salg",
|
||||
"key": "6B4T"
|
||||
},
|
||||
{
|
||||
"id": "prs",
|
||||
"name": "Personalisering",
|
||||
"key": "P2Q5"
|
||||
},
|
||||
{
|
||||
"id": "srp",
|
||||
"name": "Support",
|
||||
"key": "P2Q5"
|
||||
}
|
||||
]
|
||||
</script>
|
||||
<link href="/static/layout.css" rel="stylesheet" type="text/css">
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ import backend/statehandler
|
|||
import gleam/bytes_tree
|
||||
import gleam/erlang/application
|
||||
import gleam/erlang/process
|
||||
import gleam/http
|
||||
import gleam/http/request
|
||||
import gleam/http/response.{type Response}
|
||||
import gleam/list
|
||||
import gleam/option.{None}
|
||||
import gleam/result
|
||||
import gleam/string
|
||||
import mist.{type ResponseData, File}
|
||||
import mist.{type ResponseData}
|
||||
import web/components/answerlist
|
||||
import web/components/card
|
||||
import web/components/control
|
||||
|
|
@ -25,7 +26,13 @@ pub fn main() {
|
|||
let assert Ok(room_handler) = roomhandler.initialize(state_handler)
|
||||
|
||||
let assert Ok(_) =
|
||||
fn(req) {
|
||||
fn(req: request.Request(mist.Connection)) {
|
||||
case req.method {
|
||||
// Filter out Head requests,
|
||||
http.Head ->
|
||||
response.new(200)
|
||||
|> response.set_body(mist.Bytes(bytes_tree.new()))
|
||||
_ ->
|
||||
case request.path_segments(req) {
|
||||
["lustre", "runtime.mjs"] -> serve_runtime()
|
||||
[] | ["index.html"] -> serve_static("root.html")
|
||||
|
|
@ -50,6 +57,7 @@ pub fn main() {
|
|||
)(req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|> mist.new
|
||||
|> mist.bind("0.0.0.0")
|
||||
|> mist.port(1234)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue