diff --git a/client/src/client/page/register_crew.gleam b/client/src/client/page/register_crew.gleam index 2882b52..af05f5f 100644 --- a/client/src/client/page/register_crew.gleam +++ b/client/src/client/page/register_crew.gleam @@ -1,8 +1,10 @@ import client/ui/header import client/ui/input +import glaze/oat/badge import glaze/oat/button import glaze/oat/card import glaze/oat/form +import gleam/dynamic/decode import gleam/list import gleam/option import icon @@ -11,6 +13,8 @@ import lustre/effect.{type Effect} import lustre/element import lustre/element/html import lustre/event +import rsvp +import shared/role import shared/session.{type Session} import shared/user.{type User} @@ -18,6 +22,7 @@ pub const empty: Model = Model( crew_leader: option.None, crew_leader_email: "", crew_members: [], + suggested_leaders: [], crew_name: input.Input( hint: "", id: "crew-name", @@ -35,6 +40,7 @@ pub type Model { crew_leader_email: String, crew_name: input.Input, crew_members: List(User), + suggested_leaders: List(User), ) } @@ -48,12 +54,22 @@ pub type Msg { UserTypedLeaderEmail(value: String) UserClickedSubmit UserClickedSearchLeader + + // + ApiReturnedLeaders(Result(List(User), rsvp.Error)) } pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { case msg { UserClickedLeader(user) -> { - let new = Model(..model, crew_leader: option.Some(user)) + let new = + Model( + ..model, + crew_leader: option.Some(user), + crew_leader_email: user.email, + suggested_leaders: [], + ) + #(new, effect.none()) } @@ -94,11 +110,36 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { UserClickedSubmit -> #(model, effect.none()) UserTypedLeaderEmail(value) -> { - let new = Model(..model, crew_leader_email: value) + let crew_leader = case value { + "" -> option.None + _ -> model.crew_leader + } + + let new = Model(..model, crew_leader_email: value, crew_leader:) #(new, effect.none()) } - UserClickedSearchLeader -> todo + UserClickedSearchLeader -> { + let api = + "/api/autocomplete/email?prefix=" + <> model.crew_leader_email + <> "&limit=2" + + let effect = + rsvp.get( + api, + rsvp.expect_json(decode.list(user.decoder()), ApiReturnedLeaders), + ) + + #(model, effect) + } + + ApiReturnedLeaders(Ok(found)) -> #( + Model(..model, suggested_leaders: found), + effect.none(), + ) + + ApiReturnedLeaders(Error(_)) -> #(model, effect.none()) } } @@ -111,7 +152,7 @@ pub fn view(_session: Session, model: Model) -> element.Element(Msg) { html.section(attributes, [ view_form([ input.view(model.crew_name, UserTypedCrewName), - view_leader_selection(), + view_leader_selection(model), ]), ]) } @@ -126,17 +167,18 @@ fn view_form(elements: List(element.Element(Msg))) -> element.Element(Msg) { ]) } -fn view_leader_selection() -> element.Element(Msg) { +fn view_leader_selection(model: Model) -> element.Element(Msg) { let input_id = "leader-email-input" let attributes = [ form.field(), attr.type_("email"), attr.id(input_id), + attr.value(model.crew_leader_email), attr.placeholder("leader@email.com"), class("placeholder:text-faint"), - event.throttle(event.on_input(UserTypedLeaderEmail), 500), + event.on_input(UserTypedLeaderEmail), ] let button = { @@ -147,9 +189,31 @@ fn view_leader_selection() -> element.Element(Msg) { button.button(attributes, [icon.mail_search([])]) } + let view_leaders = case model.suggested_leaders { + [] -> element.none() + found -> html.div([], list.map(found, user_to_leader_card)) + } + html.div([], [ html.label([attr.for(input_id), class("py-1")], [html.text("Leader email")]), form.fieldset([form.group()], [form.input(attributes), button]), + view_leaders, + ]) +} + +fn user_to_leader_card(user: User) -> element.Element(Msg) { + let on_click = event.on_click(UserClickedLeader(user)) + let style = [class("hover:cursor-pointer"), class("py-2")] + + card.card([], [ + card.content([on_click, ..style], [ + html.div([class("flex gap-2")], [ + badge.badge([badge.outline()], [html.text(user.role |> role.to_string)]), + html.strong([], [html.text(user.full_name)]), + ]), + + html.p([], [html.text(user.email)]), + ]), ]) }