╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ Login.zig ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
114 115 const password_input = &box.children.values()[password_index].widget.text_input; 116 password_input.options.label = if (failure == .wrong_password) 117 " password (invalid) " 118 else 119 " password "; 120 121 // refresh our entries in the session's focus-id -> *TextInput map with 122 // this frame's current input addresses, so the web/wasm form handling 123 // can find them by focus id 124 const inputs_arena = self.session.arena.allocator(); 125 try self.session.text_inputs.put(inputs_arena, username_input.getFocus().id, username_input); 126 try self.session.text_inputs.put(inputs_arena, password_input.getFocus().id, password_input); 127 128 try self.center.build(allocator, constraint, root_focus); 129 } 130 131 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { 132 const box = &self.center.child.box; 133 const child_id = box.focus.child_id orelse return; 134 const current = self.indexOf(child_id) orelse return; 135 136 switch (key) { 137 // Shift+Tab walks backward through the form, matching the 138 // typical UX; arrow_up does the same. 139 .arrow_up, .back_tab => if (current > 0) { 140 root_focus.setFocus(self.nav_ids[current - 1]); 141 return; 142 }, 143 // Tab moves to the next field, matching the form-style UX users 144 // expect; arrow_down does the same. 145 .arrow_down, .tab => if (current + 1 < self.nav_ids.len) { 146 root_focus.setFocus(self.nav_ids[current + 1]); 147 return; 148 }, 149 else => {}, 150 } 151 152 if (current == button_index) { 153 switch (key) { 154 .enter => { 155 try self.submit(allocator, root_focus); 156 return; 157 }, 158 .mouse => |mouse| { 159 if (inp.leftClickOn(root_focus, child_id, mouse)) { 160 try self.submit(allocator, root_focus); 161 return; 162 } 163 }, 164 else => {}, 165 } 166 } 167 168 const username_input = &box.children.values()[username_index].widget.text_input; 169 const password_input = &box.children.values()[password_index].widget.text_input; 170 const username_len_before = username_input.content.items.len; 171 const password_len_before = password_input.content.items.len; 172 173 if (box.children.getIndex(child_id)) |idx| { 174 try box.children.values()[idx].widget.input(allocator, key, root_focus); 175 } 176 177 if (username_input.content.items.len != username_len_before or 178 password_input.content.items.len != password_len_before) 179 { 180 // any edit clears the failure flag so the "(invalid)" label 181 // doesn't linger after the user has typed a correction. 182 self.session.data.login_failure = null; 183 } 184 } 185 186 pub fn clearGrid(self: *View) void { 187 self.center.clearGrid(); 188 } 189 190 pub fn getGrid(self: View) ?Grid { 191 return self.center.getGrid(); 192 } 193 194 pub fn getFocus(self: *View) *Focus { 195 return self.center.getFocus(); 196 } 197 198 pub fn getSelectedIndex(self: View) ?usize { 199 const child_id = self.center.child.box.focus.child_id orelse return null; 200 return self.indexOf(child_id); 201 } 202 203 fn indexOf(self: View, child_id: usize) ?usize { 204 for (self.nav_ids, 0..) |id, i| { 205 if (id == child_id) return i; 206 } 207 return null; 208 } 209 210 fn submit(self: *View, allocator: std.mem.Allocator, root_focus: *Focus) !void { 211 if (comptime wasm) { 212 // no DB cursor available on the wasm render path 213 self.session.data.login_failure = .unknown_user; 214 return; 215 } 216 217 const box = &self.center.child.box; 218 const username_input = &box.children.values()[username_index].widget.text_input; 219 const password_input = &box.children.values()[password_index].widget.text_input; 220 221 const username = try username_input.text(allocator); 222 defer allocator.free(username); 223 224 const password = try password_input.text(allocator); 225 defer allocator.free(password); 226 227 const haxy_moment = self.session.haxy_moment orelse { 228 // no DB context (e.g. wasm/web rendering path); treat as unknown. 229 self.session.data.login_failure = .unknown_user; 230 return; 231 }; 232 233 const result = try evt.User.verifyCredentials(evt.AdminDB, evt.admin_repo_opts.hash, haxy_moment, self.session.arena, username, password); 234 switch (result) { 235 .unknown_user => { 236 self.session.data.login_failure = .unknown_user; 237 return; 238 }, 239 .wrong_password => { 240 self.session.data.login_failure = .wrong_password; 241 return; 242 }, 243 .success => |user_id| { 244 // dupe user_id into the arena so the session can hold a stable slice 245 const user_id_stable = try self.session.arena.allocator().dupe(u8, &user_id); 246 self.session.data.user_id = user_id_stable; 247 self.session.data.login_failure = null; 248 249 // adopt the user's persisted prefs 250 try self.session.loadUserPrefs(); 251 252 // wipe the entered credentials so they don't linger if the 253 // user returns to this page after logging out 254 username_input.clear(allocator); 255 password_input.clear(allocator); 256 257 // jump focus back to the users tab — the login button we 258 // just pressed is about to be hidden by the tab-label swap 259 root_focus.setFocus(self.success_redirect_tab_id); 260 }, 261 } 262 } 263 }; 264