This commit is contained in:
Michael Freno
2025-12-18 15:03:13 -05:00
parent 5aecf6e555
commit fec58c4c17
12 changed files with 790 additions and 249 deletions

View File

@@ -20,7 +20,7 @@ export default function BlogIndex() {
<>
<Title>Blog | Michael Freno</Title>
<div class="relative mx-auto min-h-screen rounded-t-lg pt-8 pb-24 shadow-2xl">
<div class="mx-auto pt-8 pb-24">
<Suspense fallback={<TerminalSplash />}>
<div class="flex flex-col justify-center gap-4 md:flex-row md:justify-around">
<PostSortingSelect />

335
src/routes/contact.tsx Normal file
View File

@@ -0,0 +1,335 @@
import { createSignal, onMount, onCleanup, Show } from "solid-js";
import { useSearchParams } from "@solidjs/router";
import { Title, Meta } from "@solidjs/meta";
import { A } from "@solidjs/router";
import { api } from "~/lib/api";
import GitHub from "~/components/icons/GitHub";
import LinkedIn from "~/components/icons/LinkedIn";
import { getClientCookie, setClientCookie } from "~/lib/cookies.client";
import CountdownCircleTimer from "~/components/CountdownCircleTimer";
import LoadingSpinner from "~/components/LoadingSpinner";
import RevealDropDown from "~/components/RevealDropDown";
import type { UserProfile } from "~/types/user";
export default function ContactPage() {
const [searchParams] = useSearchParams();
const viewer = () => searchParams.viewer ?? "default";
const [countDown, setCountDown] = createSignal<number>(0);
const [emailSent, setEmailSent] = createSignal<boolean>(false);
const [error, setError] = createSignal<string>("");
const [loading, setLoading] = createSignal<boolean>(false);
const [user, setUser] = createSignal<UserProfile | null>(null);
let timerIdRef: ReturnType<typeof setInterval> | null = null;
const calcRemainder = (timer: string) => {
const expires = new Date(timer);
const remaining = expires.getTime() - Date.now();
const remainingInSeconds = remaining / 1000;
if (remainingInSeconds <= 0) {
setCountDown(0);
if (timerIdRef !== null) {
clearInterval(timerIdRef);
}
} else {
setCountDown(remainingInSeconds);
}
};
onMount(() => {
// Check for existing timer
const timer = getClientCookie("contactRequestSent");
if (timer) {
timerIdRef = setInterval(() => calcRemainder(timer), 1000);
}
// Fetch user data if authenticated
api.user.getProfile
.query()
.then((userData) => {
if (userData) {
setUser(userData);
}
})
.catch(() => {
// User not authenticated, no problem
});
onCleanup(() => {
if (timerIdRef !== null) {
clearInterval(timerIdRef);
}
});
});
const sendEmailTrigger = async (e: Event) => {
e.preventDefault();
const formData = new FormData(e.target as HTMLFormElement);
const name = formData.get("name") as string;
const email = formData.get("email") as string;
const message = formData.get("message") as string;
if (name && email && message) {
setLoading(true);
try {
const res = await api.misc.sendContactRequest.mutate({
name,
email,
message
});
if (res.message === "email sent") {
setEmailSent(true);
setError("");
const timer = getClientCookie("contactRequestSent");
if (timer) {
if (timerIdRef !== null) {
clearInterval(timerIdRef);
}
timerIdRef = setInterval(() => calcRemainder(timer), 1000);
}
}
} catch (err: any) {
setError(err.message || "An error occurred");
setEmailSent(false);
}
setLoading(false);
}
};
const LineageQuestionsDropDown = () => {
return (
<div class="mx-auto px-4 py-12 md:w-3/4 md:flex-row lg:w-1/2">
<RevealDropDown title={"Questions about Life and Lineage?"}>
<div>
Feel free to use the form{" "}
{viewer() === "lineage" ? "below" : "above"}, I will respond as
quickly as possible, however, you may find an answer to your
question in the following.
</div>
<ol>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">1.</span> Personal Information
</div>
<div class="pl-4">
<div class="pb-2">
You can find the entire privacy policy{" "}
<A
href="/privacy-policy/life-and-lineage"
class="text-blue underline-offset-4 hover:underline"
>
here
</A>
.
</div>
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">2.</span> Remote Backups
</div>
<div class="pl-4">
<em>Life and Lineage</em> uses a per-user database approach for
its remote storage, this provides better separation of users and
therefore privacy, and it makes requesting the removal of your
data simpler, you can even request the database dump if you so
choose. This isn&apos;t particularly expensive, but not free for
n users, so use of this feature requires a purchase of an
IAP(in-app purchase) - this can be the specific IAP for the
remote save feature, and any other IAP will also unlock this
feature.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">3.</span> Cross Device Play
</div>
<div class="pl-4">
You can use the above mentioned remote-backups to save progress
between devices/platforms.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">4.</span> Online Requirements
</div>
<div class="pl-4">
Currently, the only time you need to be online is for remote
save access. There are plans for pvp, which will require an
internet connection, but this is not implemented at time of
writing.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">5.</span> Microtransactions
</div>
<div class="pl-4">
Microtransactions are not required to play or complete the game,
the game can be fully completed without spending any money,
however 2 of the classes(necromancer and ranger) are pay-walled.
Microtransactions are supported cross-platform, so no need to
pay for each device, you simply need to login to your
gmail/apple/email account. This would require first creating a
character, signing in under options{">"}remote backups first.
</div>
</div>
</ol>
</RevealDropDown>
</div>
);
};
const renderTime = (time: number) => {
return (
<div class="timer">
<div class="value">{time.toFixed(0)}</div>
</div>
);
};
return (
<>
<Title>Contact | Michael Freno</Title>
<Meta name="description" content="Contact Me" />
<div class="flex min-h-screen w-full justify-center">
<div class="pt-[20vh]">
<div class="text-center text-3xl tracking-widest dark:text-white">
Contact
</div>
<Show when={viewer() !== "lineage"}>
<div class="mt-4 -mb-4 text-center text-xl tracking-widest dark:text-white">
(for this website or any of my apps...)
</div>
</Show>
<Show when={viewer() === "lineage"}>
<LineageQuestionsDropDown />
</Show>
<form onSubmit={sendEmailTrigger} class="min-w-[85vw] px-4">
<div
class={`flex w-full flex-col justify-evenly pt-6 ${
viewer() !== "lineage" ? "md:mt-24" : ""
}`}
>
<div class="mx-auto w-full justify-evenly md:flex md:w-3/4 md:flex-row lg:w-1/2">
<div class="input-group md:mx-4">
<input
type="text"
required
name="name"
value={user()?.displayName ?? ""}
placeholder=" "
class="underlinedInput w-full bg-transparent"
/>
<span class="bar"></span>
<label class="underlinedInputLabel">Name</label>
</div>
<div class="input-group md:mx-4">
<input
type="email"
required
name="email"
value={user()?.email ?? ""}
placeholder=" "
class="underlinedInput w-full bg-transparent"
/>
<span class="bar"></span>
<label class="underlinedInputLabel">Email</label>
</div>
</div>
<div class="mx-auto w-full pt-6 md:w-3/4 md:pt-12 lg:w-1/2">
<div class="textarea-group">
<textarea
required
name="message"
placeholder=" "
class="underlinedInput w-full bg-transparent"
rows={4}
/>
<span class="bar" />
<label class="underlinedInputLabel">Message</label>
</div>
</div>
<div class="mx-auto flex w-full justify-end pt-4 md:w-3/4 lg:w-1/2">
<Show
when={countDown() > 0}
fallback={
<button
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-base font-light transition-all duration-300 ease-out`}
>
<Show when={loading()} fallback="Send Message">
<LoadingSpinner height={24} width={24} />
</Show>
</button>
}
>
<CountdownCircleTimer
duration={60}
initialRemainingTime={countDown()}
size={48}
strokeWidth={6}
colors={"#60a5fa"}
onComplete={() => setCountDown(0)}
>
{renderTime}
</CountdownCircleTimer>
</Show>
</div>
</div>
</form>
<Show when={viewer() !== "lineage"}>
<LineageQuestionsDropDown />
</Show>
<div
class={`${
emailSent()
? "text-green-400"
: error() !== ""
? "text-red-400"
: "user-select opacity-0"
} flex justify-center text-center italic transition-opacity duration-300 ease-in-out`}
>
{emailSent() ? "Email Sent!" : error()}
</div>
<ul class="icons flex justify-center pt-24 pb-6">
<li>
<A
href="https://github.com/MikeFreno/"
target="_blank"
rel="noreferrer"
class="shaker rounded-full border-zinc-800 dark:border-zinc-300"
>
<span class="m-auto p-2">
<GitHub height={24} width={24} fill={undefined} />
</span>
</A>
</li>
<li>
<A
href="https://www.linkedin.com/in/michael-freno-176001256/"
target="_blank"
rel="noreferrer"
class="shaker rounded-full border-zinc-800 dark:border-zinc-300"
>
<span class="m-auto rounded-md p-2">
<LinkedIn height={24} width={24} fill={undefined} />
</span>
</A>
</li>
</ul>
</div>
</div>
</>
);
}

View File

@@ -2,111 +2,98 @@ import { A } from "@solidjs/router";
export default function PrivacyPolicy() {
return (
<div class="bg-zinc-100 dark:bg-zinc-900">
<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>
<div class="py-2">
Welcome to Life and Lineage (&apos;We&apos;, &apos;Us&apos;,
&apos;Our&apos;). Your privacy is important to us. This privacy
policy will help you understand our policies and procedures related
to the collection, use, and storage of personal information from our
users.
</div>
<ol>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">1.</span> Personal Information
</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>
<div class="pb-2">
<div class="-ml-6">(b) Data Removal:</div> Users can
request the removal of all information related to them by
visiting{" "}
<A
href="/deletion/life-and-lineage"
class="text-blue-400 underline-offset-4 hover:underline"
>
this page
</A>{" "}
and filling out the provided form.
</div>
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">2.</span> Third-Party Access
</div>
<div class="pb-2 pl-4">
<div class="-ml-6">(a) Limited Third-Party Access:</div> We
do not share or sell user information to third parties. However,
we do utilize third-party services for crash reporting and
performance profiling. These services do not have access to
personal user information and only receive anonymized data
related to app performance and stability.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<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>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<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. We encourage users to review this
policy regularly to stay informed about how we protect their
information.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">5.</span> Contact Us
</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{" "}
<A
href="/contact"
class="text-blue-400 underline-offset-4 hover:underline"
>
here
</A>
.
</div>
</div>
</ol>
<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>
<div class="py-2">
Welcome to Life and Lineage (&apos;We&apos;, &apos;Us&apos;,
&apos;Our&apos;). Your privacy is important to us. This privacy policy
will help you understand our policies and procedures related to the
collection, use, and storage of personal information from our users.
</div>
<ol>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">1.</span> Personal Information
</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>
<div class="pb-2">
<div class="-ml-6">(b) Data Removal:</div> Users can request the
removal of all information related to them by visiting{" "}
<A
href="/deletion/life-and-lineage"
class="text-blue hover-underline-animation"
>
this page
</A>{" "}
and filling out the provided form.
</div>
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">2.</span> Third-Party Access
</div>
<div class="pb-2 pl-4">
<div class="-ml-6">(a) Limited Third-Party Access:</div> We do not
share or sell user information to third parties. However, we do
utilize third-party services for crash reporting and performance
profiling. These services do not have access to personal user
information and only receive anonymized data related to app
performance and stability.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<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>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<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. We encourage users to review this policy
regularly to stay informed about how we protect their information.
</div>
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">5.</span> Contact Us
</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{" "}
<A href="/contact" class="text-blue hover-underline-animation">
here
</A>
.
</div>
</div>
</ol>
</div>
);
}