From 021a2871c1a15f416732526d45689f7dfd15772c Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Tue, 6 Jan 2026 10:40:15 -0500 Subject: [PATCH] UI Consolidation Cont. --- src/components/PageHead.tsx | 3 + src/components/blog/TextEditor.tsx | 108 +++++++++++++++--- src/components/ui/Button.tsx | 5 +- src/components/ui/PasswordInput.tsx | 10 +- src/routes/401.tsx | 9 +- src/routes/[...404].tsx | 9 +- src/routes/account.tsx | 92 ++++++--------- src/routes/analytics.tsx | 7 +- src/routes/blog/[title]/index.tsx | 11 +- src/routes/blog/create/index.tsx | 9 +- src/routes/blog/edit/[id].tsx | 11 +- src/routes/blog/index.tsx | 7 +- src/routes/contact.tsx | 22 +--- src/routes/deletion/life-and-lineage.tsx | 9 +- src/routes/downloads.tsx | 9 +- src/routes/index.tsx | 9 +- src/routes/login/index.tsx | 22 ++-- src/routes/login/password-reset.tsx | 25 ++-- src/routes/login/request-password-reset.tsx | 24 ++-- src/routes/marketing/life-and-lineage.tsx | 9 +- .../privacy-policy/life-and-lineage.tsx | 9 +- .../privacy-policy/shapes-with-abigail.tsx | 9 +- src/routes/resume.tsx | 9 +- src/routes/test-utils.tsx | 9 +- src/routes/test.tsx | 9 +- 25 files changed, 235 insertions(+), 220 deletions(-) diff --git a/src/components/PageHead.tsx b/src/components/PageHead.tsx index d6fd5ac..06b85bf 100644 --- a/src/components/PageHead.tsx +++ b/src/components/PageHead.tsx @@ -47,3 +47,6 @@ export default function PageHead(props: PageHeadProps) { ); } + +// Named export for consistency +export { PageHead }; diff --git a/src/components/blog/TextEditor.tsx b/src/components/blog/TextEditor.tsx index 497f814..a683242 100644 --- a/src/components/blog/TextEditor.tsx +++ b/src/components/blog/TextEditor.tsx @@ -250,6 +250,9 @@ declare module "@tiptap/core" { iframe: { setIframe: (options: { src: string }) => ReturnType; }; + video: { + setVideo: (options: { src: string }) => ReturnType; + }; } } @@ -298,24 +301,76 @@ const IframeEmbed = Node.create({ return { setIframe: (options: { src: string }) => - ({ tr, dispatch, editor }) => { + ({ tr, dispatch }) => { 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) { - const videoHTML = ``; - editor.commands.insertContent(videoHTML); - } - return true; + if (dispatch) { + tr.replaceRangeWith(selection.from, selection.to, node); } - // For non-video URLs, create iframe as normal - const node = this.type.create(options); + return true; + } + }; + } +}); + +interface VideoOptions { + HTMLAttributes: { + [key: string]: any; + }; +} + +const Video = Node.create({ + 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) { tr.replaceRangeWith(selection.from, selection.to, node); @@ -1352,6 +1407,7 @@ export default function TextEditor(props: TextEditorProps) { }), Image, IframeEmbed, + Video, TaskList, TaskItem.configure({ nested: true, @@ -2454,12 +2510,22 @@ export default function TextEditor(props: TextEditorProps) { const instance = editor(); if (!instance) return; - const url = window.prompt("URL"); + const url = window.prompt("Embed URL (YouTube, etc.)"); if (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 instance = editor(); if (!instance) return; @@ -3790,13 +3856,21 @@ export default function TextEditor(props: TextEditorProps) { > 🖼 Image + ); } + +// Named export for consistency +export { Button }; diff --git a/src/components/ui/PasswordInput.tsx b/src/components/ui/PasswordInput.tsx index f2eec6a..4e1cd9b 100644 --- a/src/components/ui/PasswordInput.tsx +++ b/src/components/ui/PasswordInput.tsx @@ -24,7 +24,7 @@ export default function PasswordInput(props: PasswordInputProps) { ); return ( - <> +
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"} > {local.showStrength && local.passwordValue !== undefined && ( -
- -
+ )} - +
); } diff --git a/src/routes/401.tsx b/src/routes/401.tsx index e815be7..5ec1f0c 100644 --- a/src/routes/401.tsx +++ b/src/routes/401.tsx @@ -1,4 +1,4 @@ -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import { HttpStatusCode } from "@solidjs/start"; import { useNavigate } from "@solidjs/router"; import { createEffect, createSignal, For } from "solid-js"; @@ -51,10 +51,9 @@ export default function Page_401() { return ( <> - 401 Unauthorized | Michael Freno -
diff --git a/src/routes/[...404].tsx b/src/routes/[...404].tsx index bd94327..493196a 100644 --- a/src/routes/[...404].tsx +++ b/src/routes/[...404].tsx @@ -1,4 +1,4 @@ -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import { HttpStatusCode } from "@solidjs/start"; import { useNavigate, useLocation } from "@solidjs/router"; import { createSignal, Show } from "solid-js"; @@ -101,10 +101,9 @@ export default function NotFound() { {/*@ts-ignore (intentional crash)*/} - 404 Not Found | Michael Freno - - Account | Michael Freno -
@@ -683,23 +682,17 @@ export default function AccountPage() {
- + Submit +
@@ -740,19 +733,13 @@ export default function AccountPage() { containerClass="input-group mx-4" />
- + Submit +
@@ -838,17 +825,14 @@ export default function AccountPage() {
- + Set +
@@ -871,18 +855,15 @@ export default function AccountPage() { {/* Sign Out Section */}
- + Sign Out +

@@ -938,19 +919,14 @@ export default function AccountPage() { />
- + Delete Account +
diff --git a/src/routes/analytics.tsx b/src/routes/analytics.tsx index 0e683d6..84b1f7f 100644 --- a/src/routes/analytics.tsx +++ b/src/routes/analytics.tsx @@ -1,5 +1,5 @@ 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 { getEvent } from "vinxi/http"; import { api } from "~/lib/api"; @@ -128,7 +128,10 @@ export default function AnalyticsPage() { return ( <> - Analytics Dashboard - Admin +
diff --git a/src/routes/blog/[title]/index.tsx b/src/routes/blog/[title]/index.tsx index 9f6fca3..6d120b2 100644 --- a/src/routes/blog/[title]/index.tsx +++ b/src/routes/blog/[title]/index.tsx @@ -6,7 +6,7 @@ import { query, useSearchParams } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import { createAsync } from "@solidjs/router"; import { getRequestEvent } from "solid-js/web"; import SessionDependantLike from "~/components/blog/SessionDependantLike"; @@ -316,12 +316,9 @@ export default function PostPage() { return ( <> - - {p().title.replaceAll("_", " ")} | Michael Freno - - - Create Blog Post | Michael Freno - }> diff --git a/src/routes/blog/edit/[id].tsx b/src/routes/blog/edit/[id].tsx index 01832ff..13e9311 100644 --- a/src/routes/blog/edit/[id].tsx +++ b/src/routes/blog/edit/[id].tsx @@ -1,6 +1,6 @@ import { Show, lazy } from "solid-js"; -import { useParams, query } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { useParams, query, redirect } from "@solidjs/router"; +import { PageHead } from "~/components/PageHead"; import { createAsync } from "@solidjs/router"; import { getEvent } from "vinxi/http"; import "../post.css"; @@ -66,10 +66,9 @@ export default function EditPost() { return ( <> - Edit Post | Michael Freno - - Blog | Michael Freno +
}> diff --git a/src/routes/contact.tsx b/src/routes/contact.tsx index 313face..9f94009 100644 --- a/src/routes/contact.tsx +++ b/src/routes/contact.tsx @@ -6,15 +6,16 @@ import { query, createAsync } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; import { A } from "@solidjs/router"; import { action, redirect } from "@solidjs/router"; +import { PageHead } from "~/components/PageHead"; import { api } from "~/lib/api"; import { getClientCookie, setClientCookie } from "~/lib/cookies.client"; import CountdownCircleTimer from "~/components/CountdownCircleTimer"; import LoadingSpinner from "~/components/LoadingSpinner"; import RevealDropDown from "~/components/RevealDropDown"; import Input from "~/components/ui/Input"; +import { Button } from "~/components/ui/Button"; import type { UserProfile } from "~/types/user"; import { getCookie, setCookie } from "vinxi/http"; import { z } from "zod"; @@ -360,8 +361,7 @@ export default function ContactPage() { return ( <> - Contact | Michael Freno - +
@@ -422,19 +422,9 @@ export default function ContactPage() { countDown() > 0 || (contactData()?.remainingTime ?? 0) > 0 } fallback={ - + } > - Account Deletion - Life and Lineage | Michael Freno -
diff --git a/src/routes/downloads.tsx b/src/routes/downloads.tsx index b97ba6e..2760f42 100644 --- a/src/routes/downloads.tsx +++ b/src/routes/downloads.tsx @@ -1,4 +1,4 @@ -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import { A } from "@solidjs/router"; import { createSignal, onMount, onCleanup } from "solid-js"; import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore"; @@ -50,10 +50,9 @@ export default function DownloadsPage() { return ( <> - Downloads | Michael Freno -
diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 6cc0852..62d27fd 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,14 +1,13 @@ -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import { DarkModeToggle } from "~/components/DarkModeToggle"; import { Typewriter } from "~/components/Typewriter"; export default function Home() { return ( <> - Home | Michael Freno -
diff --git a/src/routes/login/index.tsx b/src/routes/login/index.tsx index a0b0043..f75a69c 100644 --- a/src/routes/login/index.tsx +++ b/src/routes/login/index.tsx @@ -6,7 +6,7 @@ import { redirect, query } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import { getEvent } from "vinxi/http"; import GoogleLogo from "~/components/icons/GoogleLogo"; import GitHub from "~/components/icons/GitHub"; @@ -18,6 +18,7 @@ import { env } from "~/env/client"; import { VALIDATION_CONFIG, COUNTDOWN_CONFIG } from "~/config"; import Input from "~/components/ui/Input"; import PasswordInput from "~/components/ui/PasswordInput"; +import { Button } from "~/components/ui/Button"; const checkAuth = query(async () => { "use server"; @@ -318,10 +319,9 @@ export default function LoginPage() { return ( <> - Login | Michael Freno -
@@ -480,21 +480,13 @@ export default function LoginPage() { 0} fallback={ - + } > - Reset Password | Michael Freno -
@@ -222,17 +222,14 @@ export default function PasswordResetPage() { - {passwordChangeLoading() ? "Setting..." : "Set New Password"} - + Set New Password + } >
diff --git a/src/routes/login/request-password-reset.tsx b/src/routes/login/request-password-reset.tsx index 4498604..26bdaa2 100644 --- a/src/routes/login/request-password-reset.tsx +++ b/src/routes/login/request-password-reset.tsx @@ -1,11 +1,12 @@ import { createSignal, createEffect, onCleanup, Show } from "solid-js"; import { A, useNavigate } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import CountdownCircleTimer from "~/components/CountdownCircleTimer"; import { isValidEmail } from "~/lib/validation"; import { getClientCookie } from "~/lib/cookies.client"; import { COUNTDOWN_CONFIG } from "~/config"; import Input from "~/components/ui/Input"; +import { Button } from "~/components/ui/Button"; export default function RequestPasswordResetPage() { const navigate = useNavigate(); @@ -123,10 +124,9 @@ export default function RequestPasswordResetPage() { return ( <> - Request Password Reset | Michael Freno -
Password Reset Request @@ -152,17 +152,9 @@ export default function RequestPasswordResetPage() { 0} fallback={ - + } >
diff --git a/src/routes/marketing/life-and-lineage.tsx b/src/routes/marketing/life-and-lineage.tsx index 9c6bb99..dd6be47 100644 --- a/src/routes/marketing/life-and-lineage.tsx +++ b/src/routes/marketing/life-and-lineage.tsx @@ -1,15 +1,14 @@ import { A } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import SimpleParallax from "~/components/SimpleParallax"; import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark"; export default function LifeAndLineageMarketing() { return ( <> - Life and Lineage | Michael Freno -
diff --git a/src/routes/privacy-policy/life-and-lineage.tsx b/src/routes/privacy-policy/life-and-lineage.tsx index ce1eee9..6fb7e60 100644 --- a/src/routes/privacy-policy/life-and-lineage.tsx +++ b/src/routes/privacy-policy/life-and-lineage.tsx @@ -1,13 +1,12 @@ import { A } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; export default function PrivacyPolicy() { return ( <> - Privacy Policy - Life and Lineage | Michael Freno -
Life and Lineage's Privacy Policy
diff --git a/src/routes/privacy-policy/shapes-with-abigail.tsx b/src/routes/privacy-policy/shapes-with-abigail.tsx index f7d8241..9143ba8 100644 --- a/src/routes/privacy-policy/shapes-with-abigail.tsx +++ b/src/routes/privacy-policy/shapes-with-abigail.tsx @@ -1,13 +1,12 @@ import { A } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; export default function PrivacyPolicy() { return ( <> - Privacy Policy - Shapes with Abigail | Michael Freno -
diff --git a/src/routes/resume.tsx b/src/routes/resume.tsx index 73a3f48..f7958a3 100644 --- a/src/routes/resume.tsx +++ b/src/routes/resume.tsx @@ -1,5 +1,5 @@ -import { Title, Meta } from "@solidjs/meta"; import { onCleanup, onMount } from "solid-js"; +import { PageHead } from "~/components/PageHead"; export default function Resume() { let iframeRef: HTMLIFrameElement | undefined; @@ -25,10 +25,9 @@ export default function Resume() { return ( <> - Resume | Michael Freno -
diff --git a/src/routes/test-utils.tsx b/src/routes/test-utils.tsx index 74b6106..9c7c1d6 100644 --- a/src/routes/test-utils.tsx +++ b/src/routes/test-utils.tsx @@ -1,5 +1,5 @@ import { createSignal } from "solid-js"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import Input from "~/components/ui/Input"; import Button from "~/components/ui/Button"; import { @@ -44,10 +44,9 @@ export default function TestUtilsPage() { return ( <> - Utility Testing | Michael Freno -
diff --git a/src/routes/test.tsx b/src/routes/test.tsx index 44ce00c..5edb758 100644 --- a/src/routes/test.tsx +++ b/src/routes/test.tsx @@ -1,6 +1,6 @@ import { createSignal, For, Show } from "solid-js"; import { query, createAsync } from "@solidjs/router"; -import { Title, Meta } from "@solidjs/meta"; +import { PageHead } from "~/components/PageHead"; import { getRequestEvent } from "solid-js/web"; import { api } from "~/lib/api"; @@ -914,10 +914,9 @@ export default function TestPage() { return ( <> - API Testing | Michael Freno -