diff --git a/client/src/client/page/register_crew.gleam b/client/src/client/page/register_crew.gleam index af05f5f..84b89ee 100644 --- a/client/src/client/page/register_crew.gleam +++ b/client/src/client/page/register_crew.gleam @@ -7,6 +7,7 @@ import glaze/oat/form import gleam/dynamic/decode import gleam/list import gleam/option +import gleam/string import icon import lustre/attribute.{class} as attr import lustre/effect.{type Effect} @@ -19,35 +20,37 @@ import shared/session.{type Session} import shared/user.{type User} pub const empty: Model = Model( - crew_leader: option.None, + selected_leader: option.None, crew_leader_email: "", - crew_members: [], - suggested_leaders: [], crew_name: input.Input( hint: "", id: "crew-name", input_type: "text", is_valid: True, - label: "Name", + label: "Crew name", placeholder: "Crew ABC", value: "", ), + selected_members: [], + suggested_leaders: [], + suggested_members: [], ) pub type Model { Model( - crew_leader: option.Option(User), - crew_leader_email: String, crew_name: input.Input, - crew_members: List(User), + crew_leader_email: String, + selected_leader: option.Option(User), + selected_members: List(User), suggested_leaders: List(User), + suggested_members: List(User), ) } pub type Msg { // User Management - UserClickedLeader(user: User) - UserClickedMember(user: User) + UserSelectedLeader(user: User) + UserSelectedMember(user: User) // Form UserTypedCrewName(value: String) @@ -56,16 +59,16 @@ pub type Msg { UserClickedSearchLeader // - ApiReturnedLeaders(Result(List(User), rsvp.Error)) + ApiSuggestedLeaders(Result(List(User), rsvp.Error)) } pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { case msg { - UserClickedLeader(user) -> { + UserSelectedLeader(user) -> { let new = Model( ..model, - crew_leader: option.Some(user), + selected_leader: option.Some(user), crew_leader_email: user.email, suggested_leaders: [], ) @@ -73,17 +76,12 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { #(new, effect.none()) } - UserClickedMember(user) -> - case list.contains(model.crew_members, user) { - True -> { - let apply = fn(each) { each != user } - let crew_members = list.filter(model.crew_members, apply) - - #(Model(..model, crew_members:), effect.none()) - } + UserSelectedMember(user) -> + case list.contains(model.selected_members, user) { + True -> #(model, effect.none()) False -> #( - Model(..model, crew_members: [user, ..model.crew_members]), + Model(..model, selected_members: [user, ..model.selected_members]), effect.none(), ) } @@ -110,12 +108,18 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { UserClickedSubmit -> #(model, effect.none()) UserTypedLeaderEmail(value) -> { - let crew_leader = case value { - "" -> option.None - _ -> model.crew_leader + let new = case value { + "" -> + Model( + ..model, + crew_leader_email: value, + selected_leader: option.None, + suggested_leaders: [], + ) + + value -> Model(..model, crew_leader_email: value) } - let new = Model(..model, crew_leader_email: value, crew_leader:) #(new, effect.none()) } @@ -128,18 +132,18 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { let effect = rsvp.get( api, - rsvp.expect_json(decode.list(user.decoder()), ApiReturnedLeaders), + rsvp.expect_json(decode.list(user.decoder()), ApiSuggestedLeaders), ) #(model, effect) } - ApiReturnedLeaders(Ok(found)) -> #( + ApiSuggestedLeaders(Ok(found)) -> #( Model(..model, suggested_leaders: found), effect.none(), ) - ApiReturnedLeaders(Error(_)) -> #(model, effect.none()) + ApiSuggestedLeaders(Error(_)) -> #(model, effect.none()) } } @@ -181,39 +185,61 @@ fn view_leader_selection(model: Model) -> element.Element(Msg) { event.on_input(UserTypedLeaderEmail), ] - let button = { + let search_button = { let on_click = event.on_click(UserClickedSearchLeader) - let style = [class("size-fit"), class("hover:text-background")] - let attributes = [on_click, button.outline(), ..style] + + let email_is_empty = string.is_empty(model.crew_leader_email) + let email_is_too_short = string.length(model.crew_leader_email) <= 3 + + let attributes = [ + on_click, + button.outline(), + attr.disabled(email_is_empty || email_is_too_short), + + class("h-full"), + class("hover:text-background"), + ] 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)) + found -> + html.div( + [class("grid grid-cols-1 gap-2 @md:grid-cols-2")], + list.map(found, user_to_leader_card), + ) } - html.div([], [ + html.div([class("@container")], [ html.label([attr.for(input_id), class("py-1")], [html.text("Leader email")]), - form.fieldset([form.group()], [form.input(attributes), button]), + form.fieldset([form.group()], [form.input(attributes), search_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)]), - ]), + let on_click = event.on_click(UserSelectedLeader(user)) + let hover_style = class("hover:cursor-pointer") + + let card_header = + card.header([class("flex gap-2")], [ + badge.badge([badge.outline()], [html.text(user.role |> role.to_string)]), + html.strong([class("truncate")], [html.text(user.full_name)]), + ]) + + let card_content = + card.content([], [html.p([class("truncate")], [html.text(user.email)])]) + + let card_attributes = [ + on_click, + hover_style, + class("flex flex-col items-center p-4"), + ] - html.p([], [html.text(user.email)]), - ]), + card.card(card_attributes, [ + html.div([class("flex flex-col gap-2")], [card_header, card_content]), ]) } diff --git a/client/src/icon.gleam b/client/src/icon.gleam index 8721c09..aeb86e2 100644 --- a/client/src/icon.gleam +++ b/client/src/icon.gleam @@ -170,3 +170,34 @@ pub fn mail_search(attributes: List(Attribute(a))) { ], ) } + +pub fn circle_user(attributes: List(Attribute(a))) { + svg.svg( + [ + attribute("stroke-linejoin", "round"), + attribute("stroke-linecap", "round"), + attribute("stroke-width", "2"), + attribute("stroke", "currentColor"), + attribute("fill", "none"), + attribute("viewBox", "0 0 24 24"), + attribute("height", "24"), + attribute("width", "24"), + ..attributes + ], + [ + svg.circle([ + attribute("r", "10"), + attribute("cy", "12"), + attribute("cx", "12"), + ]), + svg.circle([ + attribute("r", "3"), + attribute("cy", "10"), + attribute("cx", "12"), + ]), + svg.path([ + attribute("d", "M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"), + ]), + ], + ) +}