From b49fc947aa96b20c88e5ded2cc1d8fcb996d6961 Mon Sep 17 00:00:00 2001 From: GregTCLTK Date: Sun, 24 Nov 2024 17:03:20 +0100 Subject: [PATCH] signin page wip --- assets/imports.ts | 2 + deno.jsonc | 4 +- pages/music-landing/main.ts | 4 +- pages/shared/footer.css | 199 ----------------- pages/shared/footer.ts | 1 - pages/shared/navigation.css | 68 ------ pages/shared/navigation.ts | 288 ------------------------- pages/user/actions.ts | 22 +- pages/user/signin.css | 16 +- pages/user/signin.ts | 412 +++++++++++++++++++----------------- pages/user/state.ts | 10 +- serve.ts | 5 +- 12 files changed, 252 insertions(+), 779 deletions(-) delete mode 100644 pages/shared/footer.css delete mode 100644 pages/shared/navigation.css diff --git a/assets/imports.ts b/assets/imports.ts index 24ca7ec8..4062475c 100644 --- a/assets/imports.ts +++ b/assets/imports.ts @@ -4,7 +4,9 @@ import googleLogo from './img/googleLogo.svg'; import discordLogo from './img/discordLogo.svg'; // @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" import dots from './img/dots.svg'; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" import splash from "./img/splash.png"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" import templateArtwork from "./img/template-artwork.png"; export { diff --git a/deno.jsonc b/deno.jsonc index e2a87b65..6b1f8a78 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -10,8 +10,8 @@ "@std/cache": "jsr:@std/cache@^0.1.3", "shared/": "./pages/shared/", "zod/": "https://deno.land/x/zod@v3.23.8/", - "webgen/": "../WebGen/" - // "webgen/": "https://raw.githubusercontent.com/lucsoft/WebGen/3616d3b/" + // "webgen/": "../WebGen/" + "webgen/": "https://raw.githubusercontent.com/lucsoft/WebGen/8e634b6/" }, "lock": false, "compilerOptions": { diff --git a/pages/music-landing/main.ts b/pages/music-landing/main.ts index 9f0340e0..cbd1d0e5 100644 --- a/pages/music-landing/main.ts +++ b/pages/music-landing/main.ts @@ -23,9 +23,11 @@ import tidal from "./assets/tidal.svg"; import tiktok from "./assets/tiktok.svg"; // @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" import youtube from "./assets/youtube.svg"; - +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" import backgroundImage from "./assets/background.png"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" import criticz from "./assets/criticz.jpg"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" import redz from "./assets/redz.jpg"; await RegisterAuthRefresh(); diff --git a/pages/shared/footer.css b/pages/shared/footer.css deleted file mode 100644 index 6818bf18..00000000 --- a/pages/shared/footer.css +++ /dev/null @@ -1,199 +0,0 @@ -.footer-space { - overflow: hidden; -} - -.footer { - --spacing: 4rem; - position: relative; - display: grid; - grid-auto-flow: row; - margin: calc(var(--spacing) * 2) auto var(--spacing); - max-width: 1235px; - width: calc(100vw - 5rem); - z-index: 1; - justify-items: start; - box-sizing: border-box; - gap: var(--spacing); -} - -.footer .area-fg { - display: grid; - grid-auto-flow: row; - justify-items: start; - gap: var(--spacing); -} - -.footer .area-bg { - width: 100%; - box-sizing: border-box; -} - -.round-button { - border-radius: 12px; -} - -/* Header */ - -.large-button { - padding: 0 3rem; - font-size: 1.2rem; - height: 50px; - border-radius: 18px; -} - -.footer .text-section { - display: grid; - grid-auto-flow: row; - white-space: break-spaces; - z-index: 1; - gap: calc(var(--spacing) / 2); -} - -.footer .title { - font-size: 52px; - font-weight: 900; -} - -.footer .subtitle { - font-size: 22px; - opacity: 1; -} - -.footer .splash-image { - width: 572px; - right: 0; - rotate: -12deg; - position: absolute; - z-index: -1; -} - -/* Middle Section */ - -.footer .grouped-links { - display: flex; - flex-wrap: wrap; - max-width: 50rem; - margin-bottom: 150px; - gap: 1rem 0; -} - -.footer .grouped-links>* { - z-index: 2; - align-content: flex-start; - flex-grow: 1; - gap: 8px; -} - -.footer .grouped-links .link { - padding: 0; - margin: 0; - font-weight: 400; -} - -.footer .grouped-links .link:hover { - text-decoration: underline; - background: transparent; -} - -.footer .grouped-links .title { - font-size: 18px; - font-weight: bold; - margin-bottom: 1rem; -} - -/* Links */ - -.footer .icon-bar { - display: grid; - grid-template-columns: auto max-content max-content; - align-items: center; - gap: 1rem; - width: 100%; -} - -.footer .icon-bar .icons { - display: grid; - grid-auto-flow: column; - grid-auto-columns: max-content; - gap: 1rem; -} - -.footer .icon-bar .icons .icon { - padding: .2rem; - border-radius: 2rem; - background: hsla(var(--background-color-h), var(--background-color-s), var(--background-color-l), 0%); - color: hsl(var(--background-color-h), var(--background-color-s), var(--background-color-l)); -} - -.footer .icon-bar .icons .icon:hover { - box-shadow: none; - background: hsla(var(--background-color-h), var(--background-color-s), var(--background-color-l), 20%); -} - -@media (max-width: 1130px) { - .footer .splash-image { - width: 453.2px; - right: -5rem; - top: -4rem; - } -} - -@media (max-width: 770px) { - .footer .icon-bar { - grid-template-columns: max-content max-content; - justify-content: center; - } - - .footer .icon-bar .icons { - grid-column: 1 / -1; - } - - .footer .icon-bar :nth-child(2) { - justify-self: end; - } - - .footer .icon-bar :nth-child(3) { - justify-self: start; - } - - .footer .grouped-links { - margin-bottom: 50px; - } -} - -@media (max-width: 550px) { - .footer { - width: auto; - margin: 0; - overflow: hidden; - } - - .footer .grouped-links .column { - flex-basis: 50%; - } - - .footer .icon-bar .icons { - gap: 0; - } - - - .footer .splash-image { - top: unset; - right: 0; - bottom: -3rem; - } - - .footer .area-fg { - padding: 2rem 2rem 0; - } - - .footer .area-bg { - padding: 0 2rem 2rem; - background: linear-gradient(180deg, rgba(255, 255, 0, 0.00) 0%, rgba(0, 0, 0, 0.02) 4.70%, rgba(0, 0, 0, 0.04) 8.90%, rgba(0, 0, 0, 0.07) 12.80%, rgba(0, 0, 0, 0.10) 16.56%, rgba(0, 0, 0, 0.14) 20.37%, rgba(0, 0, 0, 0.18) 24.40%, rgba(0, 0, 0, 0.23) 28.83%, rgba(0, 0, 0, 0.29) 33.84%, rgba(0, 0, 0, 0.35) 39.60%, rgba(0, 0, 0, 0.43) 46.30%, rgba(0, 0, 0, 0.52) 54.10%, rgba(0, 0, 0, 0.62) 63.20%, rgba(0, 0, 0, 0.73) 73.76%, rgba(0, 0, 0, 0.86) 85.97%, #000 100%); - } - - [data-theme=light] .footer .area-bg { - padding: 0 2rem 2rem; - background: linear-gradient(180deg, rgba(255, 255, 255, 0.00) 0%, rgba(255, 255, 255, 0.02) 4.70%, rgba(255, 255, 255, 0.04) 8.90%, rgba(255, 255, 255, 0.07) 12.80%, rgba(255, 255, 255, 0.10) 16.56%, rgba(255, 255, 255, 0.14) 20.37%, rgba(255, 255, 255, 0.18) 24.40%, rgba(255, 255, 255, 0.23) 28.83%, rgba(255, 255, 255, 0.29) 33.84%, rgba(255, 255, 255, 0.35) 39.60%, rgba(255, 255, 255, 0.43) 46.30%, rgba(255, 255, 255, 0.52) 54.10%, rgba(255, 255, 255, 0.62) 63.20%, rgba(255, 255, 255, 0.73) 73.76%, rgba(255, 255, 255, 0.86) 85.97%, #FFF 100%); - } -} \ No newline at end of file diff --git a/pages/shared/footer.ts b/pages/shared/footer.ts index bafb3968..0c661af2 100644 --- a/pages/shared/footer.ts +++ b/pages/shared/footer.ts @@ -1,7 +1,6 @@ import { Box, Component, Grid, Label } from "webgen/core/mod.ts"; import { asRef, BootstrapIcon, Content, css, Image, mediaQueryRef, PrimaryButton, SecondaryButton, TextButton } from "webgen/mod.ts"; import { splash } from "../../assets/imports.ts"; -import "./footer.css"; export function Footer() { return Content( diff --git a/pages/shared/navigation.css b/pages/shared/navigation.css deleted file mode 100644 index ae889a3a..00000000 --- a/pages/shared/navigation.css +++ /dev/null @@ -1,68 +0,0 @@ -.remove-from-layout { - display: contents; -} - -.sticky-footer { - position: sticky; - height: 37px; - bottom: var(--gap); - padding: 0 var(--gap); - left: 0; - right: 0; - box-sizing: border-box; - z-index: 5; -} - -.action-list-bar { - grid-auto-flow: column; - justify-content: center; -} - -.action-list-bar:has(a +a) { - justify-content: space-between; -} - -.history-list { - grid-auto-flow: column; - grid-auto-columns: auto; - justify-content: start; - white-space: nowrap; - font-size: 2.26063rem; -} - -.history-entry { - opacity: 60%; - cursor: pointer; - display: grid; - grid-auto-flow: column; - align-items: center; -} - -.history-entry.mobile { - justify-content: start; - gap: 0.5rem; - margin-bottom: 0.5rem; -} - -.history-list .history-entry.mobile { - font-size: 1.8rem; -} - -.history-entry .label { - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.history-entry:hover { - opacity: 70%; -} - -.history-entry:not(.mobile) .wicon { - margin: 0 .5rem; -} - -.history-entry.mobile .wicon { - font-size: 1.5rem; - margin-left: -0.4rem; -} \ No newline at end of file diff --git a/pages/shared/navigation.ts b/pages/shared/navigation.ts index 9b3e7251..c4c47a2d 100644 --- a/pages/shared/navigation.ts +++ b/pages/shared/navigation.ts @@ -49,291 +49,3 @@ export function Navigation(actions?: Component) { PageRouter, ); } - -// import { assert } from "@std/assert"; -// import { alwaysRef, asRef, Box, Component, Empty, Entry, Grid, isMobile, isRef, Label, Refable, Reference } from "webgen/mod.ts"; -// import "./navigation.css"; - -// export interface ClickHandler { -// (path: string, node: MenuNode): void | Promise; -// } - -// export type RenderItem = Component | MenuNode; - -// export interface MenuNode { -// id: string; -// hidden?: Refable; -// title: Refable; -// subtitle?: Refable; -// children?: Refable; -// replacement?: Refable; -// suffix?: Refable; -// clickHandler?: ClickHandler; -// firstRenderHandler?: ClickHandler; -// } - -// export interface CategoryNode extends MenuNode { -// displayTextInHeader?: "same" | "useRoot"; -// } - -// export type RootNode = Omit & { -// categories?: CategoryNode[]; -// actions?: Refable; -// }; - -// function traverseToMenuNode(rootNode: RootNode, path: string): MenuNode | null { -// const pathSegments = path.split("/").filter(Boolean); -// let currentNode: MenuNode | undefined = rootNode as MenuNode; - -// for (const segment of pathSegments) { -// if (currentNode?.children) { -// const childNode = alwaysRef(currentNode.children).getValue().find( -// (child) => !(child instanceof Component) && child.id === segment, -// ) as MenuNode | undefined; - -// if (childNode) { -// currentNode = childNode; -// continue; -// } -// } - -// // Node not found for the given segment -// return null; -// } - -// return currentNode || null; -// } - -// function resolvePathToNodes(rootNode: RootNode, path: string): MenuNode[] | null { -// const nodes: MenuNode[] = []; -// const pathSegments = path.split("/").filter(Boolean); -// let currentNode: MenuNode | undefined = rootNode as MenuNode; - -// for (const segment of pathSegments) { -// if (currentNode?.children) { -// const childNode = alwaysRef(currentNode.children).getValue().find( -// (child) => !(child instanceof Component) && child.id === segment, -// ) as MenuNode | undefined; - -// if (childNode) { -// currentNode = childNode; -// nodes.push(currentNode); -// continue; -// } -// } -// assert(segment, `Missing path segment ${segment} for ${path}`); -// } - -// return nodes; -// } - -// function getMenuNodeByPrefix(rootNode: RootNode, rootId: string): MenuNode { -// // When no categories -// if (rootId == "-") return rootNode as MenuNode; - -// // Use category as root -// assert(rootNode.categories); -// const categoryNode = rootNode.categories.find((category) => category.id === rootId); -// assert(categoryNode, "category not found"); -// return categoryNode; -// } - -// class MenuImpl extends Component { -// rootNode: RootNode; -// path: Reference; -// displayed = asRef([]); -// #header: Reference<(data: this) => Component> = asRef<(data: this) => Component>(defaultHeader); -// #footer: Reference<(data: this) => Component> = asRef<(data: this) => Component>(defaultFooter); - -// constructor(rootNode: RootNode) { -// super(); -// this.rootNode = rootNode; -// this.path = asRef(rootNode.categories?.at(0) ? `${rootNode.categories.at(0)!.id}/` : "-/"); -// // Renderer -// this.wrapper.append( -// Vertical( -// this.#header.map((it) => it(this)).asRefComponent().removeFromLayout(), -// HeavyList(this.displayed, (item) => { -// if (item instanceof Component) { -// return item; -// } - -// if (asRef(item.hidden ?? false).getValue()) { -// return Empty(); -// } - -// const entry = Entry(item.replacement ? asRef(item.replacement).getValue() : item).addClass(isMobile.map((mobile) => mobile ? "small" : "desktop")); -// const click = this.#createClickHandler(item); -// if (item.suffix) { -// entry.addSuffix(asRef(item.suffix).getValue()); -// } -// if (click) { -// entry.onPromiseClick(async () => await click()); -// } -// return entry; -// }), -// this.#footer.map((it) => it(this)).asRefComponent().removeFromLayout(), -// ).setGap().draw(), -// ); - -// // Listener -// this.path.listen((val) => { -// const [rootId] = val.split("/"); -// const unprefixed = val.replace(rootId, ""); -// const root = getMenuNodeByPrefix(rootNode, rootId); -// assert(root); -// const item = traverseToMenuNode(root, unprefixed); - -// assert(item, "No Node found"); -// if (isRef(item.children)) { -// item.children.listen((items) => { -// if (val == this.path.getValue()) { -// this.displayed.setValue(items); -// } -// }); -// } else { -// this.displayed.setValue(item.children ?? []); -// } -// }); -// } - -// #createClickHandler(menu: MenuNode): undefined | (() => Promise | void) { -// if (menu.clickHandler) { -// return async () => { -// await menu.clickHandler?.(`${this.path.getValue() + menu.id}/`, menu); -// if (menu.children) { -// this.path.setValue(`${this.path.getValue() + menu.id}/`); -// } -// }; -// } -// if (menu.children) { -// return () => { -// this.path.setValue(`${this.path.getValue() + menu.id}/`); -// }; -// } -// return undefined; -// } - -// setHeader(header: (data: this) => Component) { -// this.#header.setValue(header); -// return this; -// } - -// setFooter(footer: (data: this) => Component) { -// this.#footer.setValue(footer); -// } -// } - -// /** -// * A Extendable Declarative Refable Navigation Component. -// * @param rootNode -// * @returns -// */ -// export const Navigation = (rootNode: RootNode) => new MenuImpl(rootNode); - -// function defaultHeader(menu: MenuImpl) { -// return isMobile.map((mobile) => { -// const list = Vertical( -// createBreadcrumb(menu), -// createTagList(menu), -// ).setGap(); -// if (!mobile) { -// return Grid( -// list, -// createActionList(menu), -// ).setRawColumns("auto max-content").setGap().setAlignItems("center"); -// } -// return list; -// }).asRefComponent(); -// } - -// function defaultFooter(menu: MenuImpl) { -// return isMobile.map((mobile) => mobile && menu.rootNode.actions ? Box(createActionList(menu)).addClass(asRef(menu.rootNode.actions).map((it) => it.length == 0 ? "remove-from-layout" : "normal"), "sticky-footer") : Empty()).asRefComponent().removeFromLayout(); -// } - -// export function createActionList(menu: MenuImpl) { -// return asRef(menu.rootNode.actions ?? []).map((it) => Grid(...it).addClass("action-list-bar")).asRefComponent().removeFromLayout(); -// } - -// export function createTagList(menu: MenuImpl) { -// if (!menu.rootNode.categories) return Empty(); -// const index = asRef(0); -// index.listen((val, oldVal) => { -// if (oldVal != undefined) { -// const path = menu.rootNode.categories![val]; -// if (path) { -// menu.path.setValue(`${path.id}/`); -// } -// } -// }); - -// menu.path.listen((path) => { -// index.setValue(menu.rootNode.categories!.findIndex((it) => it.id == path.split("/").at(0))); -// }); - -// return menu.path.map((path) => { -// const [rootId] = path.split("/"); -// const unprefixed = path.replace(rootId, ""); -// const visible = unprefixed == "/"; -// return visible && menu.rootNode.categories ? Taglist(menu.rootNode.categories.map((it) => it.title), index) : Empty(); -// }).asRefComponent(); -// } - -// export function createBreadcrumb(menu: MenuImpl) { -// return isMobile.map((mobile) => { -// const history = menu.path.map((path) => { -// const [rootId] = path.split("/"); -// const unprefixed = path.replace(rootId, ""); - -// const root = getMenuNodeByPrefix(menu.rootNode, rootId); -// const items = resolvePathToNodes(root, unprefixed) ?? []; -// return [root, ...items]; -// }); -// function moveToPath(index: number) { -// menu.path.setValue(`${history.getValue().filter((_, i) => index >= i).map((it) => it.id ?? "-").join("/")}/`); -// } - -// if (mobile) { -// return history.map((it) => { -// const last = it.at(-2); -// if (!last) { -// return Label(parseTitle(menu.rootNode, it.at(-1)!, it.length - 1)) -// .addClass("label"); -// } -// return Box( -// // TODO: Make this a bit smaller -// Grid( -// MIcon("arrow_back_ios_new"), -// Label(parseTitle(menu.rootNode, last, it.indexOf(last) + 1)).addClass("label"), -// ) -// .addClass("history-entry", "mobile") -// .onClick(() => moveToPath(it.indexOf(last))), -// Label(parseTitle(menu.rootNode, it.at(-1)!, it.length - 1)) -// .addClass("label"), -// ); -// }).asRefComponent().addClass("history-list").removeFromLayout(); -// } -// return history.map((it) => -// Grid( -// ...it.map((entry, index) => -// Box( -// Label(entry.title) -// .setFontWeight("bold") -// .addClass("label"), -// MIcon("arrow_forward_ios"), -// ) -// .addClass("history-entry") -// .onClick(() => moveToPath(index)) -// ).filter((_, i) => i != it.length - 1), -// Label(parseTitle(menu.rootNode, it.at(-1)!, it.length - 1)) -// .addClass("label") -// .setFontWeight("bold"), -// ).addClass("history-list") -// ).asRefComponent().removeFromLayout(); -// }).asRefComponent().removeFromLayout(); -// } - -// function parseTitle(rootNode: RootNode, node: MenuNode, index: number) { -// if (index === 0 && ( node).displayTextInHeader != "same") return rootNode.title; -// return node.title; -// } diff --git a/pages/user/actions.ts b/pages/user/actions.ts index 26caaa37..23bf8026 100644 --- a/pages/user/actions.ts +++ b/pages/user/actions.ts @@ -8,8 +8,8 @@ export async function loginUser() { try { assert(state.email && state.password, "Missing Email or Password"); const rsp = await API.auth.email.post({ - email: state.email, - password: state.password, + email: state.email.value, + password: state.password.value, }); if (rsp.status == "rejected") { throw rsp.reason; @@ -18,16 +18,16 @@ export async function loginUser() { await logIn(rsp.value); gotoGoal(); } catch (error) { - state.error = displayError(error); + state.error.setValue(displayError(error)); } } export async function registerUser() { try { const { name, email, password } = { - email: state.email ?? "", - password: state.password ?? "", - name: state.name ?? "", + email: state.email.value ?? "", + password: state.password.value ?? "", + name: state.name.value ?? "", }; assert(name && email && password, "Missing fields"); const rsp = await API.auth.register.post({ @@ -42,7 +42,7 @@ export async function registerUser() { await logIn(rsp.value); gotoGoal(); } catch (error) { - state.error = displayError(error); + state.error.setValue(displayError(error)); } } @@ -63,7 +63,7 @@ export async function handleStateChange() { if (params.type && ["google", "discord", "microsoft"].includes(params.type) && params.code) { const rsp = await API.auth.oauth.post(params.type, params.code); if (rsp.status === "rejected") { - return state.error = displayError(rsp.reason); + return state.error.setValue(displayError(rsp.reason)); } await logIn(rsp.value); gotoGoal(); @@ -72,7 +72,7 @@ export async function handleStateChange() { if (params.type == "reset-password" && params.token) { const rsp = await API.auth.fromUserInteraction.get(params.token); if (rsp.status === "rejected") { - return state.error = displayError(rsp.reason); + return state.error.setValue(displayError(rsp.reason)); } await logIn(rsp.value); gotoGoal(); @@ -81,12 +81,12 @@ export async function handleStateChange() { if (params.type == "verify-email" && params.token) { const rsp = await API.user.mail.validate.post(params.token); if (rsp.status === "rejected") { - return state.error = displayError(rsp.reason); + return state.error.setValue(displayError(rsp.reason)); } await forceRefreshToken(); await delay(1000); gotoGoal(); return; } - state.type = "login"; + state.type.setValue("login"); } diff --git a/pages/user/signin.css b/pages/user/signin.css index 3e98f13f..455d1595 100644 --- a/pages/user/signin.css +++ b/pages/user/signin.css @@ -26,21 +26,21 @@ background-size: cover; } -.auth-area { +/* .auth-area { min-height: 100vh; display: grid; place-items: center; -} +} */ -.line-header { +/* .line-header { line-height: 94px; -} +} */ -.prefix-logo { +/* .prefix-logo { position: absolute; left: .7rem; width: 1.2rem; -} +} */ .loader.loading { width: 100%; @@ -51,10 +51,10 @@ align-items: end; margin-top: -1rem; } - +/* .header { max-width: 21rem; -} +} */ @media (prefers-color-scheme: light) { body { diff --git a/pages/user/signin.ts b/pages/user/signin.ts index 53e15d9b..c8325c00 100644 --- a/pages/user/signin.ts +++ b/pages/user/signin.ts @@ -2,211 +2,239 @@ import { assert } from "@std/assert"; import { Footer } from "shared/footer.ts"; import { RegisterAuthRefresh } from "shared/helper.ts"; import { API } from "shared/mod.ts"; -import { appendBody, Box, Color, Component, Content, Empty, Grid, Image, isMobile, Label, TextInput, WebGenTheme } from "webgen/mod.ts"; +import { appendBody, Box, Color, Content, css, EmailInput, Empty, FullWidthSection, Grid, Image, isMobile, Label, mediaQueryRef, PasswordInput, PrimaryButton, Spinner, TextButton, TextInput, WebGenTheme, WriteSignal } from "webgen/mod.ts"; import "../../assets/css/main.css"; -import { discordLogo, googleLogo } from "../../assets/imports.ts"; +import { googleLogo } from "../../assets/imports.ts"; import { DynaNavigation } from "../../components/nav.ts"; -import { handleStateChange, loginUser, registerUser } from "./actions.ts"; -import "./signin.css"; +import { handleStateChange, loginUser } from "./actions.ts"; import { state } from "./state.ts"; +// @deno-types="https://raw.githubusercontent.com/lucsoft-DevTeam/lucsoft.de/master/custom.d.ts" +import backgroundImage from "../holding./resources/background.jpg"; + await RegisterAuthRefresh(); +document.adoptedStyleSheets.push(css` + body { + --box-shadow-cta: 0 .824px 1.752px #db572124, 0 2.085px 4.43px #db572133, 0 4.253px 9.038px #db57213d, 0 8.76px 18.616px #db57214d, 0 24px 51px #db572170; + --box-shadow-cta-hover: 0 1.237px .69px #db572145, 0 3.127px 5.113px #db572157, 0 6.38px 15.547px #db57215c, 0 13.141px 37.63px #db572163, 0 36px 100px #db572182;; + --background-cta: linear-gradient(139deg, #e39123 6.59%, #db5721 101.73%); + + --background-free-tier: linear-gradient(139deg, #e3912333 6.59%, #db572133 101.73%), #0a0a0a; + --badge-free-tier: linear-gradient(139deg,#d9881c73 6.59%,#c6451073 101.73%); + --background-paid-tier: linear-gradient(139deg, #d9881c 6.59%, #c64510 101.73%); + --badge-paid-tier: #00000040; + --bg-color: ${Color.reverseNeutral.mix(new Color("black"), 50)}; + background-color: var(--bg-color); + } +`); -export const Form = (ele: Component) => { - const form = createElement("form"); - form.append(ele.draw()); - form.addEventListener("submit", (e: Event) => { - e.preventDefault(); - if (!form.reportValidity()) return; - form.querySelector("#submit-button")?.click(); - }); - return Custom(form); -}; +const ErrorMessage = (message: WriteSignal) => message.value ? Label(message).setPadding("var(--wg-button-padding, 5px 10px)").setCssStyle("color", "red").setCssStyle("borderRadius", "var(--wg-checkbox-border-radius, var(--wg-radius-tiny))").setCssStyle("backgroundColor", "#2e0000") : Empty(); -const ErrorMessage = () => - state.$error.map((error) => error != undefined ? Label(error ?? "Please try again later.").addClass("error-message").setMargin("1rem 0 0") : Box()) - .asRefComponent() - .removeFromLayout(); +const isLightMode = mediaQueryRef("(prefers-color-scheme: light)"); -appendBody(WebGenTheme(Content(Grid( - DynaNavigation("Home"), - Empty().addClass("background-image"), - Box( - Grid( - isMobile.map((small) => - Label("Welcome back!") - .setMargin("5rem 0 .8rem") - .addClass(small ? "no-custom" : "line-header", "header") - .setFontWeight("extrabold") - .setTextSize(small ? "6xl" : "7xl") - ).asRefComponent().removeFromLayout(), - state.$type.map((type) => { - if (type == "reset-password-from-email") { - return Form(Grid( - TextInput("password", "New Password") - .ref(state.$password), - Button("Reset your Password") - .setId("submit-button") - .setJustifyContent("center") - .onPromiseClick(async () => { - try { - assert(API.getToken(), "Missing Token!"); - await API.user.setMe.post({ - password: state.password, - }); - state.type = "login"; - } catch (_) { - state.error = "Failed: Please try again later"; - } - }), - Label(API.getToken() ? "" : "Error: Link is invalid").addClass("error-message"), - ErrorMessage(), - )); - } - if (type == "request-reset-password") { - return Form(Grid( - TextInput("email", "Email") - .ref(state.$email) - .onChange(() => state.error = undefined) - .setAutofill("email") - .required() - .setMargin("0 0 .6rem"), - Button("Reset") - .onPromiseClick(async () => { - try { - assert(state.email, "Email is missing"); +appendBody( + WebGenTheme( + Content( + FullWidthSection( + DynaNavigation("Home"), + ), + FullWidthSection( + Empty() + .setAttribute("theme", isLightMode.map((x) => x ? "light" : "dark")) + .addStyle(css` + :host { + position: absolute; + display: block; + inset: -0.5rem; + --image: url('${backgroundImage}'); + background: + linear-gradient(180deg, rgba(0, 0, 0, 0.61) 0%, var(--bg-color) 77.08%, var(--bg-color) 100%), + var(--image) no-repeat center center; + background-size: cover; + filter: blur(4.5px); + z-index: -1; + } + :host([theme=light]) { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.61) 0%, #f3f5fa 77.08%, #f3f5fa 100%), + var(--image) no-repeat center center; + background-size: cover; + } + `), + ), + Grid( + Grid( + isMobile.map((small) => + Label("Welcome back!") + .setMaxWidth("21rem") + .setMargin("5rem 0 .8rem") + .setFontWeight("extrabold") + .setTextSize(small ? "6xl" : "7xl") + .setCssStyle("lineHeight", small ? "var(--wg-lineheight-${6xl})" : "94px") + ), + Box(state.type.map((type) => { + if (type == "reset-password-from-email") { + return Box(Grid( + TextInput(state.password, "New Password"), + PrimaryButton("Reset your Password") + .onPromiseClick(async () => { + try { + assert(API.getToken(), "Missing Token!"); + await API.user.setMe.post({ + password: state.password.value, + }); + state.type.setValue("login"); + } catch (_) { + state.error.setValue("Failed: Please try again later"); + } + }) + .setId("submit-button") + .setJustifyContent("center"), + Label(API.getToken() ? "" : "Error: Link is invalid").addClass("error-message"), + ErrorMessage(state.error), + )); + } + // if (type == "request-reset-password") { + // return Form(Grid( + // TextInput("email", "Email") + // .ref(state.$email) + // .onChange(() => state.error = undefined) + // .setAutofill("email") + // .required() + // .setMargin("0 0 .6rem"), + // Button("Reset") + // .onPromiseClick(async () => { + // try { + // assert(state.email, "Email is missing"); - await API.auth.forgotPassword.post(state.email); + // await API.auth.forgotPassword.post(state.email); - alert("Email sent! Please check your Inbox/Spam folder."); - } catch (_) { - state.error = _.message; - } - }) - .setJustifyContent("center"), - ErrorMessage(), - Horizontal( - Label("Already have an account?"), - Button("Sign in") - .setStyle(ButtonStyle.Inline) - .onClick(() => state.type = "login") - .setColor(Color.Colored) - .addClass("link"), - Spacer(), - ) - .setMargin("1rem 0 0"), - )); - } + // alert("Email sent! Please check your Inbox/Spam folder."); + // } catch (_) { + // state.error = _.message; + // } + // }) + // .setJustifyContent("center"), + // ErrorMessage(), + // Horizontal( + // Label("Already have an account?"), + // Button("Sign in") + // .setStyle(ButtonStyle.Inline) + // .onClick(() => state.type = "login") + // .setColor(Color.Colored) + // .addClass("link"), + // Spacer(), + // ) + // .setMargin("1rem 0 0"), + // )); + // } - if (type == "login") { - return Form(Grid( - LinkButton("Sign in with Google", API.auth.oauthRedirect("google")) - .setJustifyContent("center") - .addPrefix( - Image(googleLogo, "Google Logo") - .addClass("prefix-logo"), - ) - .setMargin("0 0 .6rem"), - LinkButton("Sign in with Discord", API.auth.oauthRedirect("discord")) - .setJustifyContent("center") - .addPrefix( - Image(discordLogo, "Discord Logo") - .addClass("prefix-logo"), - ) - // .setMargin("0 0 .6rem"), - // LinkButton("Sign in with Microsoft", API.auth.oauthRedirect("microsoft")) - // .setJustifyContent("center") - // .addPrefix( - // Image(discordLogo, "logo") - // .addClass("prefix-logo") - // ) - .setMargin("0 0 2rem"), - TextInput("email", "Email") - .ref(state.$email) - .onChange(() => state.error = undefined) - .setAutofill("email") - .required() - .setMargin("0 0 .6rem"), - TextInput("password", "Password") - .ref(state.$password) - .onChange(() => state.error = undefined) - .setAutofill("current-password") - .required() - .setMargin("0 0 .6rem"), - Button("Login") - .setId("login-button") - .onPromiseClick(async () => await loginUser()) - .setJustifyContent("center"), - ErrorMessage(), - Horizontal( - Label("New here?"), - Button("Create an Account") - .setStyle(ButtonStyle.Inline) - .onClick(() => state.type = "register") - .setColor(Color.Colored) - .addClass("link"), - Spacer(), - ) - .setMargin("1.3rem 0 0"), - Horizontal( - Label("Forgot your Password?"), - Button("Reset it here") - .setStyle(ButtonStyle.Inline) - .setColor(Color.Colored) - .onClick(() => state.type = "request-reset-password") - .addClass("link"), - Spacer(), - ), - )); - } + if (type == "login") { + return Grid( + PrimaryButton("Sign in with Google") + .onClick(() => { + location.href = API.auth.oauthRedirect("google"); + }) + .addPrefix( + Image(googleLogo, "Google Logo") + .setCssStyle("position", "absolute"), + ), + PrimaryButton("Sign in with Discord") + .onClick(() => { + location.href = API.auth.oauthRedirect("discord"); + }) + // .addPrefix( + // Image(discordLogo, "Discord Logo") + // .setCssStyle("position", "absolute"), + // ) + // LinkButton("Sign in with Microsoft", API.auth.oauthRedirect("microsoft")) + // .setJustifyContent("center") + // .addPrefix( + // Image(discordLogo, "logo") + // .addClass("prefix-logo") + // ) + .setMargin("0 0 1.3rem"), + EmailInput(state.email, "Email"), + // .onChange(() => state.error = undefined) + // .setAutofill("email") + // .required() + PasswordInput(state.password, "Password"), + // .onChange(() => state.error = undefined) + // .setAutofill("current-password") + // .required() + PrimaryButton("Login") + .onPromiseClick(async () => await loginUser()) + .setId("login-button") + .setJustifyContent("center"), + ErrorMessage(state.error), + Grid( + Label("New here?"), + TextButton("Create an Account") + .onClick(() => state.type.setValue("register")), + ).setTemplateColumns("auto auto"), + Grid( + Label("Forgot your Password?"), + TextButton("Reset it here") + .onClick(() => state.type.setValue("request-reset-password")), + ).setTemplateColumns("auto auto"), + ).setGap(); + } - if (type == "register") { - return Form(Grid( - TextInput("text", "Name") - .required() - .setAutofill("name") - .onChange(() => state.error = undefined) - .ref(state.$name) - .setMargin("0 0 .6rem"), - TextInput("email", "Email") - .setAutofill("email") - .required() - .onChange(() => state.error = undefined) - .ref(state.$email) - .setMargin("0 0 .6rem"), - TextInput("password", "Password") - .required() - .setAutofill("new-password") - .onChange(() => state.error = undefined) - .ref(state.$password) - .setMargin("0 0 .6rem"), - Button("Register") - .setId("register-button") - .onPromiseClick(registerUser) - .setJustifyContent("center"), - ErrorMessage(), - Horizontal( - Label("Already have an account?"), - Button("Sign in") - .setStyle(ButtonStyle.Inline) - .onClick(() => state.type = "login") - .setColor(Color.Colored) - .addClass("link"), - Spacer(), - ) - .setMargin("1rem 0 0"), - )); - } + // if (type == "register") { + // return Form(Grid( + // TextInput("text", "Name") + // .required() + // .setAutofill("name") + // .onChange(() => state.error = undefined) + // .ref(state.$name) + // .setMargin("0 0 .6rem"), + // TextInput("email", "Email") + // .setAutofill("email") + // .required() + // .onChange(() => state.error = undefined) + // .ref(state.$email) + // .setMargin("0 0 .6rem"), + // TextInput("password", "Password") + // .required() + // .setAutofill("new-password") + // .onChange(() => state.error = undefined) + // .ref(state.$password) + // .setMargin("0 0 .6rem"), + // Button("Register") + // .setId("register-button") + // .onPromiseClick(registerUser) + // .setJustifyContent("center"), + // ErrorMessage(), + // Horizontal( + // Label("Already have an account?"), + // Button("Sign in") + // .setStyle(ButtonStyle.Inline) + // .onClick(() => state.type = "login") + // .setColor(Color.Colored) + // .addClass("link"), + // Spacer(), + // ) + // .setMargin("1rem 0 0"), + // )); + // } - return Box( - LoadingSpinner(), - Label("Loading..."), - ErrorMessage(), - ).addClass("loading", "loader"); - }).asRefComponent(), + return Box( + Spinner(), + Label("Loading..."), + ErrorMessage(state.error), + ).addClass("loading", "loader"); + })), + ), + ) + .setMinHeight("100vh") + .setCssStyle("placeItems", "center") + .addClass("auth-area"), + FullWidthSection( + Footer(), + ), ), - ).addClass("auth-area"), - Footer(), -)))); + ) + .setPrimaryColor(new Color("#eb8c2d")), +); await handleStateChange(); diff --git a/pages/user/state.ts b/pages/user/state.ts index 97052883..b906199a 100644 --- a/pages/user/state.ts +++ b/pages/user/state.ts @@ -1,11 +1,11 @@ -import { asState } from "webgen/mod.ts"; +import { asRefRecord } from "webgen/mod.ts"; type ViewType = "loading" | "request-reset-password" | "reset-password-from-email" | "login" | "register"; -export const state = asState({ +export const state = asRefRecord({ type: "loading", - name: undefined, - email: undefined, - password: undefined, + name: "", + email: "", + password: "", error: undefined, }); diff --git a/serve.ts b/serve.ts index cffcb781..c0cab44a 100644 --- a/serve.ts +++ b/serve.ts @@ -1,7 +1,4 @@ import { serve } from "https://deno.land/x/esbuild_serve@1.5.0/mod.ts"; -// import { serve } from "../esbuild_serve/mod.ts"; - -// CI FAIL const title = new Map(Object.entries({ "index": "BBN Music", @@ -58,7 +55,7 @@ serve({ // "p/privacy-policy": "./pages/holding/privacyPolicy.ts", // "p/terms": "./pages/holding/terms.ts", // "p/imprint": "./pages/holding/imprint.ts", - // "signin": "./pages/user/signin.ts", + "signin": "./pages/user/signin.ts", // "callback": "./pages/misc/callback.ts", // "oauth": "./pages/user/oauth.ts", // "music": "./pages/music-landing/main.ts",