almost done

This commit is contained in:
Michael Freno
2025-12-19 14:04:32 -05:00
parent 0459c9536c
commit 8f4fac422b
6 changed files with 269 additions and 110 deletions

View File

@@ -3,6 +3,9 @@ import { useNavigate, cache, redirect } from "@solidjs/router";
import { getEvent } from "vinxi/http";
import Eye from "~/components/icons/Eye";
import EyeSlash from "~/components/icons/EyeSlash";
import XCircle from "~/components/icons/XCircle";
import Dropzone from "~/components/blog/Dropzone";
import AddImageToS3 from "~/lib/s3upload";
import { validatePassword, isValidEmail } from "~/lib/validation";
import { checkAuthStatus } from "~/server/utils";
@@ -71,6 +74,17 @@ export default function AccountPage() {
createSignal(false);
const [showPasswordSuccess, setShowPasswordSuccess] = createSignal(false);
// Profile image state
const [profileImage, setProfileImage] = createSignal<Blob | undefined>(
undefined
);
const [profileImageHolder, setProfileImageHolder] = createSignal<
string | null
>(null);
const [profileImageStateChange, setProfileImageStateChange] =
createSignal(false);
const [preSetHolder, setPreSetHolder] = createSignal<string | null>(null);
// Form refs
let oldPasswordRef: HTMLInputElement | undefined;
let newPasswordRef: HTMLInputElement | undefined;
@@ -90,6 +104,10 @@ export default function AccountPage() {
const result = await response.json();
if (result.result?.data) {
setUser(result.result.data);
// Set preset holder if user has existing image
if (result.result.data.image) {
setPreSetHolder(result.result.data.image);
}
}
}
} catch (err) {
@@ -99,6 +117,82 @@ export default function AccountPage() {
}
});
// Profile image handlers
const handleImageDrop = (acceptedFiles: File[]) => {
acceptedFiles.forEach((file: File) => {
setProfileImage(file);
const reader = new FileReader();
reader.onload = () => {
const str = reader.result as string;
setProfileImageHolder(str);
setProfileImageStateChange(true);
};
reader.readAsDataURL(file);
});
};
const removeImage = () => {
setProfileImage(undefined);
setProfileImageHolder(null);
if (preSetHolder()) {
setProfileImageStateChange(true);
setPreSetHolder(null);
} else {
setProfileImageStateChange(false);
}
};
const setUserImage = async (e: Event) => {
e.preventDefault();
setProfileImageSetLoading(true);
setShowImageSuccess(false);
const currentUser = user();
if (!currentUser) {
setProfileImageSetLoading(false);
return;
}
try {
let imageUrl = "";
// Upload new image if one was selected
if (profileImage()) {
const imageKey = await AddImageToS3(
profileImage()!,
currentUser.id,
"user"
);
imageUrl = imageKey || "";
}
// Update user profile image
const response = await fetch("/api/trpc/user.updateProfileImage", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ imageUrl })
});
const result = await response.json();
if (response.ok && result.result?.data) {
setUser(result.result.data);
setShowImageSuccess(true);
setProfileImageStateChange(false);
setTimeout(() => setShowImageSuccess(false), 3000);
// Update preSetHolder with new image
setPreSetHolder(imageUrl || null);
} else {
alert("Error updating profile image!");
}
} catch (err) {
console.error("Profile image update error:", err);
alert("Error updating profile image! Check console.");
} finally {
setProfileImageSetLoading(false);
}
};
// Email update handler
const setEmailTrigger = async (e: Event) => {
e.preventDefault();
@@ -375,6 +469,57 @@ export default function AccountPage() {
Account Settings
</div>
{/* Profile Image Section */}
<div class="mx-auto mb-8 flex max-w-md justify-center">
<div class="flex flex-col py-4">
<div class="mb-2 text-center text-lg font-semibold">
Profile Image
</div>
<div class="flex items-start">
<Dropzone
onDrop={handleImageDrop}
acceptedFiles="image/jpg, image/jpeg, image/png"
fileHolder={profileImageHolder()}
preSet={preSetHolder() || currentUser().image || null}
/>
<button
type="button"
onClick={removeImage}
class="z-20 -ml-6 h-fit rounded-full transition-all hover:brightness-125"
>
<XCircle
height={36}
width={36}
stroke="currentColor"
strokeWidth={1}
/>
</button>
</div>
<form onSubmit={setUserImage}>
<button
type="submit"
disabled={
profileImageSetLoading() || !profileImageStateChange()
}
class={`${
profileImageSetLoading() || !profileImageStateChange()
? "bg-blue cursor-not-allowed brightness-75"
: "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"}
</button>
</form>
<Show when={showImageSuccess()}>
<div class="text-green mt-2 text-center text-sm">
Profile image updated!
</div>
</Show>
</div>
</div>
<hr class="mx-auto mb-8 max-w-4xl" />
{/* Email Section */}
<div class="mx-auto flex max-w-4xl flex-col gap-6 md:grid md:grid-cols-2">
<div class="flex items-center justify-center text-lg md:justify-normal">
@@ -431,7 +576,7 @@ export default function AccountPage() {
emailButtonLoading() ||
(currentUser().email !== null &&
!currentUser().emailVerified)
? "bg-blue cursor-not-allowed brightness-50"
? "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`}
>
@@ -482,7 +627,7 @@ export default function AccountPage() {
disabled={displayNameButtonLoading()}
class={`${
displayNameButtonLoading()
? "bg-blue cursor-not-allowed brightness-50"
? "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`}
>
@@ -610,7 +755,7 @@ export default function AccountPage() {
disabled={passwordChangeLoading() || !passwordsMatch()}
class={`${
passwordChangeLoading() || !passwordsMatch()
? "bg-blue cursor-not-allowed brightness-50"
? "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`}
>
@@ -670,7 +815,7 @@ export default function AccountPage() {
disabled={deleteAccountButtonLoading()}
class={`${
deleteAccountButtonLoading()
? "bg-red cursor-not-allowed brightness-50"
? "bg-red cursor-not-allowed brightness-75"
: "bg-red hover:brightness-125 active:scale-90"
} mx-auto mt-4 flex justify-center rounded px-4 py-2 text-base transition-all duration-300 ease-out`}
>

View File

@@ -105,15 +105,14 @@ export default function ContactPage() {
<div class="w-full py-12">
<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.
Feel free to use the form below, 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
<span class="-ml-2 pr-2">1.</span> Personal Information
</div>
<div class="pl-4">
<div class="pb-2">
@@ -130,7 +129,7 @@ export default function ContactPage() {
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">2.</span> Remote Backups
<span class="-ml-2 pr-2">2.</span> Remote Backups
</div>
<div class="pl-4">
<em>Life and Lineage</em> uses a per-user database approach for
@@ -146,7 +145,7 @@ export default function ContactPage() {
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">3.</span> Cross Device Play
<span class="-ml-2 pr-2">3.</span> Cross Device Play
</div>
<div class="pl-4">
You can use the above mentioned remote-backups to save progress
@@ -155,7 +154,7 @@ export default function ContactPage() {
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">4.</span> Online Requirements
<span class="-ml-2 pr-2">4.</span> Online Requirements
</div>
<div class="pl-4">
Currently, the only time you need to be online is for remote
@@ -166,7 +165,7 @@ export default function ContactPage() {
</div>
<div class="py-2">
<div class="pb-2 text-lg">
<span class="-ml-4 pr-2">5.</span> Microtransactions
<span class="-ml-2 pr-2">5.</span> Microtransactions
</div>
<div class="pl-4">
Microtransactions are not required to play or complete the game,
@@ -205,9 +204,7 @@ export default function ContactPage() {
(for this website or any of my apps...)
</div>
</Show>
<Show when={viewer() === "lineage"}>
<LineageQuestionsDropDown />
</Show>
<LineageQuestionsDropDown />
<form onSubmit={sendEmailTrigger} class="w-full">
<div
class={`flex w-full flex-col justify-evenly pt-6 ${
@@ -286,9 +283,6 @@ export default function ContactPage() {
</div>
</div>
</form>
<Show when={viewer() !== "lineage"}>
<LineageQuestionsDropDown />
</Show>
<div
class={`${
emailSent()

View File

@@ -2,38 +2,51 @@ import { Typewriter } from "~/components/Typewriter";
export default function Home() {
return (
<main class="p-4 text-xl">
<Typewriter speed={30} keepAlive={2000}>
<div class="text-4xl">Hey!</div>
</Typewriter>
<Typewriter speed={80} keepAlive={2000}>
<div>
My name is <span class="text-green">Mike Freno</span>, I'm a{" "}
<span class="text-blue">Software Engineer</span> based in{" "}
<span class="text-yellow">Brooklyn, NY</span>
</div>
</Typewriter>
<Typewriter speed={100}>
I'm a passionate dev tooling, game, and open source software developer.
Recently been working in the world of{" "}
<a
href="https://www.love2d.org"
class="text-blue hover-underline-animation"
>
LÖVE
</a>{" "}
</Typewriter>
You can see some of my work <a>here</a>(github)
<Typewriter speed={50} keepAlive={false}>
<div>My Collection of By-the-ways:</div>
</Typewriter>
<Typewriter speed={50} keepAlive={false}>
<ul class="list-disc pl-8">
<li>I use Neovim</li>
<li>I use Arch Linux</li>
<li>I use Rust</li>
</ul>
</Typewriter>
<main class="flex h-full flex-col p-4 text-xl">
<div class="flex-1">
<Typewriter speed={30} keepAlive={2000}>
<div class="text-4xl">Hey!</div>
</Typewriter>
<Typewriter speed={80} keepAlive={2000}>
<div>
My name is <span class="text-green">Mike Freno</span>, I'm a{" "}
<span class="text-blue">Software Engineer</span> based in{" "}
<span class="text-yellow">Brooklyn, NY</span>
</div>
</Typewriter>
<Typewriter speed={100} keepAlive={2000}>
I'm a passionate dev tooling, game, and open source software
developer. Recently been working in the world of{" "}
<a
href="https://www.love2d.org"
class="text-blue hover-underline-animation"
>
LÖVE
</a>{" "}
</Typewriter>
<Typewriter speed={100} keepAlive={2000}>
You can see some of my work{" "}
<a
href="https://github.com/mikefreno"
class="text-blue hover-underline-animation"
>
here (github)
</a>
</Typewriter>
</div>
<div class="flex flex-col items-end gap-4">
<Typewriter speed={50} keepAlive={false}>
<div>My Collection of By-the-ways:</div>
</Typewriter>
<Typewriter speed={50} keepAlive={false}>
<ul class="list-disc pl-8">
<li>I use Neovim</li>
<li>I use Arch Linux</li>
<li>I use Rust</li>
</ul>
</Typewriter>
</div>
</main>
);
}