๐Ÿ‘ฉโ€๐Ÿš’ Firefighters API written in Gleam!
lustre gleam

:construction: add role selection to signup page

kacaii.dev 17540924 34a42a25

verified
+131 -47
+3 -3
client/src/client/page/login.gleam
··· 122 122 } 123 123 124 124 fn message_text(model: Model) -> element.Element(Msg) { 125 - let style = class("inline-block text-center text-white") 125 + let style = class("text-center text-white") 126 126 127 127 case model.message { 128 128 "" -> element.none() ··· 135 135 136 136 html.fieldset([style], [ 137 137 text_input( 138 - placeholder: "Email", 138 + placeholder: "user@sigo.br", 139 139 value: model.email, 140 140 icon: "md-email", 141 141 disabled: False, ··· 144 144 ), 145 145 146 146 text_input( 147 - placeholder: "Password", 147 + placeholder: "************", 148 148 value: model.password, 149 149 icon: "fa-key", 150 150 disabled: model.email == "",
+98 -32
client/src/client/page/signup.gleam
··· 1 1 import client/ui/header 2 2 import client/ui/nerd_font 3 + import gleam/http/response 4 + import gleam/list 3 5 import lustre/attribute.{class} as attr 6 + import lustre/effect 4 7 import lustre/element 5 8 import lustre/element/html 6 9 import lustre/event 10 + import rsvp 7 11 import shared/role 8 12 import shared/session 9 13 10 14 pub const empty = Model( 11 - name: "", 12 - role: role.Firefighter, 13 - email: "", 14 - password: "", 15 - confirm_password: "", 16 - active: False, 15 + user_name: "", 16 + user_role: role.None, 17 + user_email: "", 18 + user_password: "", 19 + user_confirm_password: "", 20 + user_is_active: False, 21 + loading: False, 17 22 ) 18 23 19 24 pub type Model { 20 25 Model( 21 - name: String, 22 - role: role.Role, 23 - email: String, 24 - password: String, 25 - confirm_password: String, 26 - active: Bool, 26 + user_name: String, 27 + user_role: role.Role, 28 + user_email: String, 29 + user_password: String, 30 + user_confirm_password: String, 31 + user_is_active: Bool, 32 + loading: Bool, 27 33 ) 28 34 } 29 35 30 36 pub type Msg { 31 37 UserUpdatedNameField(String) 32 38 UserUpdatedEmailField(String) 33 - UserSelectedRole(role.Role) 34 - UserSentRequest 39 + UserSelectedRole(String) 35 40 UserUpdatedPasswordField(String) 36 41 UserUpdatedConfirmPasswordField(String) 42 + 43 + UserSentRequest 44 + ServerSentResponse(Result(response.Response(String), rsvp.Error)) 37 45 } 38 46 39 47 pub fn view(_session: session.Session, model: Model) -> element.Element(Msg) { ··· 41 49 html.section(attributes, [container(model)]) 42 50 } 43 51 52 + pub fn update(model: Model, msg: Msg) -> #(Model, effect.Effect(Msg)) { 53 + case msg { 54 + UserUpdatedNameField(value) -> #( 55 + Model(..model, user_name: value), 56 + effect.none(), 57 + ) 58 + 59 + UserUpdatedEmailField(value) -> #( 60 + Model(..model, user_email: value), 61 + effect.none(), 62 + ) 63 + 64 + UserSelectedRole(value) -> { 65 + let role = role.from_string(value) 66 + #(Model(..model, user_role: role), effect.none()) 67 + } 68 + 69 + UserUpdatedPasswordField(value) -> #( 70 + Model(..model, user_password: value), 71 + effect.none(), 72 + ) 73 + 74 + UserUpdatedConfirmPasswordField(value) -> #( 75 + Model(..model, user_confirm_password: value), 76 + effect.none(), 77 + ) 78 + 79 + UserSentRequest -> { 80 + todo as "build and send request" 81 + 82 + let model = Model(..model, loading: True) 83 + #(model, todo as "rsvp_effect") 84 + } 85 + 86 + ServerSentResponse(Ok(_)) -> todo 87 + ServerSentResponse(Error(reason)) -> todo 88 + } 89 + } 90 + 44 91 fn container(model: Model) -> element.Element(Msg) { 45 92 let attributes = [ 46 93 class("grid grid-cols-1 gap-4 items-center"), ··· 48 95 class("rounded-md border border-surface"), 49 96 ] 50 97 51 - html.div(attributes, [fieldset(model), button()]) 98 + html.div(attributes, [fieldset(model), button(model)]) 52 99 } 53 100 54 - fn button() -> element.Element(Msg) { 101 + fn button(model: Model) -> element.Element(Msg) { 102 + let icon = case model.loading { 103 + True -> nerd_font.icon([class("animate-spin")], "extra-progress_spinner_1") 104 + False -> nerd_font.icon([], "fa-user_plus") 105 + } 106 + 55 107 let attributes = [ 56 108 class("p-2 font-bold text-white rounded-md bg-secondary"), 57 109 class("flex gap-2 justify-center items-center"), 58 110 class("hover:cursor-pointer hover:bg-accent hover:text-primary"), 111 + class("diabled:text-primary disabled:bg-surface"), 59 112 60 113 event.on_click(UserSentRequest), 114 + attr.disabled(model.loading), 61 115 ] 62 116 63 - html.button(attributes, [ 64 - nerd_font.icon([], "fa-user_plus"), 65 - html.text("Register"), 66 - ]) 117 + html.button(attributes, [icon, html.text("Register")]) 67 118 } 68 119 69 120 fn fieldset(model: Model) -> element.Element(Msg) { 70 - let attributes = [ 71 - class("flex flex-col gap-2"), 72 - ] 121 + let attributes = [class("flex flex-col gap-2")] 73 122 74 123 html.fieldset(attributes, [ 75 124 text_input( 76 125 placeholder: "Name", 77 - value: model.name, 126 + value: model.user_name, 78 127 icon: "fa-id_badge", 79 128 input_type: "text", 80 129 on_input: UserUpdatedNameField, 81 130 ), 82 131 83 132 text_input( 84 - placeholder: "Email", 85 - value: model.email, 133 + placeholder: "user@sigo.br", 134 + value: model.user_email, 86 135 icon: "md-email", 87 136 input_type: "email", 88 137 on_input: UserUpdatedEmailField, 89 138 ), 90 139 91 140 text_input( 92 - placeholder: "Password", 93 - value: model.password, 141 + placeholder: "********", 142 + value: model.user_password, 94 143 icon: "fa-key", 95 144 input_type: "password", 96 145 on_input: UserUpdatedPasswordField, 97 146 ), 98 147 99 148 text_input( 100 - placeholder: "Confirm password", 101 - value: model.confirm_password, 149 + placeholder: "********", 150 + value: model.user_confirm_password, 102 151 icon: "fa-key", 103 152 input_type: "password", 104 153 on_input: UserUpdatedConfirmPasswordField, 105 154 ), 155 + 156 + role_selection(model), 106 157 ]) 107 158 } 108 159 160 + fn role_selection(model: Model) -> element.Element(Msg) { 161 + let attributes = [ 162 + class("px-2 text-white rounded-md border border-surface"), 163 + 164 + attr.value(model.user_role |> role.to_string()), 165 + event.on_input(UserSelectedRole), 166 + ] 167 + 168 + html.select( 169 + attributes, 170 + list.map(role.all, fn(role) { html.option([], role.to_string(role)) }), 171 + ) 172 + } 173 + 109 174 fn text_input( 110 175 placeholder placeholder: String, 111 176 value value: String, ··· 113 178 input_type input_type: String, 114 179 on_input on_input: fn(String) -> Msg, 115 180 ) -> element.Element(Msg) { 116 - let icon_element = case icon { 181 + let icon = case icon { 117 182 "" -> element.none() 118 183 _ -> { 119 184 let attributes = [ ··· 138 203 event.on_input(on_input), 139 204 attr.type_(input_type), 140 205 ]), 141 - icon_element, 206 + 207 + icon, 142 208 ]) 143 209 }
+1 -1
justfile
··· 44 44 45 45 # Start the pod 46 46 [group("podman")] 47 - start: init-database 47 + start: 48 48 podman pod start {{ pod_name }} 49 49 50 50 # Stop pod
+27 -10
shared/src/shared/role.gleam
··· 9 9 Developer 10 10 Firefighter 11 11 Sargeant 12 + 13 + None 12 14 } 13 15 16 + pub const all = [ 17 + Admin, 18 + Analyst, 19 + Captain, 20 + Developer, 21 + Firefighter, 22 + Sargeant, 23 + 24 + None, 25 + ] 26 + 14 27 pub fn to_json(role: Role) -> json.Json { 15 28 case role { 16 29 Admin -> json.string("admin") ··· 19 32 Developer -> json.string("developer") 20 33 Firefighter -> json.string("firefighter") 21 34 Sargeant -> json.string("sargeant") 35 + 36 + None -> json.string("none") 22 37 } 23 38 } 24 39 ··· 31 46 "developer" -> decode.success(Developer) 32 47 "firefighter" -> decode.success(Firefighter) 33 48 "sargeant" -> decode.success(Sargeant) 49 + "none" -> decode.success(None) 34 50 35 - _ -> decode.failure(Firefighter, "Role") 51 + _ -> decode.failure(None, "Role") 36 52 } 37 53 } 38 54 39 - pub fn from_string(string: String) -> Result(Role, Nil) { 55 + pub fn from_string(string: String) -> Role { 40 56 case string.lowercase(string) { 41 - "admin" -> Ok(Admin) 42 - "analyst" -> Ok(Analyst) 43 - "captain" -> Ok(Captain) 44 - "developer" -> Ok(Developer) 45 - "firefighter" -> Ok(Firefighter) 46 - "sargeant" -> Ok(Sargeant) 47 - 48 - _ -> Error(Nil) 57 + "admin" -> Admin 58 + "analyst" -> Analyst 59 + "captain" -> Captain 60 + "developer" -> Developer 61 + "firefighter" -> Firefighter 62 + "sargeant" -> Sargeant 63 + "none" | _ -> None 49 64 } 50 65 } 51 66 ··· 57 72 Developer -> "developer" 58 73 Firefighter -> "firefighter" 59 74 Sargeant -> "sargeant" 75 + 76 + None -> "none" 60 77 } 61 78 }
+2 -1
sql/create/tables.sql
··· 4 4 'captain', 5 5 'developer', 6 6 'firefighter', 7 - 'sargeant' 7 + 'sargeant', 8 + 'none' 8 9 ); 9 10 10 11 create table user_account (