Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 189 additions & 33 deletions app/components/Navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,205 @@
"use client";

import { useEffect, useState } from "react";
import Menu from "./Menu";
import ThemeSwitch from "./ThemeSwitch";
import Link from "next/link";
import clsx from "clsx";
import { links } from "@/lib/navData";
import { usePathname } from "next/navigation";

export default function Navbar() {
const [open, setOpen] = useState(false);
const [solid, setSolid] = useState(false);
const [time, setTime] = useState("");
const rawPathname = usePathname();
const pathname = rawPathname.startsWith("/blog") ? "/blog" : rawPathname;

useEffect(() => {
function handleScroll() {
if (window.scrollY >= 20) {
setSolid(true);
} else {
setSolid(false);
}
function updateTime() {
const now = new Date();
setTime(
now.toLocaleTimeString(undefined, {
hour: "2-digit",
minute: "2-digit",
}),
);
}

window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
});
updateTime();
const id = setInterval(updateTime, 30000);
return () => clearInterval(id);
}, []);

return (
<nav
className={clsx(
"fixed top-0 left-0 z-50 flex w-full items-center justify-between px-4 py-2 before:absolute before:left-0 before:-z-10 before:h-full before:w-full before:rounded-b-2xl before:border-b before:bg-zinc-50/70 before:backdrop-blur-lg before:transition lg:hidden dark:before:bg-zinc-950/70",
{
"before:border-zinc-200/70 before:opacity-100 before:drop-shadow-lg dark:before:border-zinc-800/70":
solid,
"before:border-transparent before:opacity-0": !solid,
},
)}
>
<Menu open={open} setOpen={setOpen} />

<Link
className="text-3xl font-semibold transition-colors hover:text-zinc-600 dark:hover:text-zinc-400"
href="/"
<>
{/* Win2000 Taskbar */}
<nav
className="fixed top-0 left-0 z-50 w-full lg:hidden"
style={{
background: "#d4d0c8",
borderBottom: "2px solid #404040",
boxShadow: "0 2px 0 #808080",
display: "flex",
alignItems: "center",
padding: "2px 4px",
gap: "4px",
}}
>
Nick Oates
</Link>
{/* Start button */}
<button
type="button"
onClick={() => setOpen((v) => !v)}
aria-expanded={open}
aria-label="Open navigation menu"
style={{
display: "flex",
alignItems: "center",
gap: "4px",
padding: "2px 8px",
background: open ? "#808080" : "#d4d0c8",
borderTop: open ? "2px solid #404040" : "2px solid #ffffff",
borderLeft: open ? "2px solid #404040" : "2px solid #ffffff",
borderRight: open ? "2px solid #ffffff" : "2px solid #404040",
borderBottom: open ? "2px solid #ffffff" : "2px solid #404040",
boxShadow: open
? "inset 1px 1px 0 #808080, inset -1px -1px 0 #dfdfdf"
: "inset 1px 1px 0 #dfdfdf, inset -1px -1px 0 #808080",
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
fontWeight: "bold",
color: "#000",
cursor: "default",
}}
>
<span style={{ fontSize: "14px" }}>πŸͺŸ</span>
Start
</button>

{/* Divider */}
<div style={{ width: "2px", height: "20px", background: "#808080", borderRight: "1px solid #fff" }} aria-hidden />

{/* Page title */}
<span
style={{
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
color: "#000",
flexGrow: 1,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
}}
>
Nick Oates β€” {links.find((l) => l.href === pathname)?.title ?? "Page"}
</span>

<ThemeSwitch />
</nav>
{/* System clock */}
<div
style={{
background: "#d4d0c8",
borderTop: "1px solid #808080",
borderLeft: "1px solid #808080",
borderRight: "1px solid #ffffff",
borderBottom: "1px solid #ffffff",
padding: "2px 6px",
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
color: "#000",
}}
>
{time}
</div>
</nav>

{/* Start Menu popup */}
{open && (
<div
className="fixed z-50 lg:hidden"
style={{
top: "32px",
left: "4px",
background: "#d4d0c8",
borderTop: "2px solid #ffffff",
borderLeft: "2px solid #ffffff",
borderRight: "2px solid #404040",
borderBottom: "2px solid #404040",
boxShadow: "2px 2px 8px rgba(0,0,0,0.5), inset 1px 1px 0 #dfdfdf, inset -1px -1px 0 #808080",
minWidth: "160px",
}}
>
{/* Start menu header */}
<div
style={{
background: "linear-gradient(to bottom, #0a246a, #3a5fa8)",
padding: "8px 6px",
writingMode: "vertical-rl",
transform: "rotate(180deg)",
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "14px",
fontWeight: "bold",
color: "#fff",
float: "left",
height: "100%",
minHeight: "140px",
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
letterSpacing: "1px",
}}
>
Nick<span style={{ fontWeight: "normal", opacity: 0.8 }}>Oates</span>
</div>
<div style={{ marginLeft: "28px" }}>
{links.map((link) => {
const isActive = pathname === link.href;
return (
<Link
key={link.href}
href={link.href}
onClick={() => setOpen(false)}
style={{
display: "flex",
alignItems: "center",
gap: "8px",
padding: "5px 16px 5px 8px",
textDecoration: "none",
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
color: isActive ? "#fff" : "#000",
background: isActive ? "#0a246a" : "transparent",
}}
className={!isActive ? "hover:bg-[#0a246a] hover:text-white" : ""}
>
<link.Icon size={20} aria-hidden />
{link.title}
</Link>
);
})}
<div style={{ borderTop: "1px solid #808080", margin: "2px 0" }} aria-hidden />
<a
href="mailto:nick@nickoates.com"
style={{
display: "flex",
alignItems: "center",
gap: "8px",
padding: "5px 16px 5px 8px",
textDecoration: "none",
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
color: "#000",
}}
className="hover:bg-[#0a246a] hover:text-white"
>
βœ‰ Contact
</a>
</div>
</div>
)}

{/* Backdrop to close menu */}
{open && (
<div
className="fixed inset-0 z-40 lg:hidden"
onClick={() => setOpen(false)}
aria-hidden
/>
)}
</>
);
}
113 changes: 72 additions & 41 deletions app/components/NewsletterForm.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
"use client";

import { IconLoader2, IconMail } from "@tabler/icons-react";
import clsx from "clsx";
import { subscribe, type State } from "@/lib/actions";
import { useActionState } from "react";
import TextInput from "@/components/TextInput";

export default function NewsletterForm({
title = "Subscribe to my newsletter",
Expand All @@ -19,50 +17,83 @@ export default function NewsletterForm({
message: title,
});

const icon = isPending ? (
<IconLoader2 className="animate-spin" />
) : (
<IconMail />
);

return (
<div className="mx-auto max-w-lg rounded-3xl border border-zinc-400/50 bg-neutral-100/50 p-4 backdrop-blur-sm md:text-lg dark:border-zinc-600/50 dark:bg-neutral-900/50 print:hidden">
<span
className={clsx(
{
"text-red-600 dark:text-red-400": status === "error",
},
"font-semibold",
)}
<div className="print:hidden">
<p
style={{
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
fontWeight: "bold",
color: status === "error" ? "#cc0000" : "#000",
marginBottom: "6px",
}}
>
{message}
</span>

<form
className={clsx("mt-2 flex w-full flex-wrap justify-between gap-2", {
hidden: status === "success",
})}
action={formAction}
>
<TextInput
className="grow rounded-xl"
type="email"
id="email"
name="email"
placeholder="Enter your email..."
required
aria-label="Email address"
autoComplete="email"
/>
</p>

<button
className="box-border flex grow cursor-default flex-row items-center justify-center gap-2 rounded-xl border-t border-white/30 bg-linear-to-b from-teal-600 to-teal-800 px-4 py-2 font-semibold text-white drop-shadow-xs active:opacity-70 enabled:hover:from-teal-500 enabled:hover:to-teal-700 disabled:opacity-70 dark:from-teal-700 dark:to-teal-900 dark:enabled:hover:from-teal-600 dark:enabled:hover:to-teal-800"
disabled={isPending}
type="submit"
{status !== "success" && (
<form
action={formAction}
style={{
display: "flex",
flexWrap: "wrap",
gap: "4px",
}}
>
{icon} Subscribe
</button>
</form>
<input
type="email"
id="email"
name="email"
placeholder="Enter your email address..."
required
aria-label="Email address"
autoComplete="email"
style={{
flex: "1 1 180px",
background: "#fff",
borderTop: "2px solid #404040",
borderLeft: "2px solid #404040",
borderRight: "2px solid #ffffff",
borderBottom: "2px solid #ffffff",
boxShadow: "inset 1px 1px 0 #808080",
padding: "2px 6px",
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
color: "#000",
outline: "none",
}}
/>
<button
type="submit"
disabled={isPending}
style={{
display: "flex",
alignItems: "center",
gap: "4px",
padding: "2px 10px",
background: "#d4d0c8",
borderTop: "2px solid #ffffff",
borderLeft: "2px solid #ffffff",
borderRight: "2px solid #404040",
borderBottom: "2px solid #404040",
boxShadow: "inset 1px 1px 0 #dfdfdf, inset -1px -1px 0 #808080",
fontFamily: '"MS Sans Serif", Tahoma, Arial, sans-serif',
fontSize: "11px",
color: "#000",
cursor: "default",
opacity: isPending ? 0.6 : 1,
whiteSpace: "nowrap",
}}
>
{isPending ? (
<IconLoader2 size={14} className="animate-spin" aria-hidden />
) : (
<IconMail size={14} aria-hidden />
)}
Subscribe
</button>
</form>
)}
</div>
);
}
Loading