diff --git a/src/app/(user)/profile/[uid]/edit/EditProfileForm.tsx b/src/app/(user)/profile/[uid]/edit/EditProfileForm.tsx new file mode 100644 index 0000000..9c594bf --- /dev/null +++ b/src/app/(user)/profile/[uid]/edit/EditProfileForm.tsx @@ -0,0 +1,155 @@ +"use client"; + +import { useForm } from "react-hook-form"; +import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@/components/ui/form"; +import { z } from "zod"; +import { profileSchema } from "@/types/schemas/profile.schema"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { uploadAvatar } from "@/lib/data/bucket/uploadAvatar"; +import toast from "react-hot-toast"; +import { useRouter } from "next/navigation"; +import { MdxEditor } from "@/components/MarkdownEditor"; +import { updateProfile } from "@/lib/data/profileMutate"; +import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; +import { useState } from "react"; + +interface ProfileData { + username: string; + full_name: string; + bio: string; +} + +type EditProfileFormProps = { + profileData: ProfileData; + uid: string; + sessionUserId: string; +}; + +export default function EditProfileForm({ profileData, uid, sessionUserId }: EditProfileFormProps) { + const router = useRouter(); + const client = createSupabaseClient(); + const profileForm = useForm>({ + resolver: zodResolver(profileSchema), + defaultValues: { + avatars: undefined, + username: profileData?.username || "", + full_name: profileData?.full_name || "", + bio: profileData?.bio || "", + }, + }); + + const [bioContent, setBioContent] = useState(profileData.bio || ""); + + const onProfileSubmit = async (updates: z.infer) => { + const { avatars, username, full_name } = updates; + let avatarUrl = null; + + try { + if (avatars instanceof File) { + const avatarData = await uploadAvatar(client, avatars, uid); + avatarUrl = avatarData?.path + ? `${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/avatars/${avatarData.path}` + : null; + } + + const updateData = { + username, + full_name, + bio: bioContent, + ...(avatarUrl && { avatar_url: avatarUrl }), + }; + + const hasChanges = Object.values(updateData).some((value) => value !== undefined && value !== null); + + if (!hasChanges) { + toast.error("No fields to update!"); + return; + } + + const result = await updateProfile(client, uid, updateData); + + if (result) { + toast.success("Profile updated successfully!"); + router.push(`/profile/${uid}`); + router.refresh(); + } else { + toast.error("Failed to update profile!"); + } + } catch (error) { + toast.error("Error updating profile!"); + console.error("Error updating profile:", error); + } + }; + + return ( +
+
+ + ( + + Avatar + + onChange(event.target.files && event.target.files[0])} + /> + + + + + )} + /> + ( + + Username + + + + This is your public display name. + + + )} + /> + ( + + Full Name + + + + This is your public full name. + + + )} + /> + ( + + Bio + + + + This is your public bio description in Markdown format. + + + )} + /> + + + +
+ ); +} diff --git a/src/app/(user)/profile/[uid]/edit/page.tsx b/src/app/(user)/profile/[uid]/edit/page.tsx index 97abc85..33a9e29 100644 --- a/src/app/(user)/profile/[uid]/edit/page.tsx +++ b/src/app/(user)/profile/[uid]/edit/page.tsx @@ -1,179 +1,39 @@ -"use client"; +import { createSupabaseClient } from "@/lib/supabase/serverComponentClient"; +import { getUserProfile } from "@/lib/data/userQuery"; +import { notFound } from "next/navigation"; +import EditProfileForm from "./EditProfileForm"; -import { updateProfile } from "@/lib/data/profileMutate"; -import { useForm } from "react-hook-form"; -import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@/components/ui/form"; -import { z } from "zod"; -import { profileSchema } from "@/types/schemas/profile.schema"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { Input } from "@/components/ui/input"; -import { Button } from "@/components/ui/button"; -import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; -import { uploadAvatar } from "@/lib/data/bucket/uploadAvatar"; -import toast from "react-hot-toast"; -import { useRouter } from "next/navigation"; -import { Separator } from "@/components/ui/separator"; -import useSession from "@/lib/supabase/useSession"; -import React, { useState } from "react"; -import { MdxEditor } from "@/components/MarkdownEditor"; +type ProfilePageProps = { + params: { uid: string }; +}; -export default function EditProfilePage({ params }: { params: { uid: string } }) { +export default async function ProfilePage({ params }: ProfilePageProps) { const uid = params.uid; const client = createSupabaseClient(); - const router = useRouter(); - const { session, loading: isLoadingSession } = useSession(); + const { + data: { user }, + } = await client.auth.getUser(); - const profileForm = useForm>({ - resolver: zodResolver(profileSchema), - }); - - const [bioContent, setBioContent] = useState(""); - - if (isLoadingSession) { - return ( -
-

Loading session...

-
- ); + if (!user || user?.id !== uid) { + notFound(); } - const onProfileSubmit = async (updates: z.infer) => { - const { avatars, username, full_name } = updates; - let avatarUrl = null; + const { data: profileData, error } = await getUserProfile(client, uid); - try { - if (avatars instanceof File) { - const { data: currentProfile, error: fetchError } = await client - .from("profiles") - .select("avatar_url") - .eq("id", uid) - .single(); - - if (fetchError) { - throw new Error("Failed to fetch existing profile data"); - } - - if (currentProfile?.avatar_url) { - const oldAvatarPath = currentProfile.avatar_url.split("/").pop(); - const { error: deleteError } = await client.storage.from("avatars").remove([oldAvatarPath]); - - if (deleteError) { - console.warn("Failed to delete old avatar:", deleteError.message); - } - } - - const avatarData = await uploadAvatar(client, avatars, uid); - avatarUrl = avatarData?.path - ? `${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/avatars/${avatarData.path}` - : null; - } - - const updateData = { - username, - full_name, - bio: bioContent, - ...(avatarUrl && { avatar_url: avatarUrl }), - }; - - const hasChanges = Object.values(updateData).some((value) => value !== undefined && value !== null); - - if (!hasChanges) { - toast.error("No fields to update!"); - return; - } - - const result = await updateProfile(client, uid, updateData); - - if (result) { - toast.success("Profile updated successfully!"); - router.push(`/profile/${uid}`); - } else { - toast.error("Failed to update profile!"); - } - } catch (error) { - toast.error("Error updating profile!"); - console.error("Error updating profile:", error); - } - }; - - if (uid != session?.user.id) { - router.push(`/profile/${uid}`); + if (error || !profileData) { + return ( +
+

Error loading profile data. Please try again later.

+
+ ); } return (
Update Profile - -
-
-
- - ( - - Avatar - - onChange(event.target.files && event.target.files[0])} - /> - - - - - )} - /> - ( - - Username - - - - This is your public display name. - - - )} - /> - ( - - Full Name - - - - This is your public full name. - - - )} - /> - ( - - Bio - - - - This is your public bio description in Markdown format. - - - )} - /> - - -
+
); }