tangled
alpha
login
or
join now
kacaii.dev
/
sigo
0
fork
atom
๐ฉโ๐ Firefighters API written in Gleam!
lustre
gleam
0
fork
atom
overview
issues
pulls
pipelines
:construction: add role selection to signup page
kacaii.dev
1 week ago
17540924
34a42a25
verified
This commit was signed with the committer's
known signature
.
kacaii.dev
SSH Key Fingerprint:
SHA256:n9v7QGNWHCUv1x/483hCtPUvTsVabU5PzC5CSJMUNtI=
+131
-47
5 changed files
expand all
collapse all
unified
split
client
src
client
page
login.gleam
signup.gleam
justfile
shared
src
shared
role.gleam
sql
create
tables.sql
+3
-3
client/src/client/page/login.gleam
···
122
122
}
123
123
124
124
fn message_text(model: Model) -> element.Element(Msg) {
125
125
-
let style = class("inline-block text-center text-white")
125
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
138
-
placeholder: "Email",
138
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
147
-
placeholder: "Password",
147
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
3
+
import gleam/http/response
4
4
+
import gleam/list
3
5
import lustre/attribute.{class} as attr
6
6
+
import lustre/effect
4
7
import lustre/element
5
8
import lustre/element/html
6
9
import lustre/event
10
10
+
import rsvp
7
11
import shared/role
8
12
import shared/session
9
13
10
14
pub const empty = Model(
11
11
-
name: "",
12
12
-
role: role.Firefighter,
13
13
-
email: "",
14
14
-
password: "",
15
15
-
confirm_password: "",
16
16
-
active: False,
15
15
+
user_name: "",
16
16
+
user_role: role.None,
17
17
+
user_email: "",
18
18
+
user_password: "",
19
19
+
user_confirm_password: "",
20
20
+
user_is_active: False,
21
21
+
loading: False,
17
22
)
18
23
19
24
pub type Model {
20
25
Model(
21
21
-
name: String,
22
22
-
role: role.Role,
23
23
-
email: String,
24
24
-
password: String,
25
25
-
confirm_password: String,
26
26
-
active: Bool,
26
26
+
user_name: String,
27
27
+
user_role: role.Role,
28
28
+
user_email: String,
29
29
+
user_password: String,
30
30
+
user_confirm_password: String,
31
31
+
user_is_active: Bool,
32
32
+
loading: Bool,
27
33
)
28
34
}
29
35
30
36
pub type Msg {
31
37
UserUpdatedNameField(String)
32
38
UserUpdatedEmailField(String)
33
33
-
UserSelectedRole(role.Role)
34
34
-
UserSentRequest
39
39
+
UserSelectedRole(String)
35
40
UserUpdatedPasswordField(String)
36
41
UserUpdatedConfirmPasswordField(String)
42
42
+
43
43
+
UserSentRequest
44
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
52
+
pub fn update(model: Model, msg: Msg) -> #(Model, effect.Effect(Msg)) {
53
53
+
case msg {
54
54
+
UserUpdatedNameField(value) -> #(
55
55
+
Model(..model, user_name: value),
56
56
+
effect.none(),
57
57
+
)
58
58
+
59
59
+
UserUpdatedEmailField(value) -> #(
60
60
+
Model(..model, user_email: value),
61
61
+
effect.none(),
62
62
+
)
63
63
+
64
64
+
UserSelectedRole(value) -> {
65
65
+
let role = role.from_string(value)
66
66
+
#(Model(..model, user_role: role), effect.none())
67
67
+
}
68
68
+
69
69
+
UserUpdatedPasswordField(value) -> #(
70
70
+
Model(..model, user_password: value),
71
71
+
effect.none(),
72
72
+
)
73
73
+
74
74
+
UserUpdatedConfirmPasswordField(value) -> #(
75
75
+
Model(..model, user_confirm_password: value),
76
76
+
effect.none(),
77
77
+
)
78
78
+
79
79
+
UserSentRequest -> {
80
80
+
todo as "build and send request"
81
81
+
82
82
+
let model = Model(..model, loading: True)
83
83
+
#(model, todo as "rsvp_effect")
84
84
+
}
85
85
+
86
86
+
ServerSentResponse(Ok(_)) -> todo
87
87
+
ServerSentResponse(Error(reason)) -> todo
88
88
+
}
89
89
+
}
90
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
51
-
html.div(attributes, [fieldset(model), button()])
98
98
+
html.div(attributes, [fieldset(model), button(model)])
52
99
}
53
100
54
54
-
fn button() -> element.Element(Msg) {
101
101
+
fn button(model: Model) -> element.Element(Msg) {
102
102
+
let icon = case model.loading {
103
103
+
True -> nerd_font.icon([class("animate-spin")], "extra-progress_spinner_1")
104
104
+
False -> nerd_font.icon([], "fa-user_plus")
105
105
+
}
106
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
111
+
class("diabled:text-primary disabled:bg-surface"),
59
112
60
113
event.on_click(UserSentRequest),
114
114
+
attr.disabled(model.loading),
61
115
]
62
116
63
63
-
html.button(attributes, [
64
64
-
nerd_font.icon([], "fa-user_plus"),
65
65
-
html.text("Register"),
66
66
-
])
117
117
+
html.button(attributes, [icon, html.text("Register")])
67
118
}
68
119
69
120
fn fieldset(model: Model) -> element.Element(Msg) {
70
70
-
let attributes = [
71
71
-
class("flex flex-col gap-2"),
72
72
-
]
121
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
77
-
value: model.name,
126
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
84
-
placeholder: "Email",
85
85
-
value: model.email,
133
133
+
placeholder: "user@sigo.br",
134
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
92
-
placeholder: "Password",
93
93
-
value: model.password,
141
141
+
placeholder: "********",
142
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
100
-
placeholder: "Confirm password",
101
101
-
value: model.confirm_password,
149
149
+
placeholder: "********",
150
150
+
value: model.user_confirm_password,
102
151
icon: "fa-key",
103
152
input_type: "password",
104
153
on_input: UserUpdatedConfirmPasswordField,
105
154
),
155
155
+
156
156
+
role_selection(model),
106
157
])
107
158
}
108
159
160
160
+
fn role_selection(model: Model) -> element.Element(Msg) {
161
161
+
let attributes = [
162
162
+
class("px-2 text-white rounded-md border border-surface"),
163
163
+
164
164
+
attr.value(model.user_role |> role.to_string()),
165
165
+
event.on_input(UserSelectedRole),
166
166
+
]
167
167
+
168
168
+
html.select(
169
169
+
attributes,
170
170
+
list.map(role.all, fn(role) { html.option([], role.to_string(role)) }),
171
171
+
)
172
172
+
}
173
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
116
-
let icon_element = case icon {
181
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
141
-
icon_element,
206
206
+
207
207
+
icon,
142
208
])
143
209
}
+1
-1
justfile
···
44
44
45
45
# Start the pod
46
46
[group("podman")]
47
47
-
start: init-database
47
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
12
+
13
13
+
None
12
14
}
13
15
16
16
+
pub const all = [
17
17
+
Admin,
18
18
+
Analyst,
19
19
+
Captain,
20
20
+
Developer,
21
21
+
Firefighter,
22
22
+
Sargeant,
23
23
+
24
24
+
None,
25
25
+
]
26
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
35
+
36
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
49
+
"none" -> decode.success(None)
34
50
35
35
-
_ -> decode.failure(Firefighter, "Role")
51
51
+
_ -> decode.failure(None, "Role")
36
52
}
37
53
}
38
54
39
39
-
pub fn from_string(string: String) -> Result(Role, Nil) {
55
55
+
pub fn from_string(string: String) -> Role {
40
56
case string.lowercase(string) {
41
41
-
"admin" -> Ok(Admin)
42
42
-
"analyst" -> Ok(Analyst)
43
43
-
"captain" -> Ok(Captain)
44
44
-
"developer" -> Ok(Developer)
45
45
-
"firefighter" -> Ok(Firefighter)
46
46
-
"sargeant" -> Ok(Sargeant)
47
47
-
48
48
-
_ -> Error(Nil)
57
57
+
"admin" -> Admin
58
58
+
"analyst" -> Analyst
59
59
+
"captain" -> Captain
60
60
+
"developer" -> Developer
61
61
+
"firefighter" -> Firefighter
62
62
+
"sargeant" -> Sargeant
63
63
+
"none" | _ -> None
49
64
}
50
65
}
51
66
···
57
72
Developer -> "developer"
58
73
Firefighter -> "firefighter"
59
74
Sargeant -> "sargeant"
75
75
+
76
76
+
None -> "none"
60
77
}
61
78
}
+2
-1
sql/create/tables.sql
···
4
4
'captain',
5
5
'developer',
6
6
'firefighter',
7
7
-
'sargeant'
7
7
+
'sargeant',
8
8
+
'none'
8
9
);
9
10
10
11
create table user_account (