Profile + Edit Bio + Logout + styling

- Added handler for editing bio to handlers.js
- Added openProfile() and closeProfile() buttons in AppWindowContainer (Profile page is global)
- Added Logout and Profile functionality to Sidebar.js
  - SidebarItem(text).onClick() fires twice, unable to resolve
- Adjust Login page styling
- Added onLogout() to index.js (removes auth_token)
- Added Profile.js, displays user's profile picture (placeholder), name, and bio. User can edit bio.
- Added removeAuthToken() to util.js
- Added /signout to vite config
This commit is contained in:
2026-03-19 00:14:29 -04:00
parent ede464fb0d
commit 72f0518f9d
9 changed files with 185 additions and 9 deletions

View File

@@ -22,7 +22,7 @@ class AuthPage extends Shadow {
})
}
selectedPage = 2 // 1 == login, 2 == signup
selectedPage = 1 // 1 == login, 2 == signup
render() {
VStack(() => {
@@ -60,7 +60,7 @@ class AuthPage extends Shadow {
.color("var(--text)")
.horizontalAlign("center")
.margin("auto")
.marginTop(1, em)
.marginTop(7.5, em)
.marginBottom(0, em)
.gap(0.5, em)

View File

@@ -25,12 +25,6 @@ class Signup extends Shadow {
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")
@@ -43,6 +37,12 @@ class Signup extends Shadow {
.marginVertical(1, em)
.padding(1, em)
.styles(this.inputStyles)
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")

120
src/Profile/Profile.js Normal file
View File

@@ -0,0 +1,120 @@
import server from "../_/code/bridge/serverFunctions";
css(`
profile- textarea::-webkit-scrollbar {
display: none;
width: 0px;
height: 0px;
}
profile- textarea::-webkit-scrollbar-thumb {
background: transparent;
}
profile- textarea::-webkit-scrollbar-track {
background: transparent;
}
`)
class Profile extends Shadow {
constructor() {
super()
this.profile = global.profile
this.bioText = global.profile.bio ?? ""
}
render() {
ZStack(() => {
p("X")
.color("var(--quillred")
.fontSize(4, em)
.position("absolute")
.top(0.5, rem)
.right(2, rem)
.zIndex(1001)
.onClick(() => {
$("appwindowcontainer-").closeProfile()
})
form(() => {
VStack(() => {
HStack(() => { }) // Profile Image placeholder
.boxSizing("border-box")
.height(10, em)
.width(10, em)
.paddingHorizontal(0.5, em)
.border("1px solid var(--accent)")
.borderRadius(100, pct)
.background("var(--darkaccent)")
h1(this.profile.first_name + " " + this.profile.last_name)
.color("var(--headertext")
.width(70, pct)
.textAlign("center")
h2("Bio")
.color("var(--headertext")
.margin(0)
.paddingVertical(0.9, em)
.borderTop("2px solid var(--divider)")
.width(70, pct)
.textAlign("center")
textarea(this.bioText ? this.bioText : "Tap to start typing...")
.attr({ name: "bioinput" })
.padding(1, em)
.width(90, pct)
.height(15, em)
.boxSizing("border-box")
.background("var(--searchbackground)")
.color("var(--darktext)")
.border("1px solid color-mix(in srgb, var(--accent) 60%, transparent)")
.borderRadius(12, px)
.fontFamily("Arial")
.fontSize(1.1, em)
.outline("none")
.onAppear((e) => {
if (this.bioText) {
$("profile- textarea").innerText = this.bioText
}
})
.lineHeight(1.2, em)
button("Save Bio")
.padding(1, em)
.fontSize(1.1, em)
.borderRadius(12, px)
.background("var(--searchbackground)")
.color("var(--text)")
.border("1px solid var(--accent)")
.boxSizing("border-box")
.marginVertical(0.75, em)
})
.horizontalAlign("center")
.marginTop(5, em)
})
.onSubmit(async (e) => {
e.preventDefault();
const newBio = new FormData(e.target).get("bioinput");
if (newBio.trim() !== this.profile.bio.trim()) {
const result = await server.editBio(newBio, this.profile.id)
const { bio, updated_at } = result.data
global.profile.bio = bio
global.profile.updated_at = updated_at
this.profile = global.profile
}
})
})
.backgroundColor("var(--main)")
.overflowX("hidden")
.height(window.visualViewport.height - 20, px)
.boxSizing("border-box")
.width(100, pct)
.position("fixed")
.top(20, px)
.zIndex(1000)
// })
}
}
register(Profile)

View File

@@ -42,6 +42,10 @@ const handlers = {
async getJobs(networkId) {
return global.db.jobs.getByNetwork(networkId)
},
async editBio(newBio, userId) {
return global.db.members.editBio(newBio, userId)
}
}
export default handlers

View File

@@ -1,6 +1,9 @@
import "./AppWindow.js"
import "../Profile/Profile.js"
class AppWindowContainer extends Shadow {
isProfileOpen = false
render() {
ZStack(() => {
@@ -11,12 +14,26 @@ class AppWindowContainer extends Shadow {
})
.width(100, pct)
.gap(0)
if (this.isProfileOpen) {
Profile()
}
})
.height(100, pct)
.overflowY("hidden")
.display("flex")
.position("relative")
}
openProfile() {
this.isProfileOpen = true
this.rerender()
}
closeProfile() {
this.isProfileOpen = false
this.rerender()
}
}
register(AppWindowContainer)

View File

@@ -1,3 +1,5 @@
import util from "../util"
class Sidebar extends Shadow {
SidebarItem(text) {
@@ -7,10 +9,18 @@ class Sidebar extends Shadow {
.fontFamily("Sedan SC")
.marginLeft(2, em)
.fontStyle("italic")
.onClick(function () {
.onClick(function () { // BUG -- Fires twice every time
if(this.innerText === "Home") {
window.navigateTo("/")
return
} else if (this.innerText === "Logout") {
$("sidebar-").close()
global.onLogout()
return
} else if (this.innerText === "Profile") {
$("appwindowcontainer-").openProfile()
$("sidebar-").close()
return
}
window.navigateTo(this.innerText.toLowerCase().replace(/\s+/g, ""))
})
@@ -18,6 +28,7 @@ class Sidebar extends Shadow {
render() {
VStack(() => {
this.SidebarItem("Profile")
this.SidebarItem("Home")
this.SidebarItem("Map")
this.SidebarItem("Logout")
@@ -35,6 +46,12 @@ class Sidebar extends Shadow {
.zIndex(3)
}
close() {
if(this.style.right !== "-71vw") {
this.style.right = "-71vw"
}
}
toggle() {
if(this.style.right === "-71vw") {
this.style.right = "0vw"

View File

@@ -150,6 +150,16 @@ let Global = class {
// navigateTo("/")
}
async onLogout() {
await util.removeAuthToken()
await fetch(`${util.HOST}/signout`, {
method: "GET",
credentials: "include"
});
this.profile = null
AuthPage()
}
constructor() {
window.addEventListener("navigate", this.onNavigate)

View File

@@ -18,6 +18,10 @@ export default class util {
});
}
static async removeAuthToken() {
await Preferences.remove({ key: 'auth_token'})
}
static cssVariable(value) {
return getComputedStyle(document.documentElement)
.getPropertyValue("--" + value)

View File

@@ -24,6 +24,10 @@ export default defineConfig({
target: "http://localhost:10002",
changeOrigin: true
},
"/signout": {
target: "http://localhost:10002",
changeOrigin: true
},
"/profile": {
target: "http://localhost:10002",
changeOrigin: true