Files
planning-poker-gwargh/frontend/src/main/scala/industries/sunshine/planningpoker/JoinRoomComponent.scala
efim 346069ae96 bugfix: disallow joining room without nickname
and also creating a room without a name
maybe maybe spectatorship sometimes later
2023-04-30 09:39:41 +04:00

182 lines
5.8 KiB
Scala

package industries.sunshine.planningpoker
import scala.scalajs.js
import scala.scalajs.js.annotation.*
import com.raquo.laminar.api.L.{*, given}
import io.laminext.fetch.Fetch
import io.laminext.fetch.circe._
import io.laminext.syntax.core._
import io.laminext.syntax.validation._
import scala.util.Failure
import scala.util.Success
import concurrent.ExecutionContext.Implicits.global
object JoinRoomComponent {
// TODO inputs for room name, room password, nick name, nick password
// do the get to /login route
// display errors if any.
// but then what? attempt to start the websocket?
// and if websocket closed show this component,
// if it's open - doesn't show this component, show the room.
// i suppose it should be managed on a level up
// or rather what? ws stream should be retried every time someone presses submit button
// and receives 200 ok
// so, parent page should send in observer for the successful auth. and on that observer - start \ restart the websocket
def nameInput(data: Var[String], placeholderText: String) = input(
className := "border-2 m-1 rounded",
placeholder := placeholderText,
controlled(
value <-- data.signal,
onInput.mapToValue --> data.writer
)
).validated(V.nonBlank("name must not be blank"))
def passInput(data: Var[String], placeholderText: String) = input(
tpe := "password",
className := "border-2 m-1 rounded",
placeholder := placeholderText,
controlled(
value <-- data.signal,
onInput.mapToValue --> data.writer
)
)
val roomNameVar = Var("")
val roomPassVar = Var("")
val nicknameVar = Var("")
val nicknamePass = Var("")
val (responsesStream, responseReceived) = EventStream.withCallback[FetchResponse[String]]
def render(loggedIn: Observer[Boolean]): Element = {
val submitButton = button(
"Join room",
className := "m-1 border-2 rounded-full border-green-400 bg-green-200 shadow-inner shadow-green-400 active:shadow-yellow-400 duration-100 ease-linear",
onClick
.mapTo {
(roomNameVar.now(), roomPassVar.now(), nicknameVar.now(), nicknamePass.now())
}
.flatMap { case (roomName, roomPass, nickname, nicknamePass) =>
Fetch
.post(
"/api/login",
body = Requests.LogIn(
roomName,
nickname,
roomPass,
nicknamePass
)
)
.text
.map { response =>
if (response.ok) {
loggedIn.onNext(true)
response
} else response
}
} --> responseReceived
)
val newRoomButton = button(
className := "m-1 border-2 rounded-full border-yellow-400 bg-yellow-200 shadow-inner shadow-yellow-400 active:shadow-red-400 duration-100 ease-linear",
"Create new room",
onClick
.mapTo {
(roomNameVar.now(), roomPassVar.now(), nicknameVar.now(), nicknamePass.now())
}
.flatMap { case (roomName, roomPass, nickname, nicknamePass) =>
Fetch
.post(
"/api/create-room",
body = Requests.LogIn(
roomName,
nickname,
roomPass,
nicknamePass
)
)
.text
.map { response =>
if (response.ok) {
loggedIn.onNext(true)
response
} else response
}
} --> responseReceived
)
val roomNameInput = nameInput(roomNameVar, "Enter room name:")
val roomPassInput = passInput(roomPassVar, "room password")
val nickNameInput = nameInput(nicknameVar, "Enter your nickname:")
val nickPassInput = passInput(nicknamePass, "nickname pass")
div(
className := "bg-green-50 w-full h-full flex justify-center",
div(
className := "w-60 flex flex-col justify-center",
"Logging in:",
roomNameInput,
div(
child.maybe <-- roomNameInput.validationError.optionMap(errors =>
span(
cls := "text-red-700 text-sm",
errors.map(error => div(error))
)
)
),
roomPassInput,
label("Alert: no https, please use throwaway passwords"),
nickNameInput,
div(
child.maybe <-- nickNameInput.validationError.optionMap(errors =>
span(
cls := "text-red-700 text-sm",
errors.map(error => div(error))
)
)
),
nickPassInput,
submitButton,
newRoomButton,
div(
div(
code("error log:")
),
div(
cls := "flex flex-col space-y-4 p-4 max-h-96 overflow-auto bg-gray-900",
children.command <-- responsesStream.recoverToTry.map {
case Success(response) => {
CollectionCommand.Append(
div(
div(
cls := "flex space-x-2 items-center",
code(cls := "text-green-500", "Status: "),
code(cls := "text-green-300", s"${response.status} ${response.statusText}")
),
div(
cls := "text-green-400 text-xs",
code(response.data)
)
)
)
}
case Failure(exception) =>
CollectionCommand.Append(
div(
div(
cls := "flex space-x-2 items-center",
code(cls := "text-red-500", "Error: "),
code(cls := "text-red-300", exception.getMessage)
)
)
)
}
)
)
)
)
}
}