// frontend/app/(sidebar)/profile/page.tsx "use client"; import React, { useState, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import * as z from "zod"; import { toast } from "sonner"; import { Loader2, User as UserIcon, Mail, Save, X, Edit, Camera } from "lucide-react"; import { fetchUserMe, UserDataOutput } from "@/api/user"; // Fetch function import { updateUserProfile, UpdateUserProfileInput } from "@/api/profile"; // Update function import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Button } from "@/components/ui/button"; import { Form, FormControl, FormField, FormItem, FormMessage } from "@/components/ui/form"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; // For loading state // Schema for editable fields const profileSchema = z.object({ username: z .string() .min(3, "Username must be at least 3 characters") .max(30, "Username cannot exceed 30 characters") .optional() // Make it optional if user doesn't have one initially .or(z.literal("")), // Allow empty string }); type ProfileFormData = z.infer; export default function ProfilePage() { const queryClient = useQueryClient(); const [isEditing, setIsEditing] = useState(false); // Fetch current user data const { data: userData, isLoading, isError, error, } = useQuery({ queryKey: ["userMe"], queryFn: fetchUserMe, staleTime: 5 * 60 * 1000, // Cache for 5 minutes }); const user = userData?.user; // Setup react-hook-form const form = useForm({ resolver: zodResolver(profileSchema), defaultValues: { username: "", }, }); // Populate form when user data loads or edit mode changes useEffect(() => { if (user) { form.reset({ username: user.username || "", }); } }, [user, isEditing, form.reset]); // Mutation for updating profile const mutation = useMutation({ mutationFn: updateUserProfile, onSuccess: (updatedUser) => { toast.success("Profile updated successfully!"); // Update the cache with the new user data queryClient.setQueryData(["userMe"], { user: updatedUser }); setIsEditing(false); // Exit edit mode }, onError: (error) => { toast.error(`Failed to update profile: ${error.message}`); }, }); const handleEditToggle = () => { if (isEditing) { // Reset form to original values if canceling edit form.reset({ username: user?.username || "" }); } setIsEditing(!isEditing); }; const onSubmit = (formData: ProfileFormData) => { const updatePayload: UpdateUserProfileInput = {}; // Only include username if it actually changed if (formData.username !== undefined && formData.username !== (user?.username || "")) { // Allow setting to empty string if desired, or add validation to prevent it updatePayload.username = formData.username; } if (Object.keys(updatePayload).length > 0) { mutation.mutate(updatePayload); } else { // No changes were made setIsEditing(false); // Just exit edit mode } }; // --- Render Logic --- if (isLoading) { return (
); } if (isError) { return
Error loading profile: {(error as Error)?.message}
; } if (!user) { return
User data not found.
; } return (

User Profile

Account Information View and manage your personal details.
{/* Avatar Section (Placeholder for upload) */}
{user.username ? user.username.charAt(0).toUpperCase() : user.email.charAt(0).toUpperCase()}
{/* Username Field */} ( )} /> {/* Email Field (Read-only) */}

Email cannot be changed currently.

{/* User ID (Read-only) */}
{/* Save Button (Visible only in edit mode) */} {isEditing && ( <>
)}
); }