metadata and titles

This commit is contained in:
Michael Freno
2025-12-19 17:05:24 -05:00
parent 69c95c3060
commit e24e53f8d7
19 changed files with 1936 additions and 1750 deletions

View File

@@ -1,4 +1,4 @@
import { Title } from "@solidjs/meta";
import { Title, Meta } from "@solidjs/meta";
import { HttpStatusCode } from "@solidjs/start";
import { useNavigate } from "@solidjs/router";
import { createEffect, createSignal, For } from "solid-js";
@@ -47,7 +47,11 @@ export default function Page_401() {
return (
<>
<Title>401 - Unauthorized</Title>
<Title>401 Unauthorized | Michael Freno</Title>
<Meta
name="description"
content="401 - Unauthorized access. Please log in to access this page."
/>
<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">
{/* Animated particle background */}

View File

@@ -1,4 +1,4 @@
import { Title } from "@solidjs/meta";
import { Title, Meta } from "@solidjs/meta";
import { HttpStatusCode } from "@solidjs/start";
import { useNavigate } from "@solidjs/router";
import { createEffect, createSignal, For } from "solid-js";
@@ -43,7 +43,11 @@ export default function NotFound() {
return (
<>
<Title>404 - Not Found</Title>
<Title>404 Not Found | Michael Freno</Title>
<Meta
name="description"
content="404 - Page not found. The page you're looking for doesn't exist."
/>
<HttpStatusCode code={404} />
<div class="relative min-h-screen w-full overflow-hidden bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 dark:from-black dark:via-slate-900 dark:to-black">
{/* Animated particle background */}

View File

@@ -1,10 +1,17 @@
import { Title } from "@solidjs/meta";
import { Title, Meta } from "@solidjs/meta";
export default function About() {
return (
<>
<Title>About | Michael Freno</Title>
<Meta
name="description"
content="Learn more about Michael Freno - Software Engineer, game developer, and open source contributor."
/>
<main>
<Title>About</Title>
<h1>About</h1>
</main>
</>
);
}

View File

@@ -1,4 +1,5 @@
import { createSignal, createEffect, Show, onMount } from "solid-js";
import { Title, Meta } from "@solidjs/meta";
import { useNavigate, cache, redirect } from "@solidjs/router";
import { getEvent } from "vinxi/http";
import Eye from "~/components/icons/Eye";
@@ -453,6 +454,13 @@ export default function AccountPage() {
};
return (
<>
<Title>Account | Michael Freno</Title>
<Meta
name="description"
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="pt-24">
<Show
@@ -507,7 +515,9 @@ export default function AccountPage() {
: "bg-blue hover:brightness-125 active:scale-90"
} mt-2 flex w-full justify-center rounded px-4 py-2 text-base transition-all duration-300 ease-out`}
>
{profileImageSetLoading() ? "Uploading..." : "Set Image"}
{profileImageSetLoading()
? "Uploading..."
: "Set Image"}
</button>
</form>
<Show when={showImageSuccess()}>
@@ -618,7 +628,8 @@ export default function AccountPage() {
/>
<span class="bar"></span>
<label class="underlinedInputLabel">
Set {currentUser().displayName ? "New " : ""}Display Name
Set {currentUser().displayName ? "New " : ""}Display
Name
</label>
</div>
<div class="flex justify-end">
@@ -631,7 +642,9 @@ export default function AccountPage() {
: "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() ? "Submitting..." : "Submit"}
{displayNameButtonLoading()
? "Submitting..."
: "Submit"}
</button>
</div>
<Show when={showDisplayNameSuccess()}>
@@ -673,7 +686,10 @@ export default function AccountPage() {
}
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125"
>
<Show when={showOldPasswordInput()} fallback={<Eye />}>
<Show
when={showOldPasswordInput()}
fallback={<Eye />}
>
<EyeSlash />
</Show>
</button>
@@ -695,7 +711,9 @@ export default function AccountPage() {
<label class="underlinedInputLabel">New Password</label>
<button
type="button"
onClick={() => setShowPasswordInput(!showPasswordInput())}
onClick={() =>
setShowPasswordInput(!showPasswordInput())
}
class="text-subtext0 absolute top-2 right-0 transition-all hover:brightness-125"
>
<Show when={showPasswordInput()} fallback={<Eye />}>
@@ -837,5 +855,6 @@ export default function AccountPage() {
</Show>
</div>
</div>
</>
);
}

View File

@@ -1,6 +1,6 @@
import { Show, Suspense, For } from "solid-js";
import { useParams, A, Navigate, query } from "@solidjs/router";
import { Title } from "@solidjs/meta";
import { Title, Meta } from "@solidjs/meta";
import { createAsync } from "@solidjs/router";
import { getRequestEvent } from "solid-js/web";
import SessionDependantLike from "~/components/blog/SessionDependantLike";
@@ -159,6 +159,13 @@ export default function PostPage() {
<Title>
{p().title.replaceAll("_", " ")} | Michael Freno
</Title>
<Meta
name="description"
content={
p().subtitle ||
`Read ${p().title.replaceAll("_", " ")} by Michael Freno on the freno.me blog.`
}
/>
<div class="relative overflow-x-hidden">
{/* Fixed banner image background */}

View File

@@ -1,6 +1,6 @@
import { Show, createSignal, createEffect, onCleanup } from "solid-js";
import { useNavigate, query } from "@solidjs/router";
import { Title } from "@solidjs/meta";
import { Title, Meta } from "@solidjs/meta";
import { createAsync } from "@solidjs/router";
import { getRequestEvent } from "solid-js/web";
import { api } from "~/lib/api";
@@ -195,6 +195,10 @@ export default function CreatePost() {
return (
<>
<Title>Create Blog Post | Michael Freno</Title>
<Meta
name="description"
content="Create a new blog post with rich text editing, image uploads, and tag management."
/>
<Show
when={authState()?.privilegeLevel === "admin"}

View File

@@ -1,6 +1,6 @@
import { Show, createSignal, createEffect, onCleanup } from "solid-js";
import { useParams, useNavigate, query } from "@solidjs/router";
import { Title } from "@solidjs/meta";
import { Title, Meta } from "@solidjs/meta";
import { createAsync } from "@solidjs/router";
import { getRequestEvent } from "solid-js/web";
import { api } from "~/lib/api";
@@ -241,6 +241,10 @@ export default function EditPost() {
return (
<>
<Title>Edit Post | Michael Freno</Title>
<Meta
name="description"
content="Edit your blog post with rich text editing, image management, and tag updates."
/>
<Show
when={data()?.privilegeLevel === "admin"}

View File

@@ -1,23 +1,32 @@
import { Title, Meta } from "@solidjs/meta";
import DeletionForm from "~/components/DeletionForm";
export default function LifeAndLinageDeletionForm() {
return (
<>
<Title>Account Deletion - Life and Lineage | Michael Freno</Title>
<Meta
name="description"
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="mx-auto p-4 md:p-6 lg:p-12">
<div class="w-full justify-center text-text">
<div class="text-text w-full justify-center">
<div class="text-xl">
<em>What will happen</em>:
</div>
Once you send, if a match to the email provided is found in our
system, a 24hr grace period is started where you can request a
cancellation of the account deletion. Once the grace period ends, the
account's entry in our central database will be completely removed,
and your individual database storing your remote saves will also be
deleted. No data related to the account is retained in any way.
cancellation of the account deletion. Once the grace period ends,
the account's entry in our central database will be completely
removed, and your individual database storing your remote saves will
also be deleted. No data related to the account is retained in any
way.
</div>
<DeletionForm />
</div>
</div>
</>
);
}

View File

@@ -1,3 +1,4 @@
import { Title, Meta } from "@solidjs/meta";
import { A } from "@solidjs/router";
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
import GitHub from "~/components/icons/GitHub";
@@ -21,6 +22,13 @@ export default function DownloadsPage() {
};
return (
<>
<Title>Downloads | Michael Freno</Title>
<Meta
name="description"
content="Download Life and Lineage, Shapes with Abigail, and Cork for macOS. Available on iOS, Android, and macOS."
/>
<div class="bg-base min-h-screen pt-[15vh] pb-12">
<div class="text-text text-center text-3xl tracking-widest">
Downloads
@@ -163,5 +171,6 @@ export default function DownloadsPage() {
</ul>
</div>
</div>
</>
);
}

View File

@@ -1,8 +1,16 @@
import { Title, Meta } from "@solidjs/meta";
import DownloadOnAppStore from "~/components/icons/DownloadOnAppStore";
import { Typewriter } from "~/components/Typewriter";
export default function Home() {
return (
<>
<Title>Home | Michael Freno</Title>
<Meta
name="description"
content="Michael Freno - Software Engineer based in Brooklyn, NY. Passionate about dev tooling, game development, and open source software."
/>
<main class="flex h-full flex-col gap-8 p-4 text-xl">
<div class="flex-1">
<Typewriter speed={30} keepAlive={2000}>
@@ -151,5 +159,6 @@ export default function Home() {
</Typewriter>
</div>
</main>
</>
);
}

View File

@@ -6,6 +6,7 @@ import {
cache,
redirect
} from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
import { getEvent } from "vinxi/http";
import GoogleLogo from "~/components/icons/GoogleLogo";
import GitHub from "~/components/icons/GitHub";
@@ -320,6 +321,12 @@ export default function LoginPage() {
};
return (
<>
<Title>Login | Michael Freno</Title>
<Meta
name="description"
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">
{/* Logo section - hidden on mobile */}
{/* <div class="hidden md:flex">
@@ -639,5 +646,6 @@ export default function LoginPage() {
</div>
</div>
</div>
</>
);
}

View File

@@ -1,5 +1,6 @@
import { createSignal, createEffect, Show } from "solid-js";
import { A, useNavigate, useSearchParams } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
import Eye from "~/components/icons/Eye";
import EyeSlash from "~/components/icons/EyeSlash";
@@ -13,8 +14,10 @@ export default function PasswordResetPage() {
const [passwordBlurred, setPasswordBlurred] = createSignal(false);
const [passwordChangeLoading, setPasswordChangeLoading] = createSignal(false);
const [passwordsMatch, setPasswordsMatch] = createSignal(false);
const [showPasswordLengthWarning, setShowPasswordLengthWarning] = createSignal(false);
const [passwordLengthSufficient, setPasswordLengthSufficient] = createSignal(false);
const [showPasswordLengthWarning, setShowPasswordLengthWarning] =
createSignal(false);
const [passwordLengthSufficient, setPasswordLengthSufficient] =
createSignal(false);
const [showRequestNewEmail, setShowRequestNewEmail] = createSignal(false);
const [countDown, setCountDown] = createSignal(false);
const [error, setError] = createSignal("");
@@ -70,8 +73,8 @@ export default function PasswordResetPage() {
body: JSON.stringify({
token: token,
newPassword,
newPasswordConfirmation: newPasswordConf,
}),
newPasswordConfirmation: newPasswordConf
})
});
const result = await response.json();
@@ -158,14 +161,26 @@ export default function PasswordResetPage() {
}
return (
<div class="timer text-center">
<div class="text-sm text-slate-700 dark:text-slate-300">Change Successful!</div>
<div class="value py-1 text-3xl text-blue-500 dark:text-blue-400">{timeRemaining}</div>
<div class="text-sm text-slate-700 dark:text-slate-300">Redirecting...</div>
<div class="text-sm text-slate-700 dark:text-slate-300">
Change Successful!
</div>
<div class="value py-1 text-3xl text-blue-500 dark:text-blue-400">
{timeRemaining}
</div>
<div class="text-sm text-slate-700 dark:text-slate-300">
Redirecting...
</div>
</div>
);
};
return (
<>
<Title>Reset Password | Michael Freno</Title>
<Meta
name="description"
content="Set a new password for your account to regain access to your profile and personalized features."
/>
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
<div class="pt-24 text-center text-xl font-semibold text-slate-800 dark:text-slate-100">
Set New Password
@@ -175,9 +190,9 @@ export default function PasswordResetPage() {
onSubmit={(e) => setNewPasswordTrigger(e)}
class="mt-4 flex w-full justify-center"
>
<div class="flex flex-col justify-center max-w-md w-full px-4">
<div class="flex w-full max-w-md flex-col justify-center px-4">
{/* New Password Input */}
<div class="input-group mx-4 relative">
<div class="input-group relative mx-4">
<input
ref={newPasswordRef}
name="newPassword"
@@ -194,7 +209,7 @@ export default function PasswordResetPage() {
<button
type="button"
onClick={() => setShowPasswordInput(!showPasswordInput())}
class="absolute right-0 top-2 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
class="absolute top-2 right-0 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
>
<Show when={showPasswordInput()} fallback={<Eye />}>
<EyeSlash />
@@ -205,14 +220,14 @@ export default function PasswordResetPage() {
{/* Password Length Warning */}
<div
class={`${
showPasswordLengthWarning() ? "" : "select-none opacity-0"
} transition-opacity text-center text-red-500 text-sm duration-200 ease-in-out mt-2`}
showPasswordLengthWarning() ? "" : "opacity-0 select-none"
} mt-2 text-center text-sm text-red-500 transition-opacity duration-200 ease-in-out`}
>
Password too short! Min Length: 8
</div>
{/* Password Confirmation Input */}
<div class="input-group mx-4 mt-6 relative">
<div class="input-group relative mx-4 mt-6">
<input
ref={newPasswordConfRef}
name="newPasswordConf"
@@ -227,8 +242,10 @@ export default function PasswordResetPage() {
<label class="underlinedInputLabel">Password Confirmation</label>
<button
type="button"
onClick={() => setShowPasswordConfInput(!showPasswordConfInput())}
class="absolute right-0 top-2 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
onClick={() =>
setShowPasswordConfInput(!showPasswordConfInput())
}
class="absolute top-2 right-0 text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200"
>
<Show when={showPasswordConfInput()} fallback={<Eye />}>
<EyeSlash />
@@ -244,8 +261,8 @@ export default function PasswordResetPage() {
newPasswordConfRef &&
newPasswordConfRef.value.length >= 6
? ""
: "select-none opacity-0"
} transition-opacity text-center text-red-500 text-sm duration-200 ease-in-out mt-2`}
: "opacity-0 select-none"
} mt-2 text-center text-sm text-red-500 transition-opacity duration-200 ease-in-out`}
>
Passwords do not match!
</div>
@@ -259,9 +276,9 @@ export default function PasswordResetPage() {
disabled={passwordChangeLoading() || !passwordsMatch()}
class={`${
passwordChangeLoading() || !passwordsMatch()
? "bg-zinc-400 cursor-not-allowed"
? "cursor-not-allowed bg-zinc-400"
: "bg-blue-400 hover:bg-blue-500 active:scale-90 dark:bg-blue-600 dark:hover:bg-blue-700"
} flex justify-center rounded transition-all duration-300 ease-out my-6 px-4 py-2 text-white font-medium`}
} my-6 flex justify-center rounded px-4 py-2 font-medium text-white transition-all duration-300 ease-out`}
>
{passwordChangeLoading() ? "Setting..." : "Set New Password"}
</button>
@@ -285,16 +302,16 @@ export default function PasswordResetPage() {
{/* Error Message */}
<Show when={error() && !showRequestNewEmail()}>
<div class="flex justify-center mt-4">
<div class="text-red-500 text-sm italic">{error()}</div>
<div class="mt-4 flex justify-center">
<div class="text-sm text-red-500 italic">{error()}</div>
</div>
</Show>
{/* Token Expired Message */}
<div
class={`${
showRequestNewEmail() ? "" : "select-none opacity-0"
} text-red-500 italic transition-opacity flex justify-center duration-300 ease-in-out px-4`}
showRequestNewEmail() ? "" : "opacity-0 select-none"
} flex justify-center px-4 text-red-500 italic transition-opacity duration-300 ease-in-out`}
>
Token has expired, request a new one{" "}
<A
@@ -307,15 +324,16 @@ export default function PasswordResetPage() {
{/* Back to Login Link */}
<Show when={!countDown()}>
<div class="flex justify-center mt-6">
<div class="mt-6 flex justify-center">
<A
href="/login"
class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 underline underline-offset-4 transition-colors"
class="text-blue-500 underline underline-offset-4 transition-colors hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
Back to Login
</A>
</div>
</Show>
</div>
</>
);
}

View File

@@ -1,5 +1,6 @@
import { createSignal, createEffect, onCleanup, Show } from "solid-js";
import { A, useNavigate } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
import { isValidEmail } from "~/lib/validation";
import { getClientCookie } from "~/lib/cookies.client";
@@ -37,7 +38,10 @@ export default function RequestPasswordResetPage() {
createEffect(() => {
const timer = getClientCookie("passwordResetRequested");
if (timer) {
timerInterval = setInterval(() => calcRemainder(timer), 1000) as unknown as number;
timerInterval = setInterval(
() => calcRemainder(timer),
1000
) as unknown as number;
onCleanup(() => {
if (timerInterval) {
clearInterval(timerInterval);
@@ -71,7 +75,7 @@ export default function RequestPasswordResetPage() {
const response = await fetch("/api/trpc/auth.requestPasswordReset", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
body: JSON.stringify({ email })
});
const result = await response.json();
@@ -115,6 +119,12 @@ export default function RequestPasswordResetPage() {
};
return (
<>
<Title>Request Password Reset | Michael Freno</Title>
<Meta
name="description"
content="Request a password reset link to regain access to your account. Enter your email to receive reset instructions."
/>
<div class="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
<div class="pt-24 text-center text-xl font-semibold text-slate-800 dark:text-slate-100">
Password Reset Request
@@ -151,7 +161,7 @@ export default function RequestPasswordResetPage() {
loading()
? "bg-zinc-400"
: "bg-blue-400 hover:bg-blue-500 active:scale-90 dark:bg-blue-600 dark:hover:bg-blue-700"
} flex justify-center rounded transition-all duration-300 ease-out my-6 px-4 py-2 text-white font-medium`}
} 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>
@@ -177,28 +187,29 @@ export default function RequestPasswordResetPage() {
{/* Success Message */}
<div
class={`${
showSuccessMessage() ? "" : "select-none opacity-0"
} text-green-500 italic transition-opacity flex justify-center duration-300 ease-in-out`}
showSuccessMessage() ? "" : "opacity-0 select-none"
} flex justify-center text-green-500 italic transition-opacity duration-300 ease-in-out`}
>
If email exists, you will receive an email shortly!
</div>
{/* Error Message */}
<Show when={error()}>
<div class="flex justify-center mt-4">
<div class="text-red-500 text-sm italic">{error()}</div>
<div class="mt-4 flex justify-center">
<div class="text-sm text-red-500 italic">{error()}</div>
</div>
</Show>
{/* Back to Login Link */}
<div class="flex justify-center mt-6">
<div class="mt-6 flex justify-center">
<A
href="/login"
class="text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300 underline underline-offset-4 transition-colors"
class="text-blue-500 underline underline-offset-4 transition-colors hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-300"
>
Back to Login
</A>
</div>
</div>
</>
);
}

View File

@@ -1,11 +1,18 @@
import { A } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
import SimpleParallax from "~/components/SimpleParallax";
import DownloadOnAppStoreDark from "~/components/icons/DownloadOnAppStoreDark";
export default function LifeAndLineageMarketing() {
return (
<>
<Title>Life and Lineage | Michael Freno</Title>
<Meta
name="description"
content="A dark fantasy adventure mobile game. Download Life and Lineage on the App Store and Google Play."
/>
<SimpleParallax>
<div class="flex flex-col items-center justify-center h-full text-white">
<div class="flex h-full flex-col items-center justify-center text-white">
<div>
<img
src="/LineageIcon.png"
@@ -15,10 +22,8 @@ export default function LifeAndLineageMarketing() {
class="object-cover object-center"
/>
</div>
<h1 class="text-5xl font-bold mb-4 text-center">
Life and Lineage
</h1>
<p class="text-xl mb-8">A dark fantasy adventure</p>
<h1 class="mb-4 text-center text-5xl font-bold">Life and Lineage</h1>
<p class="mb-8 text-xl">A dark fantasy adventure</p>
<div class="flex space-x-4">
<a
class="my-auto transition-all duration-200 ease-out active:scale-95"
@@ -42,5 +47,6 @@ export default function LifeAndLineageMarketing() {
</div>
</div>
</SimpleParallax>
</>
);
}

View File

@@ -1,7 +1,14 @@
import { A } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
export default function PrivacyPolicy() {
return (
<>
<Title>Privacy Policy - Life and Lineage | Michael Freno</Title>
<Meta
name="description"
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="py-4 text-xl">Life and Lineage&apos;s Privacy Policy</div>
<div class="py-2">Last Updated: October 22, 2024</div>
@@ -18,13 +25,13 @@ export default function PrivacyPolicy() {
</div>
<div class="pl-4">
<div class="pb-2">
<div class="-ml-6">(a) Collection of Personal Data:</div> Life and
Lineage collects and stores personal data only if users opt to use
the remote saving feature. The information collected includes
email address, and if using an OAuth provider - first name, and
last name. This information is used solely for the purpose of
providing and managing the remote saving feature. It is and never
will be shared with a third party.
<div class="-ml-6">(a) Collection of Personal Data:</div> Life
and Lineage collects and stores personal data only if users opt
to use the remote saving feature. The information collected
includes email address, and if using an OAuth provider - first
name, and last name. This information is used solely for the
purpose of providing and managing the remote saving feature. It
is and never will be shared with a third party.
</div>
<div class="pb-2">
<div class="-ml-6">(b) Data Removal:</div> Users can request the
@@ -59,11 +66,11 @@ export default function PrivacyPolicy() {
<span class="-ml-4 pr-2">3.</span> Security
</div>
<div class="pb-2 pl-4">
<div class="-ml-6">(a) Data Protection:</div>Life and Lineage takes
appropriate measures to protect the personal information of users
who opt for the remote saving feature. We implement
industry-standard security protocols to prevent unauthorized access,
disclosure, alteration, or destruction of user data.
<div class="-ml-6">(a) Data Protection:</div>Life and Lineage
takes appropriate measures to protect the personal information of
users who opt for the remote saving feature. We implement
industry-standard security protocols to prevent unauthorized
access, disclosure, alteration, or destruction of user data.
</div>
</div>
@@ -85,8 +92,8 @@ export default function PrivacyPolicy() {
</div>
<div class="pb-2 pl-4">
<div class="-ml-6">(a) Reaching Out:</div> If there are any
questions or comments regarding this privacy policy, you can contact
us{" "}
questions or comments regarding this privacy policy, you can
contact us{" "}
<A href="/contact" class="text-blue hover-underline-animation">
here
</A>
@@ -95,5 +102,6 @@ export default function PrivacyPolicy() {
</div>
</ol>
</div>
</>
);
}

View File

@@ -1,7 +1,14 @@
import { A } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
export default function PrivacyPolicy() {
return (
<>
<Title>Privacy Policy - Shapes with Abigail | Michael Freno</Title>
<Meta
name="description"
content="Privacy policy for Shapes with Abigail app, explaining our commitment to child safety and non-collection of personal data."
/>
<div class="bg-zinc-100 dark:bg-zinc-900">
<div class="min-h-screen px-[8vw] py-[8vh]">
<div class="py-4 text-xl">
@@ -24,9 +31,7 @@ export default function PrivacyPolicy() {
</div>
<div class="pl-4">
<div class="pb-2">
<div class="-ml-6">
(a) Non-Collection of Personal Data:
</div>{" "}
<div class="-ml-6">(a) Non-Collection of Personal Data:</div>{" "}
Shapes with Abigail! does not collect nor store personal data.
We respect the privacy of our users, especially considering
the age of our users. We believe that no information, whether
@@ -41,10 +46,10 @@ export default function PrivacyPolicy() {
<span class="-ml-4 pr-2">2.</span> Third-Party Access
</div>
<div class="pb-2 pl-4">
<div class="-ml-6">(a) No Third-Party Access:</div> Since we
do not collect or store any user data, there is no possibility
of sharing or selling our users&apos; information to third
parties. Our priority is the safety and privacy of our users.
<div class="-ml-6">(a) No Third-Party Access:</div> Since we do
not collect or store any user data, there is no possibility of
sharing or selling our users&apos; information to third parties.
Our priority is the safety and privacy of our users.
</div>
</div>
@@ -63,14 +68,13 @@ export default function PrivacyPolicy() {
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">4.</span> Changes to the Privacy
Policy
<span class="-ml-4 pr-2">4.</span> Changes to the Privacy Policy
</div>
<div class="pb-2 pl-4">
<div class="-ml-6">(a) Updates:</div> We may update this
privacy policy periodically. Any changes to this privacy policy
will be posted on this page. However, since we do not collect
any personal data, these updates are likely to be insignificant.
<div class="-ml-6">(a) Updates:</div> We may update this privacy
policy periodically. Any changes to this privacy policy will be
posted on this page. However, since we do not collect any
personal data, these updates are likely to be insignificant.
</div>
</div>
@@ -94,5 +98,6 @@ export default function PrivacyPolicy() {
</ol>
</div>
</div>
</>
);
}

View File

@@ -1,7 +1,14 @@
import { Title } from "@solidjs/meta";
import { Title, Meta } from "@solidjs/meta";
export default function Resume() {
return (
<>
<Title>Resume | Michael Freno</Title>
<Meta
name="description"
content="View Michael Freno's resume - Software Engineer with expertise in full-stack development, game development, and open source."
/>
<main class="flex h-screen w-full flex-col">
<Title>Resume - Freno.dev</Title>
<div class="flex h-full w-full items-center justify-center">
@@ -12,5 +19,6 @@ export default function Resume() {
/>
</div>
</main>
</>
);
}

View File

@@ -1,7 +1,12 @@
import { createSignal } from "solid-js";
import { Title, Meta } from "@solidjs/meta";
import Input from "~/components/ui/Input";
import Button from "~/components/ui/Button";
import { isValidEmail, validatePassword, passwordsMatch } from "~/lib/validation";
import {
isValidEmail,
validatePassword,
passwordsMatch
} from "~/lib/validation";
/**
* Test page to validate Task 01 components and utilities
@@ -36,24 +41,30 @@ export default function TestUtilsPage() {
setLoading(true);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
await new Promise((resolve) => setTimeout(resolve, 2000));
alert(`Form submitted!\nEmail: ${email()}\nPassword: ${password()}`);
setLoading(false);
};
return (
<>
<Title>Utility Testing | Michael Freno</Title>
<Meta
name="description"
content="Testing page for form components and validation utilities."
/>
<main class="min-h-screen bg-gray-100 p-8">
<div class="max-w-2xl mx-auto">
<div class="bg-white rounded-lg shadow-lg p-6 mb-6">
<h1 class="text-3xl font-bold mb-2">Task 01 - Utility Testing</h1>
<p class="text-gray-600 mb-4">
<div class="mx-auto max-w-2xl">
<div class="mb-6 rounded-lg bg-white p-6 shadow-lg">
<h1 class="mb-2 text-3xl font-bold">Task 01 - Utility Testing</h1>
<p class="mb-4 text-gray-600">
Testing shared utilities, types, and UI components
</p>
</div>
<div class="bg-white rounded-lg shadow p-6 mb-6">
<h2 class="text-xl font-bold mb-4">Form Components & Validation</h2>
<div class="mb-6 rounded-lg bg-white p-6 shadow">
<h2 class="mb-4 text-xl font-bold">Form Components & Validation</h2>
<form onSubmit={handleSubmit} class="space-y-4">
<Input
@@ -130,30 +141,42 @@ export default function TestUtilsPage() {
</form>
</div>
<div class="bg-white rounded-lg shadow p-6">
<h2 class="text-xl font-bold mb-4">Validation Status</h2>
<div class="rounded-lg bg-white p-6 shadow">
<h2 class="mb-4 text-xl font-bold">Validation Status</h2>
<div class="space-y-2 text-sm">
<div class="flex items-center gap-2">
<span class={`w-3 h-3 rounded-full ${isValidEmail(email()) ? "bg-green-500" : "bg-gray-300"}`} />
<span
class={`h-3 w-3 rounded-full ${isValidEmail(email()) ? "bg-green-500" : "bg-gray-300"}`}
/>
<span>Email Valid: {isValidEmail(email()) ? "✓" : "✗"}</span>
</div>
<div class="flex items-center gap-2">
<span class={`w-3 h-3 rounded-full ${validatePassword(password()).isValid ? "bg-green-500" : "bg-gray-300"}`} />
<span>Password Valid: {validatePassword(password()).isValid ? "✓" : "✗"}</span>
<span
class={`h-3 w-3 rounded-full ${validatePassword(password()).isValid ? "bg-green-500" : "bg-gray-300"}`}
/>
<span>
Password Valid:{" "}
{validatePassword(password()).isValid ? "✓" : "✗"}
</span>
</div>
<div class="flex items-center gap-2">
<span class={`w-3 h-3 rounded-full ${passwordsMatch(password(), passwordConf()) ? "bg-green-500" : "bg-gray-300"}`} />
<span>Passwords Match: {passwordsMatch(password(), passwordConf()) ? "✓" : "✗"}</span>
<span
class={`h-3 w-3 rounded-full ${passwordsMatch(password(), passwordConf()) ? "bg-green-500" : "bg-gray-300"}`}
/>
<span>
Passwords Match:{" "}
{passwordsMatch(password(), passwordConf()) ? "✓" : "✗"}
</span>
</div>
</div>
</div>
<div class="bg-blue-50 border border-blue-200 rounded p-4 mt-6">
<h3 class="font-bold text-blue-800 mb-2"> Task 01 Complete</h3>
<ul class="text-sm text-blue-700 space-y-1">
<div class="mt-6 rounded border border-blue-200 bg-blue-50 p-4">
<h3 class="mb-2 font-bold text-blue-800"> Task 01 Complete</h3>
<ul class="space-y-1 text-sm text-blue-700">
<li> User types created</li>
<li> Cookie utilities created</li>
<li> Validation helpers created</li>
@@ -165,5 +188,6 @@ export default function TestUtilsPage() {
</div>
</div>
</main>
</>
);
}

View File

@@ -1,5 +1,6 @@
import { createSignal, For, Show } from "solid-js";
import { query, createAsync } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
import { getRequestEvent } from "solid-js/web";
import { api } from "~/lib/api";
@@ -932,6 +933,12 @@ export default function TestPage() {
};
return (
<>
<Title>API Testing | Michael Freno</Title>
<Meta
name="description"
content="tRPC API testing dashboard for developers to test endpoints and verify functionality."
/>
<Show
when={authState()?.privilegeLevel === "admin"}
fallback={
@@ -946,7 +953,9 @@ export default function TestPage() {
<main class="min-h-screen p-8">
<div class="mx-auto max-w-6xl">
<div class="bg-surface0 mb-6 rounded-lg p-6 shadow-lg">
<h1 class="mb-2 text-3xl font-bold">tRPC API Testing Dashboard</h1>
<h1 class="mb-2 text-3xl font-bold">
tRPC API Testing Dashboard
</h1>
<p class="text-text mb-4">
Complete API coverage: Example, Auth, Database, User, Misc, and
Lineage routers
@@ -994,7 +1003,8 @@ export default function TestPage() {
<For each={section.endpoints}>
{(endpoint) => {
const key = `${endpoint.router}.${endpoint.procedure}`;
const hasInput = endpoint.sampleInput !== undefined;
const hasInput =
endpoint.sampleInput !== undefined;
const displayInput = () => {
if (inputEdits()[key]) {
return inputEdits()[key];
@@ -1069,7 +1079,10 @@ export default function TestPage() {
<textarea
value={displayInput()}
onInput={(e) =>
updateInput(key, e.currentTarget.value)
updateInput(
key,
e.currentTarget.value
)
}
class="border-lavender bg-crust min-h-[100px] w-full rounded border p-2 font-mono text-xs"
spellcheck={false}
@@ -1096,7 +1109,11 @@ export default function TestPage() {
Response:
</p>
<pre class="max-h-60 overflow-auto text-xs text-green-400">
{JSON.stringify(results()[key], null, 2)}
{JSON.stringify(
results()[key],
null,
2
)}
</pre>
</div>
</Show>
@@ -1118,18 +1135,20 @@ export default function TestPage() {
<div class="space-y-4 text-base">
<div>
<h3 class="mb-2 text-lg font-semibold">🟢 No Auth Required</h3>
<h3 class="mb-2 text-lg font-semibold">
🟢 No Auth Required
</h3>
<ul class="ml-6 list-disc space-y-1 text-sm">
<li>
<strong>Example Router</strong> - Hello endpoint
</li>
<li>
<strong>Lineage JSON Service</strong> - All 6 endpoints work
immediately
<strong>Lineage JSON Service</strong> - All 6 endpoints
work immediately
</li>
<li>
<strong>Database</strong> - All endpoints (comments, posts,
users, reactions, likes)
<strong>Database</strong> - All endpoints (comments,
posts, users, reactions, likes)
</li>
<li>
<strong>Misc</strong> - Downloads, S3 operations, password
@@ -1179,30 +1198,32 @@ export default function TestPage() {
<strong>Example Router</strong> - Admin Dashboard
</li>
<li>
<strong>Lineage Maintenance</strong> - Find Loose Databases,
Cleanup Expired
<strong>Lineage Maintenance</strong> - Find Loose
Databases, Cleanup Expired
</li>
</ul>
</div>
<div>
<h3 class="mb-2 text-lg font-semibold">📝 Typical Workflows</h3>
<h3 class="mb-2 text-lg font-semibold">
📝 Typical Workflows
</h3>
<ol class="ml-6 list-decimal space-y-2 text-sm">
<li>
<strong>Test public endpoints:</strong> Start with Example
Hello, Lineage JSON Service, or Database queries
</li>
<li>
<strong>OAuth flow:</strong> Use Auth router callbacks with
OAuth codes from GitHub/Google
<strong>OAuth flow:</strong> Use Auth router callbacks
with OAuth codes from GitHub/Google
</li>
<li>
<strong>Email auth flow:</strong> Register verify email
login use JWT
<strong>Email auth flow:</strong> Register verify email
login use JWT
</li>
<li>
<strong>Blog/Project management:</strong> Create posts add
comments/likes upload images via S3
<strong>Blog/Project management:</strong> Create posts
add comments/likes upload images via S3
</li>
<li>
<strong>Lineage game data:</strong> Fetch JSON data
@@ -1214,9 +1235,9 @@ export default function TestPage() {
<div class="border-rosewater bg-rosewater mt-4 rounded border p-4">
<p class="text-crust text-sm">
<strong>Note:</strong> Some endpoints require specific setup
(e.g., OAuth codes, existing database records, valid S3 keys).
Check the sample input to understand what data each endpoint
expects.
(e.g., OAuth codes, existing database records, valid S3
keys). Check the sample input to understand what data each
endpoint expects.
</p>
</div>
</div>
@@ -1224,5 +1245,6 @@ export default function TestPage() {
</div>
</main>
</Show>
</>
);
}