refactor(frontend): FollowButton useOptimistic + Server Action; edit-profile-form Server Action

This commit is contained in:
2026-05-15 20:08:49 +02:00
parent 71233f069e
commit 688e7b0018
2 changed files with 26 additions and 57 deletions

View File

@@ -3,9 +3,8 @@
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { useRouter } from "next/navigation"; import { Me, UpdateProfileSchema } from "@/lib/api";
import { useAuth } from "@/hooks/use-auth"; import { updateProfile } from "@/app/actions/profile";
import { Me, UpdateProfileSchema, updateProfile } from "@/lib/api";
import { toast } from "sonner"; import { toast } from "sonner";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter } from "@/components/ui/card"; import { Card, CardContent, CardFooter } from "@/components/ui/card";
@@ -25,8 +24,6 @@ interface EditProfileFormProps {
} }
export function EditProfileForm({ currentUser }: EditProfileFormProps) { export function EditProfileForm({ currentUser }: EditProfileFormProps) {
const router = useRouter();
const { token } = useAuth();
const form = useForm<z.infer<typeof UpdateProfileSchema>>({ const form = useForm<z.infer<typeof UpdateProfileSchema>>({
resolver: zodResolver(UpdateProfileSchema), resolver: zodResolver(UpdateProfileSchema),
@@ -40,13 +37,10 @@ export function EditProfileForm({ currentUser }: EditProfileFormProps) {
}); });
async function onSubmit(values: z.infer<typeof UpdateProfileSchema>) { async function onSubmit(values: z.infer<typeof UpdateProfileSchema>) {
if (!token) return;
toast.info("Updating your profile..."); toast.info("Updating your profile...");
try { try {
await updateProfile(values, token); await updateProfile(currentUser.username, values);
toast.success("Profile updated successfully!"); toast.success("Profile updated successfully!");
router.push(`/users/${currentUser.username}`);
router.refresh();
} catch (err) { } catch (err) {
toast.error(`Failed to update profile. ${err}`); toast.error(`Failed to update profile. ${err}`);
} }

View File

@@ -1,66 +1,41 @@
"use client"; "use client"
import { useState } from "react"; import { useOptimistic } from "react"
import { useRouter } from "next/navigation"; import { followUser, unfollowUser } from "@/app/actions/social"
import { useAuth } from "@/hooks/use-auth"; import { Button } from "@/components/ui/button"
import { followUser, unfollowUser } from "@/lib/api"; import { toast } from "sonner"
import { Button } from "@/components/ui/button"; import { UserPlus, UserMinus } from "lucide-react"
import { toast } from "sonner";
import { UserPlus, UserMinus } from "lucide-react";
interface FollowButtonProps { interface FollowButtonProps {
username: string; username: string
isInitiallyFollowing: boolean; isInitiallyFollowing: boolean
} }
export function FollowButton({ export function FollowButton({ username, isInitiallyFollowing }: FollowButtonProps) {
username, const [optimisticFollowing, setOptimisticFollowing] = useOptimistic(isInitiallyFollowing)
isInitiallyFollowing,
}: FollowButtonProps) {
const [isFollowing, setIsFollowing] = useState(isInitiallyFollowing);
const [isLoading, setIsLoading] = useState(false);
const { token } = useAuth();
const router = useRouter();
const handleClick = async () => {
if (!token) {
toast.error("You must be logged in to follow users.");
return;
}
setIsLoading(true);
const action = isFollowing ? unfollowUser : followUser;
async function handleClick() {
const next = !optimisticFollowing
setOptimisticFollowing(next)
try { try {
// Optimistic update await (next ? followUser(username) : unfollowUser(username))
setIsFollowing(!isFollowing);
await action(username, token);
router.refresh(); // Re-fetch server component data to get the latest follower count etc.
} catch { } catch {
// Revert on error setOptimisticFollowing(!next) // revert
setIsFollowing(isFollowing); toast.error(`Failed to ${next ? "follow" : "unfollow"} user.`)
toast.error(`Failed to ${isFollowing ? "unfollow" : "follow"} user.`);
} finally {
setIsLoading(false);
} }
}; }
return ( return (
<Button <Button
onClick={handleClick} onClick={handleClick}
disabled={isLoading} variant={optimisticFollowing ? "secondary" : "default"}
variant={isFollowing ? "secondary" : "default"} data-following={optimisticFollowing}
data-following={isFollowing}
> >
{isFollowing ? ( {optimisticFollowing ? (
<> <><UserMinus className="mr-2 h-4 w-4" /> Unfollow</>
<UserMinus className="mr-2 h-4 w-4" /> Unfollow
</>
) : ( ) : (
<> <><UserPlus className="mr-2 h-4 w-4" /> Follow</>
<UserPlus className="mr-2 h-4 w-4" /> Follow
</>
)} )}
</Button> </Button>
); )
} }