Skip to content

Commit

Permalink
fix pfp upload bug
Browse files Browse the repository at this point in the history
  • Loading branch information
christianhelp committed Jan 4, 2025
1 parent bcb53a7 commit e375afe
Show file tree
Hide file tree
Showing 5 changed files with 282 additions and 209 deletions.
51 changes: 27 additions & 24 deletions apps/web/src/actions/user-profile-mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { revalidatePath } from "next/cache";
import { UNIQUE_KEY_CONSTRAINT_VIOLATION_CODE } from "@/lib/constants";
import c from "config";
import { DatabaseError } from "db/types";
import { registrationSettingsFormValidator, modifyAccountSettingsSchema } from "@/validators/settings";

import { registrationSettingsFormValidator, modifyAccountSettingsSchema, profileSettingsSchema } from "@/validators/settings";
import { clerkClient, type User as ClerkUser, } from "@clerk/nextjs/server";
import { PAYLOAD_TOO_LARGE_CODE } from "@/lib/constants";
import { isClerkAPIResponseError } from "@clerk/nextjs";
export const modifyRegistrationData = authenticatedAction
.schema(registrationSettingsFormValidator)
.action(
Expand Down Expand Up @@ -125,28 +127,19 @@ export const deleteResume = authenticatedAction

export const modifyProfileData = authenticatedAction
.schema(
z.object({
pronouns: z.string(),
bio: z.string(),
skills: z.string().array(),
discord: z.string(),
}),
profileSettingsSchema,
)
.action(
async ({
parsedInput: { bio, discord, pronouns, skills },
parsedInput,
ctx: { userId },
}) => {
await db
.update(userCommonData)
.set({ pronouns, bio, skills, discord })
.set({ ...parsedInput, skills:parsedInput.skills.map((v) => v.text.toLowerCase()) })
.where(eq(userCommonData.clerkID, userId));
return {
success: true,
newPronouns: pronouns,
newBio: bio,
newSkills: skills,
newDiscord: discord,
};
},
);
Expand Down Expand Up @@ -197,23 +190,33 @@ export const modifyAccountSettings = authenticatedAction
},
);

// come back and fix this tmr
export const updateProfileImage = authenticatedAction
.schema(z.object({ fileBase64: z.string(), fileName: z.string() }))
.action(
async ({ parsedInput: { fileBase64, fileName }, ctx: { userId } }) => {
const image = await decodeBase64AsFile(fileBase64, fileName);
const user = await db.query.userCommonData.findFirst({
where: eq(userCommonData.clerkID, userId),
});
if (!user) throw new Error("User not found");
const file = await decodeBase64AsFile(fileBase64, fileName);
let clerkUser:ClerkUser;
try{
clerkUser = await clerkClient.users.updateUserProfileImage(userId, {
file
});
}
catch(err){
if (typeof err === "object" && err != null && 'status' in err && err.status === PAYLOAD_TOO_LARGE_CODE) {
return {
success: false,
message: "file_too_large",
};
}
console.error(`Error updating Clerk profile image: ${err}`);
throw err;
}

const blobUpload = await put(image.name, image, {
access: "public",
});
await db
.update(userCommonData)
.set({ profilePhoto: blobUpload.url })
.where(eq(userCommonData.clerkID, user.clerkID));
.set({ profilePhoto: clerkUser.imageUrl })
.where(eq(userCommonData.clerkID, userId));
revalidatePath("/settings#profile");
return { success: true };
},
Expand Down
101 changes: 101 additions & 0 deletions apps/web/src/components/settings/ProfilePhotoSettings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"use client"
import { Avatar, AvatarImage } from "../shadcn/ui/avatar";
import { encodeFileAsBase64 } from "@/lib/utils/shared/files";
import { updateProfileImage } from "@/actions/user-profile-mod";
import { useAction } from "next-safe-action/hooks";
import { toast } from "sonner";
import { useRef, useState } from "react";
import { Input } from "../shadcn/ui/input";
import { Button } from "../shadcn/ui/button";
import { Loader2 } from "lucide-react";

export default function ProfilePhotoSettings({ profilePhoto }: { profilePhoto: string }) {
const [newProfileImage, setNewProfileImage] = useState<File | null>(null);
const [isLoading, setIsLoading] = useState(false);
// this input will either be null or a reference to the input element
const profileInputRef = useRef<HTMLInputElement | null>(null);

const { execute: runUpdateProfileImage } = useAction(
updateProfileImage,
{
onSuccess: (res) => {
setIsLoading(false);
setNewProfileImage(null);
toast.dismiss();
if (profileInputRef.current){
profileInputRef.current.value = "";
}
if (res.data?.message === 'file_too_large') {
toast.error("Please upload a file smaller than 10MB");
return;
}
toast.success("Profile Photo updated successfully!");
},
onError: () => {
setIsLoading(false);
toast.dismiss();
if (profileInputRef.current) {
profileInputRef.current.value = "";
}
toast.error(
"An error occurred while updating your profile photo!",
);
},
},
);

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files ? event.target.files[0] : null;
setNewProfileImage(file);
};
return (
<div className="rounded-lg border-2 border-muted px-5 py-10">
<h2 className="pb-5 text-3xl font-semibold">Profile Photo</h2>
<div className="max-w-[500px] space-y-4">
<div>
<Avatar className={"h-24 w-24"}>
<AvatarImage
src={profilePhoto}
alt={"User Profile Photo"}
></AvatarImage>
</Avatar>
<Input
ref={profileInputRef}
accept=".jpg, .jpeg, .png, .svg, .gif, .mp4"
type="file"
name="photo"
className="mb-4 mt-2 cursor-pointer file:cursor-pointer file:text-primary dark:border-primary dark:bg-transparent dark:ring-offset-primary"
onChange={handleFileChange}
/>
</div>
<Button
onClick={async () => {
if (!newProfileImage) {
return toast.error(
"Please select a profile photo to upload!",
);
}
setIsLoading(true);
const fileBase64 =
await encodeFileAsBase64(newProfileImage);
runUpdateProfileImage({
fileBase64,
fileName: newProfileImage.name,
});
}}
className="mt-5"
disabled={isLoading}
>
{isLoading ? (
<>
<Loader2 className={"mr-2 h-4 w-4 animate-spin"} />
<div>Updating</div>
</>
) : (
"Update"
)}
</Button>
</div>
</div>
);
}
Loading

0 comments on commit e375afe

Please sign in to comment.