Signup/Login + styling adjustments

- Modified SearchBar styling
- Modified TopBar to display blank circle if the user has no networks (previously missing image icon)
- Refactored Login into AuthPage.js
- AuthPage contains a tab selector for switching between Signup and Login
- Both Login/Signup send the request and either receive an auth_token or an error message
- If auth_token, user will be logged in as usual, in both cases
- Signup validates user input before sending request
- Added /signup target in vite config file
This commit is contained in:
2026-03-18 17:36:03 -04:00
parent d1e4814593
commit 2082e0c7bc
8 changed files with 382 additions and 99 deletions

182
src/Home/AuthPage/Signup.js Normal file
View File

@@ -0,0 +1,182 @@
import { Preferences } from '@capacitor/preferences';
import util from "../../util.js"
class Signup extends Shadow {
inputStyles(el) {
return el
.background("var(--main)")
.color("var(--text)")
.border("1px solid var(--accent)")
.fontSize(0.9, rem)
.backgroundColor("var(--searchbackground)")
.borderRadius(12, px)
.outline("none")
.onTouch((start) => {
if (start) {
this.style.backgroundColor = "var(--accent)"
} else {
this.style.backgroundColor = "var(--searchbackground)"
}
})
}
errorMessage = ""
render() {
form(() => {
VStack(() => {
input("Email", "70vw")
.attr({ name: "email", type: "email" })
.margin("auto")
.marginVertical(1, em)
.padding(1, em)
.styles(this.inputStyles)
input("First Name", "70vw")
.attr({ name: "firstName", type: "text" })
.margin("auto")
.marginVertical(1, em)
.padding(1, em)
.styles(this.inputStyles)
input("Last Name", "70vw")
.attr({ name: "lastName", type: "text" })
.margin("auto")
.marginVertical(1, em)
.padding(1, em)
.styles(this.inputStyles)
input("Password", "70vw")
.attr({ name: "password", type: "password" })
.margin("auto")
.marginVertical(1, em)
.padding(1, em)
.styles(this.inputStyles)
input("Confirm Password", "70vw")
.attr({ name: "confirmPassword", type: "password" })
.margin("auto")
.marginVertical(1, em)
.padding(1, em)
.styles(this.inputStyles)
HStack(() => {
button("==>")
.padding(1, em)
.fontSize(0.9, rem)
.borderRadius(12, px)
.background("var(--searchbackground)")
.color("var(--text)")
.border("1px solid var(--accent)")
.boxSizing("border-box")
})
.width(70, vw)
.margin("auto")
.fontSize(0.9, rem)
.paddingLeft(0, em)
.paddingRight(2, em)
.marginVertical(1, em)
.border("1px solid transparent")
})
})
.height(100, pct)
.onSubmit(async (e) => {
e.preventDefault();
const data = new FormData(e.target);
if (this.verifyInput(data)) {
this.errorMessage = "";
await this.requestSignup(data);
} else {
console.log(this.errorMessage)
}
})
}
async requestSignup(data) {
const res = await fetch(`${util.HOST}/signup`, {
method: "POST",
headers: { "Content-Type": "application/json", "X-Client": "mobile" },
body: JSON.stringify({
firstName: data.get("firstName"),
lastName: data.get("lastName"),
email: data.get("email"),
password: data.get("password")
})
});
if (res.ok) {
const { token } = await res.json();
await Preferences.set({ key: 'auth_token', value: token });
global.onLogin();
} else {
const { error } = await res.json();
this.errorMessage = error;
console.error(this.errorMessage)
}
}
verifyInput(data) {
const firstName = data.get("firstName");
const lastName = data.get("lastName");
const email = data.get("email");
const password = data.get("password");
const confirmPassword = data.get("confirmPassword");
if (!firstName || firstName.trim() === "") {
this.errorMessage = "First name is required.";
return false
} else if (firstName.trim().length < 2) {
this.errorMessage = "First name must be at least 2 characters.";
return false
} else if (!/^[a-zA-Z\s'-]+$/.test(firstName.trim())) {
this.errorMessage = "First name contains invalid characters.";
return false
}
if (!lastName || lastName.trim() === "") {
this.errorMessage = "Last name is required.";
return false
} else if (lastName.trim().length < 2) {
this.errorMessage = "Last name must be at least 2 characters.";
return false
} else if (!/^[a-zA-Z\s'-]+$/.test(lastName.trim())) {
this.errorMessage = "Last name contains invalid characters.";
return false
}
if (!email || email.trim() === "") {
this.errorMessage = "Email is required.";
return false
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.trim())) {
this.errorMessage = "Please enter a valid email address.";
return false
}
if (!password) {
this.errorMessage = "Password is required.";
return false
} else if (password.length < 8) {
this.errorMessage = "Password must be at least 8 characters.";
return false
} else if (!/[A-Z]/.test(password)) {
this.errorMessage = "Password must contain at least one uppercase letter.";
return false
} else if (!/[a-z]/.test(password)) {
this.errorMessage = "Password must contain at least one lowercase letter.";
return false
} else if (!/[0-9]/.test(password)) {
this.errorMessage = "Password must contain at least one number.";
return false
} else if (!/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
this.errorMessage = "Password must contain at least one special character.";
return false
}
if (!confirmPassword) {
this.errorMessage = "Please confirm your password.";
return false
} else if (confirmPassword !== password) {
this.errorMessage = "Passwords do not match.";
return false
}
return true;
}
}
register(Signup)