step for pin code login #1

Merged
lettosprey merged 2 commits from login into main 2026-03-30 23:49:21 +02:00
4 changed files with 97 additions and 47 deletions
Showing only changes of commit ccdba0f239 - Show all commits

7
.gitignore vendored
View file

@ -1 +1,6 @@
localbuild.sh **/.*
/localbuild.sh
/client/build
/server/build
/server/priv/static/client.js
/server/priv/static/index.html

View file

@ -6,9 +6,15 @@ FROM ghcr.io/gleam-lang/gleam:${GLEAM_VERSION}-erlang-alpine AS builder
COPY ./server/priv /quizterm/server/priv COPY ./server/priv /quizterm/server/priv
COPY ./server/src /quizterm/server/src COPY ./server/src /quizterm/server/src
COPY ./server/gleam.toml /quizterm/server/ 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 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 # Compile the server code
RUN cd /quizterm/server \ RUN cd /quizterm/server \
&& gleam export erlang-shipment && gleam export erlang-shipment

View file

@ -1,7 +1,9 @@
import gleam/int import gleam/int
import gleam/json import gleam/json
import gleam/list import gleam/list
import gleam/option.{type Option, None, Some}
import gleam/result import gleam/result
import gleam/string
import lustre import lustre
import lustre/attribute.{class} import lustre/attribute.{class}
import lustre/effect.{type Effect} import lustre/effect.{type Effect}
@ -10,7 +12,7 @@ import lustre/element/html
import lustre/event import lustre/event
import plinth/browser/document import plinth/browser/document
import plinth/browser/element as plinth_element import plinth/browser/element as plinth_element
import shared import shared.{type Room}
pub fn main() { pub fn main() {
let initial_items = let initial_items =
@ -29,37 +31,47 @@ pub fn main() {
} }
type Model { type Model {
Model(rooms: List(shared.Room), name: String) Model(
rooms: List(Room),
name: Option(String),
pin: Option(String),
typed: String,
)
} }
fn init(items: List(shared.Room)) -> #(Model, Effect(Msg)) { fn init(items: List(Room)) -> #(Model, Effect(State)) {
let model = Model(rooms: items, name: "") let model = Model(rooms: items, name: None, pin: None, typed: "")
#(model, effect.none()) #(model, effect.none())
} }
type Msg { type State {
UserTypedNewItem(String) SelectRoom(String)
UserAddedItem Initial
KeyIn(String)
} }
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { fn update(model: Model, msg: State) -> #(Model, Effect(msg)) {
case msg { case msg {
UserAddedItem -> Initial ->
case model.name { case model.name {
_ -> #(model, effect.none()) _ -> #(model, effect.none())
} }
UserTypedNewItem(text) -> { SelectRoom(text) -> {
echo "CLICK " <> text <> "aa" #(Model(..model, name: Some(text)), effect.none())
#(Model(..model, name: 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())
}
} }
} }
} }
fn view(model: Model) -> Element(Msg) { fn view(model: Model) -> Element(State) {
echo "NAME " <> model.name case model.name, model.pin {
case model.name { None, _ ->
"" ->
html.div([], [ html.div([], [
html.div([class("terminal-header")], [ html.div([class("terminal-header")], [
html.div([class("terminal-status")], [ html.div([class("terminal-status")], [
@ -72,7 +84,7 @@ fn view(model: Model) -> Element(Msg) {
]), ]),
view_room_list(model.rooms), view_room_list(model.rooms),
]) ])
_ -> { Some(_), None -> {
html.div([], [ html.div([], [
html.div([class("terminal-header")], [ html.div([class("terminal-header")], [
html.div([class("terminal-status")], [ html.div([class("terminal-status")], [
@ -86,21 +98,40 @@ fn view(model: Model) -> Element(Msg) {
pin(), 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(Msg) { fn pin() -> Element(State) {
html.div([attribute.class("terminal-section")], [ html.div([attribute.class("terminal-section")], [
html.div([attribute.class("terminal-label mb-4")], [ html.div([attribute.class("terminal-label mb-4")], [
html.text("Select room to play in"), html.text("Select room to play in"),
]), ]),
html.div([attribute.class("participants-grid")], [ html.div([attribute.class("participants-grid")], [
html.text("Enter PIN code for room"), 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(shared.Room)) -> Element(Msg) { fn view_room_list(items: List(Room)) -> Element(State) {
html.div([attribute.class("terminal-section")], [ html.div([attribute.class("terminal-section")], [
html.div([attribute.class("terminal-label mb-4")], [ html.div([attribute.class("terminal-label mb-4")], [
html.text("Select room to play in"), html.text("Select room to play in"),
@ -109,7 +140,7 @@ fn view_room_list(items: List(shared.Room)) -> Element(Msg) {
[] -> [html.text("No items in your list yet.")] [] -> [html.text("No items in your list yet.")]
_ -> { _ -> {
list.index_map(items, fn(item, index) { list.index_map(items, fn(item, index) {
content_cell(index, item, UserTypedNewItem) content_cell(index, item, SelectRoom)
}) })
} }
}), }),
@ -118,9 +149,9 @@ fn view_room_list(items: List(shared.Room)) -> Element(Msg) {
fn content_cell( fn content_cell(
number: Int, number: Int,
room: shared.Room, room: Room,
on_click: fn(String) -> Msg, on_click: fn(String) -> msg,
) -> Element(Msg) { ) -> Element(msg) {
html.div([class("participant-login"), event.on_click(on_click(room.name))], [ html.div([class("participant-login"), event.on_click(on_click(room.name))], [
html.div([class("participant-name")], [ html.div([class("participant-name")], [
html.text("" <> "[#" <> int.to_string(number) <> "] Team " <> room.name), html.text("" <> "[#" <> int.to_string(number) <> "] Team " <> room.name),

View file

@ -4,13 +4,14 @@ import backend/statehandler
import gleam/bytes_tree import gleam/bytes_tree
import gleam/erlang/application import gleam/erlang/application
import gleam/erlang/process import gleam/erlang/process
import gleam/http
import gleam/http/request import gleam/http/request
import gleam/http/response.{type Response} import gleam/http/response.{type Response}
import gleam/list import gleam/list
import gleam/option.{None} import gleam/option.{None}
import gleam/result import gleam/result
import gleam/string import gleam/string
import mist.{type ResponseData, File} import mist.{type ResponseData}
import web/components/answerlist import web/components/answerlist
import web/components/card import web/components/card
import web/components/control import web/components/control
@ -25,7 +26,13 @@ pub fn main() {
let assert Ok(room_handler) = roomhandler.initialize(state_handler) let assert Ok(room_handler) = roomhandler.initialize(state_handler)
let assert Ok(_) = 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) { case request.path_segments(req) {
["lustre", "runtime.mjs"] -> serve_runtime() ["lustre", "runtime.mjs"] -> serve_runtime()
[] | ["index.html"] -> serve_static("root.html") [] | ["index.html"] -> serve_static("root.html")
@ -50,6 +57,7 @@ pub fn main() {
)(req) )(req)
} }
} }
}
|> mist.new |> mist.new
|> mist.bind("0.0.0.0") |> mist.bind("0.0.0.0")
|> mist.port(1234) |> mist.port(1234)