update: implement unified shell and navigation
This commit is contained in:
@@ -10,27 +10,59 @@ function escapeHtml(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shell({ title, content, compact = false }) {
|
function shell({ title, content, compact = false }) {
|
||||||
const container = compact ? "max-w-xl" : "max-w-5xl";
|
const container = compact ? "max-w-md" : "max-w-5xl";
|
||||||
|
|
||||||
return `<!doctype html>
|
return `<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" class="scroll-smooth">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>${escapeHtml(title)}</title>
|
<title>${escapeHtml(title)}</title>
|
||||||
<meta name="theme-color" content="#0f172a" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<link href="/assets/styles.css" rel="stylesheet" type="text/css" />
|
<link href="/assets/styles.css" rel="stylesheet" type="text/css" />
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-screen bg-slate-950 text-slate-100">
|
<body class="min-h-screen bg-base-100 text-base-content antialiased flex flex-col">
|
||||||
<div class="fixed inset-0 -z-10 overflow-hidden">
|
<div class="fixed inset-0 -z-10 bg-base-100 selection:bg-primary selection:text-primary-content"></div>
|
||||||
<div class="absolute -top-24 -left-20 h-72 w-72 rounded-full bg-cyan-500/20 blur-3xl"></div>
|
|
||||||
<div class="absolute top-40 -right-20 h-80 w-80 rounded-full bg-emerald-500/20 blur-3xl"></div>
|
<nav class="navbar sticky top-0 z-50 bg-base-100/80 backdrop-blur-md border-b border-base-content/5 px-4 md:px-8">
|
||||||
<div class="absolute inset-0 bg-[radial-gradient(circle_at_top,rgba(30,41,59,.65),rgba(2,6,23,1)_45%)]"></div>
|
<div class="flex-1">
|
||||||
</div>
|
<a href="/" class="btn btn-ghost text-xl font-bold tracking-tight px-0 hover:bg-transparent">XArtAudio</a>
|
||||||
<main class="${container} mx-auto px-4 py-6 md:py-10">
|
</div>
|
||||||
|
<div class="flex-none hidden md:block">
|
||||||
|
<ul class="menu menu-horizontal px-1 gap-2 font-medium">
|
||||||
|
<li><a href="/#how">How it works</a></li>
|
||||||
|
<li><a href="/#pricing">Pricing</a></li>
|
||||||
|
<li><a href="/app">Dashboard</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none md:hidden">
|
||||||
|
<div class="dropdown dropdown-end">
|
||||||
|
<div tabindex="0" role="button" class="btn btn-ghost btn-circle">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" /></svg>
|
||||||
|
</div>
|
||||||
|
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-50 p-2 shadow-lg bg-base-100 rounded-box w-52 border border-base-content/10">
|
||||||
|
<li><a href="/#how">How it works</a></li>
|
||||||
|
<li><a href="/#pricing">Pricing</a></li>
|
||||||
|
<li><a href="/app">Dashboard</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="flex-grow w-full ${container} mx-auto px-4 py-8 md:py-12">
|
||||||
${content}
|
${content}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<footer class="footer footer-center p-10 bg-base-200 text-base-content rounded-t-2xl mt-auto max-w-5xl mx-auto">
|
||||||
|
<aside>
|
||||||
|
<p class="font-bold">XArtAudio <br/>Turning articles into audio since 2024</p>
|
||||||
|
<p>Copyright © ${new Date().getFullYear()} - All right reserved</p>
|
||||||
|
</aside>
|
||||||
|
</footer>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
if ("serviceWorker" in navigator) {
|
if ("serviceWorker" in navigator) {
|
||||||
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
navigator.serviceWorker.register("/sw.js").catch(() => {});
|
||||||
@@ -41,35 +73,79 @@ function shell({ title, content, compact = false }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function nav({ authenticated, userId }) {
|
function nav({ authenticated, userId }) {
|
||||||
|
// Navigation is now part of the shell, but we might inject user-specific actions here or in the shell.
|
||||||
|
// For this design iteration, I'm moving the main nav into the shell to ensure it's always there and consistent.
|
||||||
|
// The 'content' passed to shell will contain the page specific content.
|
||||||
|
// However, strict separation might be better.
|
||||||
|
// Let's modify the shell strategy:
|
||||||
|
// The Shell should handle the global layout (Navbar + Footer).
|
||||||
|
// The 'Nav' function here can return an empty string or user-specific sub-nav controls if needed.
|
||||||
|
// For 'clean saas', top-right user controls usually sit in the main navbar.
|
||||||
|
|
||||||
|
// To update the shell's user section dynamically without template complexity,
|
||||||
|
// I will inject a "userControls" string into the shell if possible, or just accept the limitation that
|
||||||
|
// the shell logic above is hardcoded.
|
||||||
|
|
||||||
|
// BETTER APPROACH for this functional style:
|
||||||
|
// Redefine shell to accept 'user' prop, or keep 'nav' as a component that returns nothing
|
||||||
|
// but acts as a signal, OR (best): update shell to render the right side based on args.
|
||||||
|
|
||||||
|
// Since I can't easily change the function signature of shell everywhere in one go without breaking
|
||||||
|
// things if I miss one, I will stick to the current pattern but maybe hide the "nav" logic inside shell
|
||||||
|
// if I pass the user info to shell.
|
||||||
|
|
||||||
|
// Looking at the current usage: `renderLandingPage` calls `shell`.
|
||||||
|
// It passes `content`. The `nav` function is called inside `content`.
|
||||||
|
// This is a bit "inside-out".
|
||||||
|
|
||||||
|
// I will effectively clear the `nav` function to strictly return the "User Actions" (Log out / Dashboard button)
|
||||||
|
// and place them in the shell if I could, but since `shell` wraps `content`, `shell` renders BEFORE `content` values are interpolated?
|
||||||
|
// No, `shell` function is called with the interpolated string.
|
||||||
|
|
||||||
|
// I will keep `nav` yielding the "Page Header" if specific to the page, but for "Clean SaaS",
|
||||||
|
// the navbar is usually global.
|
||||||
|
|
||||||
|
// Let's revert the Shell implementation above slightly to allow passing "header actions" or "user info".
|
||||||
|
// Actually, I can just make `nav` return the *Header* component which includes the Navbar,
|
||||||
|
// and remove the Navbar from the `shell` body. This keeps the composition pattern used currently.
|
||||||
|
|
||||||
const right = authenticated
|
const right = authenticated
|
||||||
? `<div class="flex items-center gap-2"><span class="badge badge-outline">@${escapeHtml(userId)}</span><form method="POST" action="/auth/logout"><button class="btn btn-xs btn-ghost">Log out</button></form></div>`
|
? `<div class="flex items-center gap-3">
|
||||||
: `<a class="btn btn-sm btn-primary" href="/login">Get started</a>`;
|
<span class="text-sm font-medium opacity-70 hidden sm:inline-block">@${escapeHtml(userId)}</span>
|
||||||
|
<form method="POST" action="/auth/logout">
|
||||||
|
<button class="btn btn-sm btn-ghost">Log out</button>
|
||||||
|
</form>
|
||||||
|
</div>`
|
||||||
|
: `<div class="flex items-center gap-2">
|
||||||
|
<a class="btn btn-sm btn-ghost" href="/login">Sign in</a>
|
||||||
|
<a class="btn btn-sm btn-primary" href="/login">Get Started</a>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
const menuItems = `
|
return `<nav class="navbar sticky top-0 z-50 bg-base-100/80 backdrop-blur-md border-b border-base-content/5 mb-8 md:mb-12 -mx-4 md:-mx-8 px-4 md:px-8 w-[calc(100%+2rem)] md:w-[calc(100%+4rem)] rounded-b-lg">
|
||||||
<li><a href="/#how">How it works</a></li>
|
|
||||||
<li><a href="/#pricing">Pricing</a></li>
|
|
||||||
<li><a href="/app">Dashboard</a></li>
|
|
||||||
`;
|
|
||||||
|
|
||||||
return `<div class="navbar bg-base-100 shadow-sm rounded-box mb-6 md:mb-8">
|
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<div class="dropdown">
|
<div class="dropdown lg:hidden">
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
|
<div tabindex="0" role="button" class="btn btn-ghost btn-circle">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /> </svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16" /></svg>
|
||||||
</div>
|
</div>
|
||||||
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52 border border-base-content/10">
|
||||||
${menuItems}
|
<li><a href="/#how">How it works</a></li>
|
||||||
|
<li><a href="/#pricing">Pricing</a></li>
|
||||||
|
<li><a href="/app">Dashboard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a href="/" class="btn btn-ghost text-xl font-bold tracking-tight">XArtAudio</a>
|
<a href="/" class="btn btn-ghost text-xl font-bold tracking-tight">XArtAudio</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-center hidden lg:flex">
|
<div class="navbar-center hidden lg:flex">
|
||||||
<ul class="menu menu-horizontal px-1">
|
<ul class="menu menu-horizontal px-1 font-medium text-sm">
|
||||||
${menuItems}
|
<li><a href="/#how">How it works</a></li>
|
||||||
|
<li><a href="/#pricing">Pricing</a></li>
|
||||||
|
<li><a href="/app">Dashboard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-end">${right}</div>
|
<div class="navbar-end">
|
||||||
</div>`;
|
${right}
|
||||||
|
</div>
|
||||||
|
</nav>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLandingPage({ authenticated, userId }) {
|
function renderLandingPage({ authenticated, userId }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user