1, 4: Fixing 404 after logged out, adding market entry
This commit is contained in:
@@ -14,16 +14,18 @@ export default class AuthHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isLoggedInUser(req, res) {
|
isLoggedInUser(req, res) {
|
||||||
const token = req.cookies.auth_token; // read cookie
|
const token = req.cookies.auth_token;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return true
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
req.user = decoded;
|
||||||
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import express from 'express';
|
|||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
import cookieParser from 'cookie-parser'
|
import cookieParser from 'cookie-parser'
|
||||||
import http from 'http'
|
import http from 'http'
|
||||||
|
import fs from 'fs'
|
||||||
import chalk from 'chalk'
|
import chalk from 'chalk'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -20,11 +21,13 @@ class Server {
|
|||||||
db;
|
db;
|
||||||
auth;
|
auth;
|
||||||
UIPath = path.join(__dirname, '../ui')
|
UIPath = path.join(__dirname, '../ui')
|
||||||
|
DBPath = path.join(__dirname, '../db')
|
||||||
|
|
||||||
registerRoutes(router) {
|
registerRoutes(router) {
|
||||||
// router.post('/api/location', handlers.updateLocation)
|
// router.post('/api/location', handlers.updateLocation)
|
||||||
router.post('/login', this.auth.login)
|
router.post('/login', this.auth.login)
|
||||||
router.get('/signout', this.auth.logout)
|
router.get('/signout', this.auth.logout)
|
||||||
|
router.get('/db/images/*', this.getUserImage)
|
||||||
router.get('/*', this.get)
|
router.get('/*', this.get)
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
@@ -49,16 +52,40 @@ class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUserImage = async (req, res) => {
|
||||||
|
function getFileByNumber(dir, number) {
|
||||||
|
const files = fs.readdirSync(dir);
|
||||||
|
const match = files.find(file => {
|
||||||
|
const base = path.parse(file).name; // filename without extension
|
||||||
|
return base === String(number);
|
||||||
|
});
|
||||||
|
return match ? path.join(dir, match) : null;
|
||||||
|
}
|
||||||
|
let filePath = getFileByNumber(path.join(this.DBPath, "images"), path.basename(req.url))
|
||||||
|
|
||||||
|
res.sendFile(filePath)
|
||||||
|
}
|
||||||
|
|
||||||
get = async (req, res) => {
|
get = async (req, res) => {
|
||||||
if(!this.auth.isLoggedInUser(req, res)) {
|
if(!this.auth.isLoggedInUser(req, res)) {
|
||||||
console.log("Not logged in")
|
console.log("Not logged in")
|
||||||
let url = req.url
|
let url = req.url
|
||||||
|
|
||||||
|
if(!url.includes(".")) { // Page request
|
||||||
if(url === "/") {
|
if(url === "/") {
|
||||||
url = "/index.html"
|
url = "/index.html"
|
||||||
} else if(!url.includes(".")) { // TODO: Make public app single-page
|
} else {
|
||||||
url = path.join("/pages", url) + ".html"
|
url = path.join("/pages", url) + ".html"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filePath = path.join(this.UIPath, "public", url);
|
||||||
|
res.sendFile(filePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.log("File not found, sending fallback:", filePath);
|
||||||
|
res.redirect("/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else { // File Request
|
||||||
let filePath;
|
let filePath;
|
||||||
if(url.startsWith("/_")) {
|
if(url.startsWith("/_")) {
|
||||||
filePath = path.join(this.UIPath, url);
|
filePath = path.join(this.UIPath, url);
|
||||||
@@ -67,6 +94,7 @@ class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.sendFile(filePath);
|
res.sendFile(filePath);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let url = req.url
|
let url = req.url
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Sam Russell
|
Sam Russell
|
||||||
Captured Sun
|
Captured Sun
|
||||||
|
11.9.25 - changed p(innerText) to p(innerHTML), adjusted onNavigate to work for multiple elements and with correct "this" scope
|
||||||
11.7.25 - changed registerShadow() to register(), changed onClick() to be like onHover()
|
11.7.25 - changed registerShadow() to register(), changed onClick() to be like onHover()
|
||||||
11.6.25 - adding default value for "button()" "children" parameter
|
11.6.25 - adding default value for "button()" "children" parameter
|
||||||
10.29.25 - adding "gap()" and "label()" functions
|
10.29.25 - adding "gap()" and "label()" functions
|
||||||
@@ -562,12 +563,12 @@ HTMLImageElement.prototype.backgroundColor = function(value) {
|
|||||||
return this; // Always returns the element itself
|
return this; // Always returns the element itself
|
||||||
};
|
};
|
||||||
|
|
||||||
window.p = function p(innerText) {
|
window.p = function p(innerHTML) {
|
||||||
let el = document.createElement("p")
|
let el = document.createElement("p")
|
||||||
if(typeof innerText === "function") {
|
if(typeof innerText === "function") {
|
||||||
el.render = innerText
|
el.render = innerHTML
|
||||||
} else {
|
} else {
|
||||||
el.innerText = innerText
|
el.innerHTML = innerHTML
|
||||||
}
|
}
|
||||||
el.style.margin = "0";
|
el.style.margin = "0";
|
||||||
quill.render(el)
|
quill.render(el)
|
||||||
@@ -765,13 +766,15 @@ HTMLElement.prototype.onKeyDown = function(cb) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* QUIRK 1:
|
/* WHY THIS LISTENER IS THE WAY IT IS:
|
||||||
In all the other callback functions, the user can choose the scope of "this". It can be either the parent shadow or the element itself.
|
- If we dispatch the "navigate" event on the window (as one would expect for a "navigate" event), a listener placed on the element will not pick it up.
|
||||||
This listener only allows for the latter functionality. This is because the navigate event fires on the window.
|
- However, if we add the event as a window event, it won't have the "this" scope that a callback normally would.
|
||||||
Without binding, "this" would refer only to the window. So here we are compromising on one of the two.
|
- Then, if we try to add that scope using bind(), it makes the function.toString() unreadable, which means we will get false positives for duplicate listeners.
|
||||||
|
- Therefore, we just have to attach the navigate event to the element, and manually trigger that when the window listener fires.
|
||||||
*/
|
*/
|
||||||
HTMLElement.prototype.onNavigate = function(cb) {
|
HTMLElement.prototype.onNavigate = function(cb) {
|
||||||
window._storeListener(window, "navigate", cb.bind(this));
|
this._storeListener("navigate", cb);
|
||||||
|
window.addEventListener("navigate", () => this.dispatchEvent(new CustomEvent("navigate")))
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Hyperia</title>
|
<title>Hyperia</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" href="_/icons/logo.svg">
|
<link rel="icon" href="/_/icons/logo.svg">
|
||||||
<link rel="stylesheet" href="_/code/shared.css">
|
<link rel="stylesheet" href="_/code/shared.css">
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
|||||||
178
ui/site/apps/Forum/Forum.js
Normal file
178
ui/site/apps/Forum/Forum.js
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
css(`
|
||||||
|
messages- {
|
||||||
|
font-family: 'Bona';
|
||||||
|
}
|
||||||
|
|
||||||
|
messages- input::placeholder {
|
||||||
|
font-family: 'Bona Nova';
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
appearance: none; /* remove default style */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked {
|
||||||
|
background-color: var(--red);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
class Messages extends Shadow {
|
||||||
|
friends = []
|
||||||
|
conversations = []
|
||||||
|
|
||||||
|
render() {
|
||||||
|
ZStack(() => {
|
||||||
|
HStack(() => {
|
||||||
|
VStack(() => {
|
||||||
|
h3("Friends")
|
||||||
|
.marginTop(0)
|
||||||
|
.marginBottom(1, em)
|
||||||
|
.marginLeft(0.4, em)
|
||||||
|
|
||||||
|
if (this.friends.length > 1) {
|
||||||
|
for(let i = 0; i < this.friends.length; i++) {
|
||||||
|
p(this.friends[i].name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p("No Friends!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.height(100, vh)
|
||||||
|
.paddingLeft(2, em)
|
||||||
|
.paddingRight(2, em)
|
||||||
|
.paddingTop(2, em)
|
||||||
|
.gap(0, em)
|
||||||
|
.borderRight("1px solid var(--periwinkle)")
|
||||||
|
|
||||||
|
VStack(() => {
|
||||||
|
h3("Conversations")
|
||||||
|
.marginTop(0)
|
||||||
|
.marginBottom(1, em)
|
||||||
|
.marginLeft(0.4, em)
|
||||||
|
|
||||||
|
if (this.conversations.length > 1) {
|
||||||
|
for(let i = 0; i < this.conversations.length; i++) {
|
||||||
|
p(this.conversations[i].name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p("No Conversations!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.height(100, vh)
|
||||||
|
.paddingLeft(2, em)
|
||||||
|
.paddingRight(2, em)
|
||||||
|
.paddingTop(2, em)
|
||||||
|
.gap(0, em)
|
||||||
|
.borderRight("1px solid var(--periwinkle)")
|
||||||
|
})
|
||||||
|
.width(100, "%")
|
||||||
|
.x(0).y(13, vh)
|
||||||
|
.borderTop("1px solid var(--periwinkle)")
|
||||||
|
|
||||||
|
p("0 Items")
|
||||||
|
.position("absolute")
|
||||||
|
.x(50, vw).y(50, vh)
|
||||||
|
.transform("translate(-50%, -50%)")
|
||||||
|
|
||||||
|
HStack(() => {
|
||||||
|
input("Search messages...", "45vw")
|
||||||
|
.attr({
|
||||||
|
"type": "text"
|
||||||
|
})
|
||||||
|
.fontSize(1.1, em)
|
||||||
|
.paddingLeft(1.3, em)
|
||||||
|
.background("transparent")
|
||||||
|
.border("1px solid var(--periwinkle)")
|
||||||
|
.outline("none")
|
||||||
|
.color("var(--accent)")
|
||||||
|
.borderRadius(10, px)
|
||||||
|
|
||||||
|
button("Search")
|
||||||
|
.marginLeft(2, em)
|
||||||
|
.borderRadius(10, px)
|
||||||
|
.background("transparent")
|
||||||
|
.border("1px solid var(--periwinkle)")
|
||||||
|
.color("var(--accent)")
|
||||||
|
.fontFamily("Bona Nova")
|
||||||
|
.onHover(function (hovering) {
|
||||||
|
if(hovering) {
|
||||||
|
this.style.background = "var(--green)"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.style.background = "transparent"
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
button("+ New Message")
|
||||||
|
.width(13, em)
|
||||||
|
.marginLeft(1, em)
|
||||||
|
.borderRadius(10, px)
|
||||||
|
.background("transparent")
|
||||||
|
.border("1px solid var(--periwinkle)")
|
||||||
|
.color("var(--accent)")
|
||||||
|
.fontFamily("Bona Nova")
|
||||||
|
.onHover(function (hovering) {
|
||||||
|
if(hovering) {
|
||||||
|
this.style.background = "var(--green)"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.style.background = "transparent"
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onClick((clicking) => {
|
||||||
|
console.log(this, "clicked")
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
.x(55, vw).y(4, vh)
|
||||||
|
.position("absolute")
|
||||||
|
.transform("translateX(-50%)")
|
||||||
|
})
|
||||||
|
.width(100, "%")
|
||||||
|
.height(100, "%")
|
||||||
|
}
|
||||||
|
|
||||||
|
SidebarName(name) {
|
||||||
|
let firstLetter = name[0]
|
||||||
|
|
||||||
|
HStack(() => {
|
||||||
|
div(firstLetter)
|
||||||
|
.display("flex")
|
||||||
|
.justifyContent("center")
|
||||||
|
.alignItems("center")
|
||||||
|
.width(1.5, em)
|
||||||
|
.height(1.5, em)
|
||||||
|
.border("1px solid var(--periwinkle)")
|
||||||
|
.borderRadius(100, "%")
|
||||||
|
p(name)
|
||||||
|
.marginLeft(1, em)
|
||||||
|
})
|
||||||
|
.alignItems("center")
|
||||||
|
.padding(5, px)
|
||||||
|
.borderRadius(0.5, em)
|
||||||
|
.cursor("default")
|
||||||
|
.onHover(function (hovering) {
|
||||||
|
if(hovering) {
|
||||||
|
this.style.background = "var(--green)"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.style.background = "transparent"
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
// Optional additional logic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register(Messages)
|
||||||
@@ -29,12 +29,17 @@ class Jobs extends Shadow {
|
|||||||
jobs = [
|
jobs = [
|
||||||
{
|
{
|
||||||
title: "Austin Chapter Lead",
|
title: "Austin Chapter Lead",
|
||||||
salary: "1% of Local Tax Revenue",
|
salary: "1% of Local Revenue",
|
||||||
location: "Austin"
|
company: "Hyperia",
|
||||||
|
city: "Austin",
|
||||||
|
state: "TX"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "San Marcos Chapter Lead",
|
title: "San Marcos Chapter Lead",
|
||||||
salary: "1% of Local Tax Revenue"
|
salary: "1% of Local Revenue",
|
||||||
|
company: "Hyperia",
|
||||||
|
city: "San Marcos",
|
||||||
|
state: "TX"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,19 @@ class JobsGrid extends Shadow {
|
|||||||
this.jobs = jobs
|
this.jobs = jobs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boldUntilFirstSpace(text) {
|
||||||
|
const index = text.indexOf(' ');
|
||||||
|
if (index === -1) {
|
||||||
|
// No spaces — bold the whole thing
|
||||||
|
return `<b>${text}</b>`;
|
||||||
|
}
|
||||||
|
return `<b>${text.slice(0, index)}</b>${text.slice(index)}`;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
VStack(() => {
|
VStack(() => {
|
||||||
h3("Results")
|
h3("Results")
|
||||||
.marginTop(0)
|
.marginTop(0.1, em)
|
||||||
.marginBottom(1, em)
|
.marginBottom(1, em)
|
||||||
.marginLeft(0.4, em)
|
.marginLeft(0.4, em)
|
||||||
.color("var(--periwinkle)")
|
.color("var(--periwinkle)")
|
||||||
@@ -17,14 +26,23 @@ class JobsGrid extends Shadow {
|
|||||||
if (this.jobs.length > 0) {
|
if (this.jobs.length > 0) {
|
||||||
ZStack(() => {
|
ZStack(() => {
|
||||||
for (let i = 0; i < this.jobs.length; i++) {
|
for (let i = 0; i < this.jobs.length; i++) {
|
||||||
|
VStack(() => {
|
||||||
p(this.jobs[i].title)
|
p(this.jobs[i].title)
|
||||||
.border("1px solid var(--periwinkle)")
|
.fontSize(1.2, em)
|
||||||
|
.fontWeight("bold")
|
||||||
|
.marginBottom(0.5, em)
|
||||||
|
p(this.jobs[i].company)
|
||||||
|
p(this.jobs[i].city + ", " + this.jobs[i].state)
|
||||||
|
.marginBottom(0.5, em)
|
||||||
|
p(this.boldUntilFirstSpace(this.jobs[i].salary))
|
||||||
|
})
|
||||||
.padding(1, em)
|
.padding(1, em)
|
||||||
|
.border("1px solid var(--periwinkle)")
|
||||||
.borderRadius(5, "px")
|
.borderRadius(5, "px")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.display("grid")
|
.display("grid")
|
||||||
.gridTemplateColumns("repeat(auto-fill, minmax(200px, 1fr))")
|
.gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))")
|
||||||
.gap(1, em)
|
.gap(1, em)
|
||||||
} else {
|
} else {
|
||||||
p("No Jobs!")
|
p("No Jobs!")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ class JobsSidebar extends Shadow {
|
|||||||
render() {
|
render() {
|
||||||
VStack(() => {
|
VStack(() => {
|
||||||
h3("Location")
|
h3("Location")
|
||||||
|
.color("var(--periwinkle)")
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import "./MarketSidebar.js"
|
||||||
|
import "./MarketGrid.js"
|
||||||
|
|
||||||
css(`
|
css(`
|
||||||
market- {
|
market- {
|
||||||
font-family: 'Bona';
|
font-family: 'Bona';
|
||||||
@@ -24,73 +27,27 @@ css(`
|
|||||||
|
|
||||||
class Market extends Shadow {
|
class Market extends Shadow {
|
||||||
|
|
||||||
|
listings = [
|
||||||
|
{
|
||||||
|
title: "Shield Lapel Pin",
|
||||||
|
stars: "5",
|
||||||
|
reviews: 1,
|
||||||
|
price: "$12",
|
||||||
|
company: "Hyperia",
|
||||||
|
type: "new",
|
||||||
|
image: "/db/images/1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
ZStack(() => {
|
ZStack(() => {
|
||||||
HStack(() => {
|
HStack(() => {
|
||||||
VStack(() => {
|
MarketSidebar()
|
||||||
|
|
||||||
HStack(() => {
|
MarketGrid(this.listings)
|
||||||
input()
|
|
||||||
.attr({
|
|
||||||
"type": "checkbox",
|
|
||||||
"id": "hyperia-check"
|
|
||||||
})
|
|
||||||
label("Hyperia-Made")
|
|
||||||
.attr({
|
|
||||||
"for": "hyperia-check"
|
|
||||||
})
|
|
||||||
.marginLeft(0.5, em)
|
|
||||||
})
|
|
||||||
|
|
||||||
HStack(() => {
|
|
||||||
input()
|
|
||||||
.attr({
|
|
||||||
"type": "checkbox",
|
|
||||||
"id": "america-check"
|
|
||||||
})
|
|
||||||
label("America-Made")
|
|
||||||
.attr({
|
|
||||||
"for": "america-check"
|
|
||||||
})
|
|
||||||
.marginLeft(0.5, em)
|
|
||||||
})
|
|
||||||
|
|
||||||
HStack(() => {
|
|
||||||
input()
|
|
||||||
.attr({
|
|
||||||
"type": "checkbox",
|
|
||||||
"id": "new-check"
|
|
||||||
})
|
|
||||||
label("New")
|
|
||||||
.attr({
|
|
||||||
"for": "new-check"
|
|
||||||
})
|
|
||||||
.marginLeft(0.5, em)
|
|
||||||
})
|
|
||||||
|
|
||||||
HStack(() => {
|
|
||||||
input()
|
|
||||||
.attr({
|
|
||||||
"type": "checkbox",
|
|
||||||
"id": "used-check"
|
|
||||||
})
|
|
||||||
label("Used")
|
|
||||||
.attr({
|
|
||||||
"for": "used-check"
|
|
||||||
})
|
|
||||||
.marginLeft(0.5, em)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.paddingLeft(3, em)
|
|
||||||
.gap(1, em)
|
|
||||||
})
|
})
|
||||||
.width(100, "%")
|
.width(100, "%")
|
||||||
.x(0).y(25, vh)
|
.x(0).y(13, vh)
|
||||||
|
|
||||||
p("0 Items")
|
|
||||||
.position("absolute")
|
|
||||||
.x(50, vw).y(50, vh)
|
|
||||||
.transform("translate(-50%, -50%)")
|
|
||||||
|
|
||||||
HStack(() => {
|
HStack(() => {
|
||||||
input("Search for products...", "45vw")
|
input("Search for products...", "45vw")
|
||||||
100
ui/site/apps/Market/MarketGrid.js
Normal file
100
ui/site/apps/Market/MarketGrid.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
class MarketGrid extends Shadow {
|
||||||
|
listings;
|
||||||
|
|
||||||
|
constructor(listings) {
|
||||||
|
super()
|
||||||
|
this.listings = listings
|
||||||
|
}
|
||||||
|
|
||||||
|
boldUntilFirstSpace(text) {
|
||||||
|
if(!text) return
|
||||||
|
const index = text.indexOf(' ');
|
||||||
|
if (index === -1) {
|
||||||
|
// No spaces — bold the whole thing
|
||||||
|
return `<b>${text}</b>`;
|
||||||
|
}
|
||||||
|
return `<b>${text.slice(0, index)}</b>${text.slice(index)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
VStack(() => {
|
||||||
|
h3("Results")
|
||||||
|
.marginTop(0.1, em)
|
||||||
|
.marginBottom(1, em)
|
||||||
|
.marginLeft(0.4, em)
|
||||||
|
.color("var(--periwinkle)")
|
||||||
|
|
||||||
|
if (this.listings.length > 0) {
|
||||||
|
ZStack(() => {
|
||||||
|
for (let i = 0; i < this.listings.length; i++) {
|
||||||
|
const rating = this.listings[i].stars
|
||||||
|
const percent = (rating / 5)
|
||||||
|
|
||||||
|
VStack(() => {
|
||||||
|
img(this.listings[i].image)
|
||||||
|
.marginBottom(0.5, em)
|
||||||
|
|
||||||
|
p(this.listings[i].company)
|
||||||
|
.marginBottom(0.5, em)
|
||||||
|
|
||||||
|
p(this.listings[i].title)
|
||||||
|
.fontSize(1.2, em)
|
||||||
|
.fontWeight("bold")
|
||||||
|
.marginBottom(0.5, em)
|
||||||
|
|
||||||
|
HStack(() => {
|
||||||
|
p(this.listings[i].stars)
|
||||||
|
.marginRight(0.2, em)
|
||||||
|
|
||||||
|
ZStack(() => {
|
||||||
|
div("★★★★★") // Empty stars (background)
|
||||||
|
.color("#ccc")
|
||||||
|
|
||||||
|
div("★★★★★") // Filled stars (foreground, clipped by width)
|
||||||
|
.color("#ffa500")
|
||||||
|
.position("absolute")
|
||||||
|
.top(0)
|
||||||
|
.left(0)
|
||||||
|
.whiteSpace("nowrap")
|
||||||
|
.overflow("hidden")
|
||||||
|
.width(percent * 5, em)
|
||||||
|
})
|
||||||
|
.display("inline-block")
|
||||||
|
.position("relative")
|
||||||
|
.fontSize(1.2, em)
|
||||||
|
.lineHeight(1)
|
||||||
|
|
||||||
|
p(this.listings[i].reviews)
|
||||||
|
.marginLeft(0.2, em)
|
||||||
|
})
|
||||||
|
.marginBottom(0.5, em)
|
||||||
|
|
||||||
|
p(this.listings[i].price)
|
||||||
|
.fontSize(1.75, em)
|
||||||
|
.marginBottom(0.5, em)
|
||||||
|
|
||||||
|
button("Buy Now")
|
||||||
|
|
||||||
|
})
|
||||||
|
.padding(1, em)
|
||||||
|
.border("1px solid var(--periwinkle)")
|
||||||
|
.borderRadius(5, "px")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.display("grid")
|
||||||
|
.gridTemplateColumns("repeat(auto-fill, minmax(250px, 1fr))")
|
||||||
|
.gap(1, em)
|
||||||
|
} else {
|
||||||
|
p("No Listings!")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.height(100, vh)
|
||||||
|
.paddingLeft(2, em)
|
||||||
|
.paddingRight(2, em)
|
||||||
|
.paddingTop(2, em)
|
||||||
|
.gap(0, em)
|
||||||
|
.width(100, "%")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register(MarketGrid)
|
||||||
65
ui/site/apps/Market/MarketSidebar.js
Normal file
65
ui/site/apps/Market/MarketSidebar.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
class MarketSidebar extends Shadow {
|
||||||
|
render() {
|
||||||
|
VStack(() => {
|
||||||
|
HStack(() => {
|
||||||
|
input()
|
||||||
|
.attr({
|
||||||
|
"type": "checkbox",
|
||||||
|
"id": "hyperia-check"
|
||||||
|
})
|
||||||
|
label("Hyperia-Made")
|
||||||
|
.attr({
|
||||||
|
"for": "hyperia-check"
|
||||||
|
})
|
||||||
|
.marginLeft(0.5, em)
|
||||||
|
})
|
||||||
|
|
||||||
|
HStack(() => {
|
||||||
|
input()
|
||||||
|
.attr({
|
||||||
|
"type": "checkbox",
|
||||||
|
"id": "america-check"
|
||||||
|
})
|
||||||
|
label("America-Made")
|
||||||
|
.attr({
|
||||||
|
"for": "america-check"
|
||||||
|
})
|
||||||
|
.marginLeft(0.5, em)
|
||||||
|
})
|
||||||
|
|
||||||
|
HStack(() => {
|
||||||
|
input()
|
||||||
|
.attr({
|
||||||
|
"type": "checkbox",
|
||||||
|
"id": "new-check"
|
||||||
|
})
|
||||||
|
label("New")
|
||||||
|
.attr({
|
||||||
|
"for": "new-check"
|
||||||
|
})
|
||||||
|
.marginLeft(0.5, em)
|
||||||
|
})
|
||||||
|
|
||||||
|
HStack(() => {
|
||||||
|
input()
|
||||||
|
.attr({
|
||||||
|
"type": "checkbox",
|
||||||
|
"id": "used-check"
|
||||||
|
})
|
||||||
|
label("Used")
|
||||||
|
.attr({
|
||||||
|
"for": "used-check"
|
||||||
|
})
|
||||||
|
.marginLeft(0.5, em)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.paddingTop(12, vh)
|
||||||
|
.paddingLeft(3, em)
|
||||||
|
.paddingRight(3, em)
|
||||||
|
.gap(1, em)
|
||||||
|
.minWidth(10, vw)
|
||||||
|
.userSelect('none')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
register(MarketSidebar)
|
||||||
@@ -111,7 +111,7 @@ class Messages extends Shadow {
|
|||||||
})
|
})
|
||||||
|
|
||||||
button("+ New Message")
|
button("+ New Message")
|
||||||
.width(15, em)
|
.width(13, em)
|
||||||
.marginLeft(1, em)
|
.marginLeft(1, em)
|
||||||
.borderRadius(10, px)
|
.borderRadius(10, px)
|
||||||
.background("transparent")
|
.background("transparent")
|
||||||
|
|||||||
@@ -64,12 +64,44 @@ class AppMenu extends Shadow {
|
|||||||
.gap(1.5, em)
|
.gap(1.5, em)
|
||||||
.paddingRight(2, em)
|
.paddingRight(2, em)
|
||||||
|
|
||||||
img("_/images/divider.svg", "40vw")
|
img("/_/images/divider.svg", "40vw")
|
||||||
.attr({
|
.attr({
|
||||||
"id": "divider",
|
"id": "divider",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.gap(0.5, em)
|
.gap(0.5, em)
|
||||||
|
.onNavigate(() => {
|
||||||
|
if(window.location.pathname === "/") {
|
||||||
|
this.styleMaximized()
|
||||||
|
$("app-window").close()
|
||||||
|
} else {
|
||||||
|
this.styleMinimized()
|
||||||
|
$("app-window").open(this.selected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.onAppear(() => {
|
||||||
|
Array.from(this.querySelectorAll("p")).forEach((el) => {
|
||||||
|
el.addEventListener("mousedown", (e) => {
|
||||||
|
el.classList.add("touched")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
window.addEventListener("mouseup", (e) => {
|
||||||
|
let target = e.target
|
||||||
|
if(!target.matches("app-menu p")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
target.classList.remove("touched")
|
||||||
|
|
||||||
|
if(target.classList.contains("selected")) {
|
||||||
|
this.selected = ""
|
||||||
|
window.navigateTo("/")
|
||||||
|
} else {
|
||||||
|
this.selected = target.innerText
|
||||||
|
window.navigateTo("/app/" + target.innerText.toLowerCase())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
if(this.selected) {
|
if(this.selected) {
|
||||||
this.styleMinimized()
|
this.styleMinimized()
|
||||||
@@ -95,35 +127,6 @@ class AppMenu extends Shadow {
|
|||||||
this.classList.add("minimized")
|
this.classList.add("minimized")
|
||||||
$("#divider").style.display = "none"
|
$("#divider").style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
Array.from(this.querySelectorAll("p")).forEach((el) => {
|
|
||||||
el.addEventListener("mousedown", (e) => {
|
|
||||||
el.classList.add("touched")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
window.addEventListener("mouseup", (e) => {
|
|
||||||
let target = e.target
|
|
||||||
if(!target.matches("app-menu p")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
target.classList.remove("touched")
|
|
||||||
|
|
||||||
if(target.classList.contains("selected")) {
|
|
||||||
this.selected = ""
|
|
||||||
this.styleMaximized(target)
|
|
||||||
window.navigateTo("/")
|
|
||||||
$("app-window").close()
|
|
||||||
} else {
|
|
||||||
this.selected = target.innerText
|
|
||||||
this.styleMinimized(target)
|
|
||||||
window.navigateTo("/app/" + target.innerText.toLowerCase())
|
|
||||||
$("app-window").open(target.innerText)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
, "app-menu")
|
, "app-menu")
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import "../apps/Jobs/Jobs.js"
|
import "../apps/Jobs/Jobs.js"
|
||||||
import "../apps/Messages.js"
|
import "../apps/Messages.js"
|
||||||
import "../apps/Market.js"
|
import "../apps/Market/Market.js"
|
||||||
|
|
||||||
class AppWindow extends Shadow {
|
class AppWindow extends Shadow {
|
||||||
app;
|
app;
|
||||||
|
|||||||
@@ -8,21 +8,20 @@ class Home extends Shadow {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
ZStack(() => {
|
ZStack(() => {
|
||||||
img("_/icons/logo.svg", "2.5em")
|
img("/_/icons/logo.svg", "2.5em")
|
||||||
.position("fixed")
|
.position("fixed")
|
||||||
.left("3em")
|
.left("3em")
|
||||||
.top("3vh")
|
.top("3vh")
|
||||||
.zIndex(3)
|
.zIndex(3)
|
||||||
// .onClick(() => {
|
.onClick(() => {
|
||||||
// window.navigateTo("/")
|
window.navigateTo("/")
|
||||||
// this.rerender()
|
})
|
||||||
// })
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.width(100, vw)
|
.width(100, vw)
|
||||||
.height(100, vh)
|
.height(100, vh)
|
||||||
.margin("0px")
|
.margin("0px")
|
||||||
.backgroundImage("url('_/images/the_return.webp')")
|
.backgroundImage("url('/_/images/the_return.webp')")
|
||||||
.backgroundSize("cover")
|
.backgroundSize("cover")
|
||||||
.backgroundPosition("48% 65%")
|
.backgroundPosition("48% 65%")
|
||||||
.backgroundRepeat("no-repeat")
|
.backgroundRepeat("no-repeat")
|
||||||
@@ -69,7 +68,6 @@ class Home extends Shadow {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.onNavigate(function () {
|
.onNavigate(function () {
|
||||||
console.log("navigate")
|
|
||||||
if(window.location.pathname === "/") {
|
if(window.location.pathname === "/") {
|
||||||
this.style.border = "1px solid var(--tan)"
|
this.style.border = "1px solid var(--tan)"
|
||||||
this.style.color = "var(--tan)"
|
this.style.color = "var(--tan)"
|
||||||
|
|||||||
Reference in New Issue
Block a user