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:
83
src/Home/AuthPage/AuthPage.js
Normal file
83
src/Home/AuthPage/AuthPage.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Preferences } from '@capacitor/preferences';
|
||||||
|
import util from "../../util.js"
|
||||||
|
import "./Login.js";
|
||||||
|
import "./Signup.js"
|
||||||
|
|
||||||
|
class AuthPage 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)"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedPage = 2 // 1 == login, 2 == signup
|
||||||
|
|
||||||
|
render() {
|
||||||
|
VStack(() => {
|
||||||
|
img(window.matchMedia('(prefers-color-scheme: dark)').matches ? "/_/icons/columnwhite.svg" : "/_/icons/logo.svg", window.isMobile() ? "5vmax" : "3vmax")
|
||||||
|
.marginTop(5, em)
|
||||||
|
.marginLeft(2, em)
|
||||||
|
.onClick((done) => {
|
||||||
|
window.navigateTo("/")
|
||||||
|
})
|
||||||
|
|
||||||
|
HStack(() => {
|
||||||
|
p("Login")
|
||||||
|
.fontWeight(this.selectedPage === 1 ? "bold" : "normal")
|
||||||
|
.padding(0.75, em)
|
||||||
|
.borderRadius(12, px)
|
||||||
|
.background(this.selectedPage === 1 ? "var(--darkaccent)" : "transparent")
|
||||||
|
.onTap(() => {
|
||||||
|
this.selectedPage = 1;
|
||||||
|
this.rerender();
|
||||||
|
})
|
||||||
|
|
||||||
|
p("Signup")
|
||||||
|
.fontWeight(this.selectedPage === 2 ? "bold" : "normal")
|
||||||
|
.padding(0.75, em)
|
||||||
|
.borderRadius(12, px)
|
||||||
|
.background(this.selectedPage === 2 ? "var(--darkaccent)" : "transparent")
|
||||||
|
.onTap(() => {
|
||||||
|
this.selectedPage = 2;
|
||||||
|
this.rerender();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.padding(0.25, em)
|
||||||
|
.borderRadius(12, px)
|
||||||
|
.background("var(--accent)")
|
||||||
|
.color("var(--text)")
|
||||||
|
.horizontalAlign("center")
|
||||||
|
.margin("auto")
|
||||||
|
.marginTop(1, em)
|
||||||
|
.marginBottom(0, em)
|
||||||
|
.gap(0.5, em)
|
||||||
|
|
||||||
|
switch (this.selectedPage) {
|
||||||
|
case 1:
|
||||||
|
Login()
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
Signup()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.width(100, vw)
|
||||||
|
.height(100, vh)
|
||||||
|
.margin(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register(AuthPage)
|
||||||
87
src/Home/AuthPage/Login.js
Normal file
87
src/Home/AuthPage/Login.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { Preferences } from '@capacitor/preferences';
|
||||||
|
import util from "../../util.js"
|
||||||
|
|
||||||
|
class Login 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)"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
form(() => {
|
||||||
|
VStack(() => {
|
||||||
|
input("Email", "70vw")
|
||||||
|
.attr({ name: "email", type: "email" })
|
||||||
|
.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)
|
||||||
|
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);
|
||||||
|
await this.requestLogin(data);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestLogin(data) {
|
||||||
|
const res = await fetch(`${util.HOST}/login`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json", "X-Client": "mobile" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
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(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register(Login)
|
||||||
182
src/Home/AuthPage/Signup.js
Normal file
182
src/Home/AuthPage/Signup.js
Normal 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)
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import { Preferences } from '@capacitor/preferences';
|
|
||||||
import util from "../util.js"
|
|
||||||
|
|
||||||
class Login 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)"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
ZStack(() => {
|
|
||||||
img(window.matchMedia('(prefers-color-scheme: dark)').matches ? "/_/icons/columnwhite.svg" : "/_/icons/logo.svg", window.isMobile() ? "5vmax" : "3vmax")
|
|
||||||
.position("absolute")
|
|
||||||
.top(5, em)
|
|
||||||
.left(2, em)
|
|
||||||
.onClick((done) => {
|
|
||||||
window.navigateTo("/")
|
|
||||||
})
|
|
||||||
|
|
||||||
form(() => {
|
|
||||||
input("Email", "70vw")
|
|
||||||
.attr({name: "email", type: "email"})
|
|
||||||
.margin(1, em).padding(1, em)
|
|
||||||
.styles(this.inputStyles)
|
|
||||||
input("Password", "70vw")
|
|
||||||
.attr({name: "password", type: "password"})
|
|
||||||
.margin(1, em).padding(1, em)
|
|
||||||
.styles(this.inputStyles)
|
|
||||||
button("==>")
|
|
||||||
.margin(1, em).padding(1, em)
|
|
||||||
.fontSize(0.9, rem)
|
|
||||||
.borderRadius(12, px)
|
|
||||||
.background("var(--searchbackground)")
|
|
||||||
.color("var(--text)")
|
|
||||||
.border("1px solid var(--accent)")
|
|
||||||
})
|
|
||||||
.x(50, vw).y(50, vh)
|
|
||||||
.center()
|
|
||||||
.onSubmit(async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const data = new FormData(e.target);
|
|
||||||
|
|
||||||
const res = await fetch(`${util.HOST}/login`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json", "X-Client": "mobile" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
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();
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.background("rgb(69, 20, 13)")
|
|
||||||
.width(100, vw)
|
|
||||||
.height(100, pct)
|
|
||||||
.margin(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
register(Login)
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
css(`
|
css(`
|
||||||
searchbar- input::placeholder {
|
searchbar- input::placeholder {
|
||||||
color: #5C504D
|
color: #5C504D;
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class SearchBar extends Shadow {
|
|||||||
.paddingVertical(0.75, em)
|
.paddingVertical(0.75, em)
|
||||||
.boxSizing("border-box")
|
.boxSizing("border-box")
|
||||||
.paddingHorizontal(1, em)
|
.paddingHorizontal(1, em)
|
||||||
.background("var(--darkaccent)")
|
.background("var(--searchbackground)")
|
||||||
.color("var(--accent)")
|
.color("var(--accent)")
|
||||||
.marginBottom(1, em)
|
.marginBottom(1, em)
|
||||||
.border("1px solid var(--accent)")
|
.border("1px solid var(--accent)")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import util from "../util.js"
|
|||||||
class TopBar extends Shadow {
|
class TopBar extends Shadow {
|
||||||
render() {
|
render() {
|
||||||
HStack(() => {
|
HStack(() => {
|
||||||
|
if (global.currentNetwork.logo) {
|
||||||
img(`${util.HOST}/db/images/${global.currentNetwork.logo}`, "2.5em", "2.5em")
|
img(`${util.HOST}/db/images/${global.currentNetwork.logo}`, "2.5em", "2.5em")
|
||||||
.borderRadius("50", pct)
|
.borderRadius("50", pct)
|
||||||
.objectFit("cover")
|
.objectFit("cover")
|
||||||
@@ -16,6 +17,14 @@ class TopBar extends Shadow {
|
|||||||
$("sidebar-").toggle()
|
$("sidebar-").toggle()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
HStack(() => { })
|
||||||
|
.height(2.5, em)
|
||||||
|
.width(2.5, em)
|
||||||
|
.padding(0.3, em)
|
||||||
|
.background("var(--accent)")
|
||||||
|
.borderRadius("50", pct)
|
||||||
|
}
|
||||||
|
|
||||||
p(global.currentApp())
|
p(global.currentApp())
|
||||||
.color("var(--headertext)")
|
.color("var(--headertext)")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Socket from "/_/code/ws/Socket.js"
|
import Socket from "/_/code/ws/Socket.js"
|
||||||
import "./Home/Home.js"
|
import "./Home/Home.js"
|
||||||
import "./Home/Login.js"
|
import "./Home/AuthPage/AuthPage.js"
|
||||||
import "./Home/ConnectionError.js"
|
import "./Home/ConnectionError.js"
|
||||||
import util from "./util.js"
|
import util from "./util.js"
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ let Global = class {
|
|||||||
|
|
||||||
this.getProfile().then(async (status) => {
|
this.getProfile().then(async (status) => {
|
||||||
if (status === 401) {
|
if (status === 401) {
|
||||||
Login()
|
AuthPage()
|
||||||
} else if(status === 500) {
|
} else if(status === 500) {
|
||||||
ConnectionError()
|
ConnectionError()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export default defineConfig({
|
|||||||
target: "http://localhost:10002",
|
target: "http://localhost:10002",
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
},
|
},
|
||||||
|
"/signup": {
|
||||||
|
target: "http://localhost:10002",
|
||||||
|
changeOrigin: true
|
||||||
|
},
|
||||||
"/profile": {
|
"/profile": {
|
||||||
target: "http://localhost:10002",
|
target: "http://localhost:10002",
|
||||||
changeOrigin: true
|
changeOrigin: true
|
||||||
|
|||||||
Reference in New Issue
Block a user