refactor(frontend): FollowButton useOptimistic + Server Action; edit-profile-form Server Action
This commit is contained in:
@@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user