UI Consolidation Cont.

This commit is contained in:
Michael Freno
2026-01-06 10:40:15 -05:00
parent 609932a55b
commit 021a2871c1
25 changed files with 235 additions and 220 deletions

View File

@@ -47,3 +47,6 @@ export default function PageHead(props: PageHeadProps) {
</> </>
); );
} }
// Named export for consistency
export { PageHead };

View File

@@ -250,6 +250,9 @@ declare module "@tiptap/core" {
iframe: { iframe: {
setIframe: (options: { src: string }) => ReturnType; setIframe: (options: { src: string }) => ReturnType;
}; };
video: {
setVideo: (options: { src: string }) => ReturnType;
};
} }
} }
@@ -298,24 +301,76 @@ const IframeEmbed = Node.create<IframeOptions>({
return { return {
setIframe: setIframe:
(options: { src: string }) => (options: { src: string }) =>
({ tr, dispatch, editor }) => { ({ tr, dispatch }) => {
const { selection } = tr; const { selection } = tr;
const node = this.type.create(options);
// Check if the src is a direct video file
const src = options.src || "";
const isVideoFile = /\.(mp4|mov|webm|ogg)(\?.*)?$/i.test(src);
if (isVideoFile) {
// Insert a proper video tag instead of iframe
if (dispatch) { if (dispatch) {
const videoHTML = `<video src="${src}" controls playsinline style="max-width: 100%; height: auto;"></video>`; tr.replaceRangeWith(selection.from, selection.to, node);
editor.commands.insertContent(videoHTML);
} }
return true; return true;
} }
};
}
});
// For non-video URLs, create iframe as normal interface VideoOptions {
const node = this.type.create(options); HTMLAttributes: {
[key: string]: any;
};
}
const Video = Node.create<VideoOptions>({
name: "video",
group: "block",
atom: true,
addOptions() {
return {
HTMLAttributes: {
class: "video-wrapper"
}
};
},
addAttributes() {
return {
src: {
default: null
},
controls: {
default: true
},
playsinline: {
default: true
}
};
},
parseHTML() {
return [
{
tag: "video[src]"
}
];
},
renderHTML({ HTMLAttributes }) {
return ["video", HTMLAttributes];
},
addCommands() {
return {
setVideo:
(options: { src: string }) =>
({ tr, dispatch }) => {
const { selection } = tr;
const node = this.type.create({
src: options.src,
controls: true,
playsinline: true
});
if (dispatch) { if (dispatch) {
tr.replaceRangeWith(selection.from, selection.to, node); tr.replaceRangeWith(selection.from, selection.to, node);
@@ -1352,6 +1407,7 @@ export default function TextEditor(props: TextEditorProps) {
}), }),
Image, Image,
IframeEmbed, IframeEmbed,
Video,
TaskList, TaskList,
TaskItem.configure({ TaskItem.configure({
nested: true, nested: true,
@@ -2454,12 +2510,22 @@ export default function TextEditor(props: TextEditorProps) {
const instance = editor(); const instance = editor();
if (!instance) return; if (!instance) return;
const url = window.prompt("URL"); const url = window.prompt("Embed URL (YouTube, etc.)");
if (url) { if (url) {
instance.commands.setIframe({ src: url }); instance.commands.setIframe({ src: url });
} }
}; };
const addVideo = () => {
const instance = editor();
if (!instance) return;
const url = window.prompt("Video URL (direct link to .mp4, .webm, etc.)");
if (url) {
instance.commands.setVideo({ src: url });
}
};
const addImage = () => { const addImage = () => {
const instance = editor(); const instance = editor();
if (!instance) return; if (!instance) return;
@@ -3790,13 +3856,21 @@ export default function TextEditor(props: TextEditorProps) {
> >
🖼 Image 🖼 Image
</button> </button>
<button
type="button"
onClick={addVideo}
class="touch-manipulation rounded px-2 py-1 text-xs select-none"
title="Add Video"
>
🎬 Video
</button>
<button <button
type="button" type="button"
onClick={addIframe} onClick={addIframe}
class="touch-manipulation rounded px-2 py-1 text-xs select-none" class="touch-manipulation rounded px-2 py-1 text-xs select-none"
title="Add Iframe" title="Add Iframe Embed"
> >
📺 Iframe 📺 Embed
</button> </button>
<button <button
type="button" type="button"

View File

@@ -23,7 +23,7 @@ export default function Button(props: ButtonProps) {
const size = () => local.size || "md"; const size = () => local.size || "md";
const baseClasses = const baseClasses =
"flex justify-center items-center rounded transition-all duration-300 ease-out"; "flex justify-center cursor-pointer items-center rounded transition-all duration-300 ease-out";
const variantClasses = () => { const variantClasses = () => {
const isDisabledOrLoading = local.disabled || local.loading; const isDisabledOrLoading = local.disabled || local.loading;
@@ -77,3 +77,6 @@ export default function Button(props: ButtonProps) {
</button> </button>
); );
} }
// Named export for consistency
export { Button };

View File

@@ -24,7 +24,7 @@ export default function PasswordInput(props: PasswordInputProps) {
); );
return ( return (
<> <div class="flex flex-col items-center gap-2">
<div class={local.containerClass || "input-group relative mx-4 mb-2"}> <div class={local.containerClass || "input-group relative mx-4 mb-2"}>
<Input <Input
{...inputProps} {...inputProps}
@@ -36,7 +36,7 @@ export default function PasswordInput(props: PasswordInputProps) {
<button <button
type="button" type="button"
onClick={() => setShowPassword(!showPassword())} onClick={() => setShowPassword(!showPassword())}
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125" class="text-subtext0 absolute right-0 bottom-2 transition-all hover:brightness-125"
aria-label={showPassword() ? "Hide password" : "Show password"} aria-label={showPassword() ? "Hide password" : "Show password"}
> >
<Show <Show
@@ -56,10 +56,8 @@ export default function PasswordInput(props: PasswordInputProps) {
</div> </div>
{local.showStrength && local.passwordValue !== undefined && ( {local.showStrength && local.passwordValue !== undefined && (
<div class="px-4 pt-1">
<PasswordStrengthMeter password={local.passwordValue} /> <PasswordStrengthMeter password={local.passwordValue} />
</div>
)} )}
</> </div>
); );
} }

View File

@@ -1,4 +1,4 @@
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { HttpStatusCode } from "@solidjs/start"; import { HttpStatusCode } from "@solidjs/start";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { createEffect, createSignal, For } from "solid-js"; import { createEffect, createSignal, For } from "solid-js";
@@ -51,10 +51,9 @@ export default function Page_401() {
return ( return (
<> <>
<Title>401 Unauthorized | Michael Freno</Title> <PageHead
<Meta title="401 Unauthorized"
name="description" description="401 - Unauthorized access. Please log in to access this page."
content="401 - Unauthorized access. Please log in to access this page."
/> />
<HttpStatusCode code={401} /> <HttpStatusCode code={401} />
<div class="relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-slate-900 via-amber-950/20 to-slate-900 dark:from-black dark:via-amber-950/30 dark:to-black"> <div class="relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-slate-900 via-amber-950/20 to-slate-900 dark:from-black dark:via-amber-950/30 dark:to-black">

View File

@@ -1,4 +1,4 @@
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { HttpStatusCode } from "@solidjs/start"; import { HttpStatusCode } from "@solidjs/start";
import { useNavigate, useLocation } from "@solidjs/router"; import { useNavigate, useLocation } from "@solidjs/router";
import { createSignal, Show } from "solid-js"; import { createSignal, Show } from "solid-js";
@@ -101,10 +101,9 @@ export default function NotFound() {
{/*@ts-ignore (intentional crash)*/} {/*@ts-ignore (intentional crash)*/}
<CrashComponent /> <CrashComponent />
</Show> </Show>
<Title>404 Not Found | Michael Freno</Title> <PageHead
<Meta title="404 Not Found"
name="description" description="404 - Page not found. The page you're looking for doesn't exist."
content="404 - Page not found. The page you're looking for doesn't exist."
/> />
<HttpStatusCode code={404} /> <HttpStatusCode code={404} />
<TerminalErrorPage <TerminalErrorPage

View File

@@ -1,5 +1,5 @@
import { createSignal, Show, createEffect } from "solid-js"; import { createSignal, Show, createEffect } from "solid-js";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { useNavigate, redirect, query, createAsync } from "@solidjs/router"; import { useNavigate, redirect, query, createAsync } from "@solidjs/router";
import { getEvent } from "vinxi/http"; import { getEvent } from "vinxi/http";
import XCircle from "~/components/icons/XCircle"; import XCircle from "~/components/icons/XCircle";
@@ -486,10 +486,9 @@ export default function AccountPage() {
return ( return (
<> <>
<Title>Account | Michael Freno</Title> <PageHead
<Meta title="Account"
name="description" description="Manage your account settings, update profile information, and configure preferences."
content="Manage your account settings, update profile information, and configure preferences."
/> />
<div class="bg-base mx-8 min-h-screen md:mx-24 lg:mx-36"> <div class="bg-base mx-8 min-h-screen md:mx-24 lg:mx-36">
@@ -683,23 +682,17 @@ export default function AccountPage() {
</div> </div>
</Show> </Show>
<div class="flex justify-end"> <div class="flex justify-end">
<button <Button
type="submit" type="submit"
disabled={ disabled={
emailButtonLoading() || userProfile().email !== null &&
(userProfile().email !== null && !userProfile().emailVerified
!userProfile().emailVerified)
} }
class={`${ loading={emailButtonLoading()}
emailButtonLoading() || class="mt-2"
(userProfile().email !== null &&
!userProfile().emailVerified)
? "bg-blue cursor-not-allowed brightness-75"
: "bg-blue hover:brightness-125 active:scale-90"
} mt-2 flex justify-center rounded px-4 py-2 text-base transition-all duration-300 ease-out`}
> >
{emailButtonLoading() ? "Submitting..." : "Submit"} Submit
</button> </Button>
</div> </div>
<Show when={showEmailSuccess()}> <Show when={showEmailSuccess()}>
<div class="text-green mt-2 text-center text-sm"> <div class="text-green mt-2 text-center text-sm">
@@ -740,19 +733,13 @@ export default function AccountPage() {
containerClass="input-group mx-4" containerClass="input-group mx-4"
/> />
<div class="flex justify-end"> <div class="flex justify-end">
<button <Button
type="submit" type="submit"
disabled={displayNameButtonLoading()} loading={displayNameButtonLoading()}
class={`${ class="mt-2"
displayNameButtonLoading()
? "bg-blue cursor-not-allowed brightness-75"
: "bg-blue hover:brightness-125 active:scale-90"
} mt-2 flex justify-center rounded px-4 py-2 text-base transition-all duration-300 ease-out`}
> >
{displayNameButtonLoading() Submit
? "Submitting..." </Button>
: "Submit"}
</button>
</div> </div>
<Show when={showDisplayNameSuccess()}> <Show when={showDisplayNameSuccess()}>
<div class="text-green mt-2 text-center text-sm"> <div class="text-green mt-2 text-center text-sm">
@@ -838,17 +825,14 @@ export default function AccountPage() {
</div> </div>
</Show> </Show>
<button <Button
type="submit" type="submit"
disabled={passwordChangeLoading() || !passwordsMatch()} disabled={!passwordsMatch()}
class={`${ loading={passwordChangeLoading()}
passwordChangeLoading() || !passwordsMatch() class="my-6"
? "bg-blue cursor-not-allowed brightness-75"
: "bg-blue hover:brightness-125 active:scale-90"
} my-6 flex justify-center rounded px-4 py-2 text-base transition-all duration-300 ease-out`}
> >
{passwordChangeLoading() ? "Setting..." : "Set"} Set
</button> </Button>
<Show when={passwordError()}> <Show when={passwordError()}>
<div class="text-red text-center text-sm"> <div class="text-red text-center text-sm">
@@ -871,18 +855,15 @@ export default function AccountPage() {
{/* Sign Out Section */} {/* Sign Out Section */}
<div class="mx-auto max-w-md py-4"> <div class="mx-auto max-w-md py-4">
<button <Button
type="button" type="button"
onClick={handleSignOut} onClick={handleSignOut}
disabled={signOutLoading()} loading={signOutLoading()}
class={`${ variant="secondary"
signOutLoading() class="w-full"
? "bg-overlay0 cursor-not-allowed opacity-75"
: "bg-overlay0 hover:bg-overlay1"
} w-full rounded px-4 py-2 transition-all`}
> >
{signOutLoading() ? "Signing Out..." : "Sign Out"} Sign Out
</button> </Button>
</div> </div>
<hr class="mt-8 mb-8" /> <hr class="mt-8 mb-8" />
@@ -938,19 +919,14 @@ export default function AccountPage() {
/> />
</div> </div>
<button <Button
type="submit" type="submit"
disabled={deleteAccountButtonLoading()} loading={deleteAccountButtonLoading()}
class={`${ variant="danger"
deleteAccountButtonLoading() class="border-text mx-auto mt-4 border"
? "bg-red cursor-not-allowed brightness-75"
: "bg-red hover:brightness-125 active:scale-90"
} border-text mx-auto mt-4 flex justify-center rounded border px-4 py-2 text-base transition-all duration-300 ease-out`}
> >
{deleteAccountButtonLoading() Delete Account
? "Deleting..." </Button>
: "Delete Account"}
</button>
<Show when={passwordDeletionError()}> <Show when={passwordDeletionError()}>
<div class="text-red mt-2 text-center text-sm"> <div class="text-red mt-2 text-center text-sm">

View File

@@ -1,5 +1,5 @@
import { createSignal, Show, For, createEffect, ErrorBoundary } from "solid-js"; import { createSignal, Show, For, createEffect, ErrorBoundary } from "solid-js";
import { Title } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { redirect, query, createAsync, useNavigate } from "@solidjs/router"; import { redirect, query, createAsync, useNavigate } from "@solidjs/router";
import { getEvent } from "vinxi/http"; import { getEvent } from "vinxi/http";
import { api } from "~/lib/api"; import { api } from "~/lib/api";
@@ -128,7 +128,10 @@ export default function AnalyticsPage() {
return ( return (
<> <>
<Title>Analytics Dashboard - Admin</Title> <PageHead
title="Analytics Dashboard - Admin"
description="Visitor analytics and performance metrics"
/>
<div class="bg-base min-h-screen px-4 py-8"> <div class="bg-base min-h-screen px-4 py-8">
<div class="mx-auto max-w-7xl"> <div class="mx-auto max-w-7xl">
<div class="mb-8"> <div class="mb-8">

View File

@@ -6,7 +6,7 @@ import {
query, query,
useSearchParams useSearchParams
} from "@solidjs/router"; } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { createAsync } from "@solidjs/router"; import { createAsync } from "@solidjs/router";
import { getRequestEvent } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import SessionDependantLike from "~/components/blog/SessionDependantLike"; import SessionDependantLike from "~/components/blog/SessionDependantLike";
@@ -316,12 +316,9 @@ export default function PostPage() {
return ( return (
<> <>
<Title> <PageHead
{p().title.replaceAll("_", " ")} | Michael Freno title={p().title.replaceAll("_", " ")}
</Title> description={
<Meta
name="description"
content={
p().subtitle || p().subtitle ||
`Read ${p().title.replaceAll("_", " ")} by Michael Freno on the freno.me blog.` `Read ${p().title.replaceAll("_", " ")} by Michael Freno on the freno.me blog.`
} }

View File

@@ -1,6 +1,6 @@
import { Show, lazy } from "solid-js"; import { Show, lazy } from "solid-js";
import { query, redirect } from "@solidjs/router"; import { query, redirect } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { createAsync } from "@solidjs/router"; import { createAsync } from "@solidjs/router";
import { getEvent } from "vinxi/http"; import { getEvent } from "vinxi/http";
import { Spinner } from "~/components/Spinner"; import { Spinner } from "~/components/Spinner";
@@ -31,10 +31,9 @@ export default function CreatePost() {
return ( return (
<> <>
<Title>Create Blog Post | Michael Freno</Title> <PageHead
<Meta title="Create Blog Post"
name="description" description="Create a new blog post with rich text editing, image uploads, and tag management."
content="Create a new blog post with rich text editing, image uploads, and tag management."
/> />
<Show when={authState()?.userID} fallback={<Spinner />}> <Show when={authState()?.userID} fallback={<Spinner />}>

View File

@@ -1,6 +1,6 @@
import { Show, lazy } from "solid-js"; import { Show, lazy } from "solid-js";
import { useParams, query } from "@solidjs/router"; import { useParams, query, redirect } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { createAsync } from "@solidjs/router"; import { createAsync } from "@solidjs/router";
import { getEvent } from "vinxi/http"; import { getEvent } from "vinxi/http";
import "../post.css"; import "../post.css";
@@ -66,10 +66,9 @@ export default function EditPost() {
return ( return (
<> <>
<Title>Edit Post | Michael Freno</Title> <PageHead
<Meta title="Edit Post"
name="description" description="Edit your blog post with rich text editing, image management, and tag updates."
content="Edit your blog post with rich text editing, image management, and tag updates."
/> />
<Show <Show

View File

@@ -1,6 +1,6 @@
import { Show } from "solid-js"; import { Show } from "solid-js";
import { useSearchParams, A, query } from "@solidjs/router"; import { useSearchParams, A, query } from "@solidjs/router";
import { Title } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { createAsync } from "@solidjs/router"; import { createAsync } from "@solidjs/router";
import { getRequestEvent } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import PostSortingSelect from "~/components/blog/PostSortingSelect"; import PostSortingSelect from "~/components/blog/PostSortingSelect";
@@ -91,7 +91,10 @@ export default function BlogIndex() {
return ( return (
<> <>
<Title>Blog | Michael Freno</Title> <PageHead
title="Blog"
description="Technical blog posts about web development, programming, and software engineering."
/>
<div class="mx-auto py-16 pb-24"> <div class="mx-auto py-16 pb-24">
<Show when={data()} fallback={<TerminalSplash />}> <Show when={data()} fallback={<TerminalSplash />}>

View File

@@ -6,15 +6,16 @@ import {
query, query,
createAsync createAsync
} from "@solidjs/router"; } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
import { A } from "@solidjs/router"; import { A } from "@solidjs/router";
import { action, redirect } from "@solidjs/router"; import { action, redirect } from "@solidjs/router";
import { PageHead } from "~/components/PageHead";
import { api } from "~/lib/api"; import { api } from "~/lib/api";
import { getClientCookie, setClientCookie } from "~/lib/cookies.client"; import { getClientCookie, setClientCookie } from "~/lib/cookies.client";
import CountdownCircleTimer from "~/components/CountdownCircleTimer"; import CountdownCircleTimer from "~/components/CountdownCircleTimer";
import LoadingSpinner from "~/components/LoadingSpinner"; import LoadingSpinner from "~/components/LoadingSpinner";
import RevealDropDown from "~/components/RevealDropDown"; import RevealDropDown from "~/components/RevealDropDown";
import Input from "~/components/ui/Input"; import Input from "~/components/ui/Input";
import { Button } from "~/components/ui/Button";
import type { UserProfile } from "~/types/user"; import type { UserProfile } from "~/types/user";
import { getCookie, setCookie } from "vinxi/http"; import { getCookie, setCookie } from "vinxi/http";
import { z } from "zod"; import { z } from "zod";
@@ -360,8 +361,7 @@ export default function ContactPage() {
return ( return (
<> <>
<Title>Contact | Michael Freno</Title> <PageHead title="Contact" description="Contact Me" />
<Meta name="description" content="Contact Me" />
<div class="bg-base flex min-h-screen w-full justify-center"> <div class="bg-base flex min-h-screen w-full justify-center">
<div class="w-full max-w-4xl px-4 pt-[20vh]"> <div class="w-full max-w-4xl px-4 pt-[20vh]">
@@ -422,19 +422,9 @@ export default function ContactPage() {
countDown() > 0 || (contactData()?.remainingTime ?? 0) > 0 countDown() > 0 || (contactData()?.remainingTime ?? 0) > 0
} }
fallback={ fallback={
<button <Button type="submit" loading={loading()} class="w-36">
type="submit" Send Message
disabled={loading()} </Button>
class={`${
loading()
? "bg-zinc-400"
: "bg-blue hover:brightness-125 active:scale-90"
} flex w-36 justify-center rounded py-3 text-base font-light transition-all duration-300 ease-out`}
>
<Show when={loading()} fallback="Send Message">
<LoadingSpinner height={24} width={24} />
</Show>
</button>
} }
> >
<Show <Show

View File

@@ -1,13 +1,12 @@
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import DeletionForm from "~/components/DeletionForm"; import DeletionForm from "~/components/DeletionForm";
export default function LifeAndLinageDeletionForm() { export default function LifeAndLinageDeletionForm() {
return ( return (
<> <>
<Title>Account Deletion - Life and Lineage | Michael Freno</Title> <PageHead
<Meta title="Account Deletion - Life and Lineage"
name="description" description="Request account deletion for Life and Lineage. Remove all your data from our system with a 24-hour grace period."
content="Request account deletion for Life and Lineage. Remove all your data from our system with a 24-hour grace period."
/> />
<div class="pt-20"> <div class="pt-20">
<div class="mx-auto p-4 md:p-6 lg:p-12"> <div class="mx-auto p-4 md:p-6 lg:p-12">

View File

@@ -1,4 +1,4 @@
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { A } from "@solidjs/router"; import { A } from "@solidjs/router";
import { createSignal, onMount, onCleanup } from "solid-js"; import { createSignal, onMount, onCleanup } from "solid-js";
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore"; import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
@@ -50,10 +50,9 @@ export default function DownloadsPage() {
return ( return (
<> <>
<Title>Downloads | Michael Freno</Title> <PageHead
<Meta title="Downloads"
name="description" description="Download Life and Lineage, Shapes with Abigail, and Cork for macOS. Available on iOS, Android, and macOS."
content="Download Life and Lineage, Shapes with Abigail, and Cork for macOS. Available on iOS, Android, and macOS."
/> />
<div class="bg-base relative min-h-screen overflow-hidden px-4 pt-[15vh] pb-12 md:px-8"> <div class="bg-base relative min-h-screen overflow-hidden px-4 pt-[15vh] pb-12 md:px-8">

View File

@@ -1,14 +1,13 @@
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { DarkModeToggle } from "~/components/DarkModeToggle"; import { DarkModeToggle } from "~/components/DarkModeToggle";
import { Typewriter } from "~/components/Typewriter"; import { Typewriter } from "~/components/Typewriter";
export default function Home() { export default function Home() {
return ( return (
<> <>
<Title>Home | Michael Freno</Title> <PageHead
<Meta title="Home"
name="description" description="Michael Freno - Software Engineer based in Brooklyn, NY"
content="Michael Freno - Software Engineer based in Brooklyn, NY"
/> />
<main class="flex h-full flex-col gap-8 px-4 py-16 text-xl"> <main class="flex h-full flex-col gap-8 px-4 py-16 text-xl">

View File

@@ -6,7 +6,7 @@ import {
redirect, redirect,
query query
} from "@solidjs/router"; } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { getEvent } from "vinxi/http"; import { getEvent } from "vinxi/http";
import GoogleLogo from "~/components/icons/GoogleLogo"; import GoogleLogo from "~/components/icons/GoogleLogo";
import GitHub from "~/components/icons/GitHub"; import GitHub from "~/components/icons/GitHub";
@@ -18,6 +18,7 @@ import { env } from "~/env/client";
import { VALIDATION_CONFIG, COUNTDOWN_CONFIG } from "~/config"; import { VALIDATION_CONFIG, COUNTDOWN_CONFIG } from "~/config";
import Input from "~/components/ui/Input"; import Input from "~/components/ui/Input";
import PasswordInput from "~/components/ui/PasswordInput"; import PasswordInput from "~/components/ui/PasswordInput";
import { Button } from "~/components/ui/Button";
const checkAuth = query(async () => { const checkAuth = query(async () => {
"use server"; "use server";
@@ -318,10 +319,9 @@ export default function LoginPage() {
return ( return (
<> <>
<Title>Login | Michael Freno</Title> <PageHead
<Meta title="Login"
name="description" description="Sign in to your account or register for a new account to access personalized features and manage your profile."
content="Sign in to your account or register for a new account to access personalized features and manage your profile."
/> />
<div class="flex h-dvh flex-row justify-evenly"> <div class="flex h-dvh flex-row justify-evenly">
<div class="relative pt-12 md:pt-24"> <div class="relative pt-12 md:pt-24">
@@ -480,21 +480,13 @@ export default function LoginPage() {
<Show <Show
when={!register() && !usePassword() && countDown() > 0} when={!register() && !usePassword() && countDown() > 0}
fallback={ fallback={
<button <Button type="submit" loading={loading()} class="w-36">
type="submit"
disabled={loading()}
class={`${
loading()
? "bg-zinc-400"
: "bg-blue hover:brightness-125 active:scale-90"
} flex w-36 justify-center rounded py-3 text-white transition-all duration-300 ease-out`}
>
{register() {register()
? "Sign Up" ? "Sign Up"
: usePassword() : usePassword()
? "Sign In" ? "Sign In"
: "Get Link"} : "Get Link"}
</button> </Button>
} }
> >
<CountdownCircleTimer <CountdownCircleTimer

View File

@@ -1,12 +1,13 @@
import { createSignal, createEffect, Show } from "solid-js"; import { createSignal, createEffect, Show } from "solid-js";
import { A, useNavigate, useSearchParams } from "@solidjs/router"; import { A, useNavigate, useSearchParams } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import CountdownCircleTimer from "~/components/CountdownCircleTimer"; import CountdownCircleTimer from "~/components/CountdownCircleTimer";
import { validatePassword } from "~/lib/validation"; import { validatePassword } from "~/lib/validation";
import { api } from "~/lib/api"; import { api } from "~/lib/api";
import { VALIDATION_CONFIG, COUNTDOWN_CONFIG } from "~/config"; import { VALIDATION_CONFIG, COUNTDOWN_CONFIG } from "~/config";
import PasswordInput from "~/components/ui/PasswordInput"; import PasswordInput from "~/components/ui/PasswordInput";
import PasswordStrengthMeter from "~/components/PasswordStrengthMeter"; import PasswordStrengthMeter from "~/components/PasswordStrengthMeter";
import { Button } from "~/components/ui/Button";
export default function PasswordResetPage() { export default function PasswordResetPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -153,10 +154,9 @@ export default function PasswordResetPage() {
return ( return (
<> <>
<Title>Reset Password | Michael Freno</Title> <PageHead
<Meta title="Reset Password"
name="description" description="Set a new password for your account to regain access to your profile and personalized features."
content="Set a new password for your account to regain access to your profile and personalized features."
/> />
<div> <div>
<div class="pt-24 text-center text-xl font-semibold"> <div class="pt-24 text-center text-xl font-semibold">
@@ -222,17 +222,14 @@ export default function PasswordResetPage() {
<Show <Show
when={countDown()} when={countDown()}
fallback={ fallback={
<button <Button
type="submit" type="submit"
disabled={passwordChangeLoading() || !passwordsMatch()} disabled={!passwordsMatch()}
class={`${ loading={passwordChangeLoading()}
passwordChangeLoading() || !passwordsMatch() class="my-6"
? "cursor-not-allowed bg-zinc-400"
: "bg-blue hover:brightness-125 active:scale-90"
} my-6 flex justify-center rounded px-4 py-2 text-base font-medium transition-all duration-300 ease-out`}
> >
{passwordChangeLoading() ? "Setting..." : "Set New Password"} Set New Password
</button> </Button>
} }
> >
<div class="mx-auto pt-4"> <div class="mx-auto pt-4">

View File

@@ -1,11 +1,12 @@
import { createSignal, createEffect, onCleanup, Show } from "solid-js"; import { createSignal, createEffect, onCleanup, Show } from "solid-js";
import { A, useNavigate } from "@solidjs/router"; import { A, useNavigate } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import CountdownCircleTimer from "~/components/CountdownCircleTimer"; import CountdownCircleTimer from "~/components/CountdownCircleTimer";
import { isValidEmail } from "~/lib/validation"; import { isValidEmail } from "~/lib/validation";
import { getClientCookie } from "~/lib/cookies.client"; import { getClientCookie } from "~/lib/cookies.client";
import { COUNTDOWN_CONFIG } from "~/config"; import { COUNTDOWN_CONFIG } from "~/config";
import Input from "~/components/ui/Input"; import Input from "~/components/ui/Input";
import { Button } from "~/components/ui/Button";
export default function RequestPasswordResetPage() { export default function RequestPasswordResetPage() {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -123,10 +124,9 @@ export default function RequestPasswordResetPage() {
return ( return (
<> <>
<Title>Request Password Reset | Michael Freno</Title> <PageHead
<Meta title="Request Password Reset"
name="description" description="Request a password reset link to regain access to your account. Enter your email to receive reset instructions."
content="Request a password reset link to regain access to your account. Enter your email to receive reset instructions."
/> />
<div class="pt-24 text-center text-xl font-semibold"> <div class="pt-24 text-center text-xl font-semibold">
Password Reset Request Password Reset Request
@@ -152,17 +152,9 @@ export default function RequestPasswordResetPage() {
<Show <Show
when={countDown() > 0} when={countDown() > 0}
fallback={ fallback={
<button <Button type="submit" loading={loading()} class="my-6">
type="submit" Request Password Reset
disabled={loading()} </Button>
class={`${
loading()
? "bg-zinc-400"
: "bg-blue hover:brightness-125 active:scale-90"
} my-6 flex justify-center rounded px-4 py-2 font-medium text-white transition-all duration-300 ease-out`}
>
{loading() ? "Sending..." : "Request Password Reset"}
</button>
} }
> >
<div class="mx-auto pt-4"> <div class="mx-auto pt-4">

View File

@@ -1,15 +1,14 @@
import { A } from "@solidjs/router"; import { A } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import SimpleParallax from "~/components/SimpleParallax"; import SimpleParallax from "~/components/SimpleParallax";
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark"; import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
export default function LifeAndLineageMarketing() { export default function LifeAndLineageMarketing() {
return ( return (
<> <>
<Title>Life and Lineage | Michael Freno</Title> <PageHead
<Meta title="Life and Lineage"
name="description" description="A dark fantasy adventure mobile game. Download Life and Lineage on the App Store and Google Play."
content="A dark fantasy adventure mobile game. Download Life and Lineage on the App Store and Google Play."
/> />
<SimpleParallax> <SimpleParallax>
<div class="flex h-full flex-col items-center justify-center text-white"> <div class="flex h-full flex-col items-center justify-center text-white">

View File

@@ -1,13 +1,12 @@
import { A } from "@solidjs/router"; import { A } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
export default function PrivacyPolicy() { export default function PrivacyPolicy() {
return ( return (
<> <>
<Title>Privacy Policy - Life and Lineage | Michael Freno</Title> <PageHead
<Meta title="Privacy Policy - Life and Lineage"
name="description" description="Privacy policy for Life and Lineage mobile game, outlining data collection, usage, and user rights."
content="Privacy policy for Life and Lineage mobile game, outlining data collection, usage, and user rights."
/> />
<div class="min-h-screen px-[8vw] py-[10vh]"> <div class="min-h-screen px-[8vw] py-[10vh]">
<div class="py-4 text-xl">Life and Lineage&apos;s Privacy Policy</div> <div class="py-4 text-xl">Life and Lineage&apos;s Privacy Policy</div>

View File

@@ -1,13 +1,12 @@
import { A } from "@solidjs/router"; import { A } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
export default function PrivacyPolicy() { export default function PrivacyPolicy() {
return ( return (
<> <>
<Title>Privacy Policy - Shapes with Abigail | Michael Freno</Title> <PageHead
<Meta title="Privacy Policy - Shapes with Abigail"
name="description" description="Privacy policy for Shapes with Abigail app, explaining our commitment to child safety and non-collection of personal data."
content="Privacy policy for Shapes with Abigail app, explaining our commitment to child safety and non-collection of personal data."
/> />
<div class="bg-base"> <div class="bg-base">
<div class="min-h-screen px-[8vw] py-[8vh]"> <div class="min-h-screen px-[8vw] py-[8vh]">

View File

@@ -1,5 +1,5 @@
import { Title, Meta } from "@solidjs/meta";
import { onCleanup, onMount } from "solid-js"; import { onCleanup, onMount } from "solid-js";
import { PageHead } from "~/components/PageHead";
export default function Resume() { export default function Resume() {
let iframeRef: HTMLIFrameElement | undefined; let iframeRef: HTMLIFrameElement | undefined;
@@ -25,10 +25,9 @@ export default function Resume() {
return ( return (
<> <>
<Title>Resume | Michael Freno</Title> <PageHead
<Meta title="Resume"
name="description" description="View Michael Freno's resume - Software Engineer."
content="View Michael Freno's resume - Software Engineer."
/> />
<main class="flex h-screen w-full flex-col"> <main class="flex h-screen w-full flex-col">

View File

@@ -1,5 +1,5 @@
import { createSignal } from "solid-js"; import { createSignal } from "solid-js";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import Input from "~/components/ui/Input"; import Input from "~/components/ui/Input";
import Button from "~/components/ui/Button"; import Button from "~/components/ui/Button";
import { import {
@@ -44,10 +44,9 @@ export default function TestUtilsPage() {
return ( return (
<> <>
<Title>Utility Testing | Michael Freno</Title> <PageHead
<Meta title="Utility Testing"
name="description" description="Testing page for form components and validation utilities."
content="Testing page for form components and validation utilities."
/> />
<main class="min-h-screen bg-gray-100 p-8"> <main class="min-h-screen bg-gray-100 p-8">
<div class="mx-auto max-w-2xl"> <div class="mx-auto max-w-2xl">

View File

@@ -1,6 +1,6 @@
import { createSignal, For, Show } from "solid-js"; import { createSignal, For, Show } from "solid-js";
import { query, createAsync } from "@solidjs/router"; import { query, createAsync } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta"; import { PageHead } from "~/components/PageHead";
import { getRequestEvent } from "solid-js/web"; import { getRequestEvent } from "solid-js/web";
import { api } from "~/lib/api"; import { api } from "~/lib/api";
@@ -914,10 +914,9 @@ export default function TestPage() {
return ( return (
<> <>
<Title>API Testing | Michael Freno</Title> <PageHead
<Meta title="API Testing"
name="description" description="tRPC API testing dashboard for developers to test endpoints and verify functionality."
content="tRPC API testing dashboard for developers to test endpoints and verify functionality."
/> />
<Show <Show
when={authState()?.privilegeLevel === "admin"} when={authState()?.privilegeLevel === "admin"}