mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-20 14:34:05 +01:00
feat: fetch data to profile edit page
This commit is contained in:
parent
747fd57f14
commit
c99301a66d
155
src/app/(user)/profile/[uid]/edit/EditProfileForm.tsx
Normal file
155
src/app/(user)/profile/[uid]/edit/EditProfileForm.tsx
Normal file
@ -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<z.infer<typeof profileSchema>>({
|
||||||
|
resolver: zodResolver(profileSchema),
|
||||||
|
defaultValues: {
|
||||||
|
avatars: undefined,
|
||||||
|
username: profileData?.username || "",
|
||||||
|
full_name: profileData?.full_name || "",
|
||||||
|
bio: profileData?.bio || "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [bioContent, setBioContent] = useState<string>(profileData.bio || "");
|
||||||
|
|
||||||
|
const onProfileSubmit = async (updates: z.infer<typeof profileSchema>) => {
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<Form {...profileForm}>
|
||||||
|
<form onSubmit={profileForm.handleSubmit(onProfileSubmit)} className="space-y-8">
|
||||||
|
<FormField
|
||||||
|
control={profileForm.control}
|
||||||
|
name="avatars"
|
||||||
|
render={({ field: { value, onChange, ...fieldProps } }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Avatar</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...fieldProps}
|
||||||
|
type="file"
|
||||||
|
onChange={(event) => onChange(event.target.files && event.target.files[0])}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription />
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={profileForm.control}
|
||||||
|
name="username"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Username</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>This is your public display name.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={profileForm.control}
|
||||||
|
name="full_name"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Full Name</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>This is your public full name.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={profileForm.control}
|
||||||
|
name="bio"
|
||||||
|
render={() => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Bio</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<MdxEditor content={bioContent} setContentInParent={setBioContent} />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>This is your public bio description in Markdown format.</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button type="submit">Submit</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -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";
|
type ProfilePageProps = {
|
||||||
import { useForm } from "react-hook-form";
|
params: { uid: string };
|
||||||
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";
|
|
||||||
|
|
||||||
export default function EditProfilePage({ params }: { params: { uid: string } }) {
|
export default async function ProfilePage({ params }: ProfilePageProps) {
|
||||||
const uid = params.uid;
|
const uid = params.uid;
|
||||||
const client = createSupabaseClient();
|
const client = createSupabaseClient();
|
||||||
const router = useRouter();
|
const {
|
||||||
const { session, loading: isLoadingSession } = useSession();
|
data: { user },
|
||||||
|
} = await client.auth.getUser();
|
||||||
|
|
||||||
const profileForm = useForm<z.infer<typeof profileSchema>>({
|
if (!user || user?.id !== uid) {
|
||||||
resolver: zodResolver(profileSchema),
|
notFound();
|
||||||
});
|
}
|
||||||
|
|
||||||
const [bioContent, setBioContent] = useState<string>("");
|
const { data: profileData, error } = await getUserProfile(client, uid);
|
||||||
|
|
||||||
if (isLoadingSession) {
|
if (error || !profileData) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center h-screen">
|
<div className="flex items-center justify-center h-screen">
|
||||||
<p>Loading session...</p>
|
<p>Error loading profile data. Please try again later.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onProfileSubmit = async (updates: z.infer<typeof profileSchema>) => {
|
|
||||||
const { avatars, username, full_name } = updates;
|
|
||||||
let avatarUrl = null;
|
|
||||||
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container max-w-screen-xl">
|
<div className="container max-w-screen-xl">
|
||||||
<div className="my-5">
|
<div className="my-5">
|
||||||
<span className="text-2xl font-bold">Update Profile</span>
|
<span className="text-2xl font-bold">Update Profile</span>
|
||||||
<Separator className="my-5" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Form {...profileForm}>
|
|
||||||
<form onSubmit={profileForm.handleSubmit(onProfileSubmit)} className="space-y-8">
|
|
||||||
<FormField
|
|
||||||
control={profileForm.control}
|
|
||||||
name="avatars"
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
render={({ field: { value, onChange, ...fieldProps } }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Avatar</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input
|
|
||||||
{...fieldProps}
|
|
||||||
type="file"
|
|
||||||
onChange={(event) => onChange(event.target.files && event.target.files[0])}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription />
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={profileForm.control}
|
|
||||||
name="username"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Username</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>This is your public display name.</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={profileForm.control}
|
|
||||||
name="full_name"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Full Name</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="" {...field} />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>This is your public full name.</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={profileForm.control}
|
|
||||||
name="bio"
|
|
||||||
render={() => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Bio</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<MdxEditor content={bioContent} setContentInParent={setBioContent} />
|
|
||||||
</FormControl>
|
|
||||||
<FormDescription>This is your public bio description in Markdown format.</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button type="submit">Submit</Button>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</div>
|
</div>
|
||||||
|
<EditProfileForm profileData={profileData} uid={uid} sessionUserId={user.id} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user