full update
This commit is contained in:
262
src/hooks/usePostSocialActions.tsx
Normal file
262
src/hooks/usePostSocialActions.tsx
Normal file
@@ -0,0 +1,262 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { Post } from "@/types/types";
|
||||
import useAxios from "@/hooks/useAxios";
|
||||
import toast from "react-hot-toast";
|
||||
import { AxiosError } from "axios";
|
||||
import { useRequireAuth } from "@/lib/auth/useRequireAuth";
|
||||
import { getStoredUserId } from "@/lib/auth/session";
|
||||
import useInfiniteScroll from "@/hooks/useInfiniteScroll";
|
||||
import { buildPostPublicUrl } from "@/lib/postSlug";
|
||||
import Cookies from "js-cookie";
|
||||
import { trackExploreInteraction } from "@/api/trackExploreInteraction";
|
||||
|
||||
export function usePostSocialState(postData: Post) {
|
||||
const {
|
||||
_id: postId,
|
||||
user_id,
|
||||
likesCount,
|
||||
is_liked,
|
||||
savesCount: initialSaves = 0,
|
||||
sendsCount: initialSends = 0,
|
||||
is_saved: initialSaved = false,
|
||||
is_following: initialFollowing = false,
|
||||
type: postType,
|
||||
} = postData;
|
||||
|
||||
const [liked, setLiked] = useState(is_liked || false);
|
||||
const [likesCountState, setLikesCount] = useState(likesCount || 0);
|
||||
const [saved, setSaved] = useState(initialSaved);
|
||||
const [savesCount, setSavesCount] = useState(initialSaves);
|
||||
const [sendsCount, setSendsCount] = useState(initialSends);
|
||||
const [following, setFollowing] = useState(initialFollowing);
|
||||
|
||||
const { request, loading } = useAxios();
|
||||
const requireAuth = useRequireAuth();
|
||||
const currentUserId = getStoredUserId();
|
||||
const isOwnPost = Boolean(currentUserId && user_id && currentUserId === String(user_id));
|
||||
|
||||
const shareUrl = buildPostPublicUrl(postId, postData);
|
||||
|
||||
const { data } = useInfiniteScroll({
|
||||
endpoint: "/users/comments",
|
||||
queryKey: ["comments", user_id, postId || "all"],
|
||||
params: postId ? { user_id, post_id: postId } : { user_id },
|
||||
});
|
||||
|
||||
const commentCount =
|
||||
data?.pages
|
||||
?.flatMap((p) => p?.comments || [])
|
||||
.filter((c: { post_id?: string; postId?: string }) =>
|
||||
postId
|
||||
? c.post_id === postId || c.postId === postId || (!c.post_id && !c.postId)
|
||||
: true
|
||||
).length ?? 0;
|
||||
|
||||
const toggleLike = useCallback(async () => {
|
||||
if (!requireAuth()) return;
|
||||
if (!user_id || !postId) {
|
||||
toast.error("لطفاً وارد حساب کاربری خود شوید");
|
||||
return;
|
||||
}
|
||||
if (loading) return;
|
||||
|
||||
const newLiked = !liked;
|
||||
const newLikesCount = newLiked ? likesCountState + 1 : likesCountState - 1;
|
||||
setLiked(newLiked);
|
||||
setLikesCount(newLikesCount);
|
||||
|
||||
try {
|
||||
const response = await request<{ likesCount?: number; is_liked?: boolean }>(
|
||||
"POST",
|
||||
"/posts/like",
|
||||
{ postId },
|
||||
{ noToast: true }
|
||||
);
|
||||
setLikesCount(response?.likesCount ?? newLikesCount);
|
||||
setLiked(response?.is_liked ?? newLiked);
|
||||
|
||||
const token = Cookies.get("token") || "";
|
||||
if (token && newLiked) {
|
||||
trackExploreInteraction(
|
||||
{
|
||||
targetType: "post",
|
||||
targetId: postId,
|
||||
authorId: user_id,
|
||||
contentType: postType === "video" ? "video" : "image",
|
||||
action: "like",
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
let errorMessage = "خطا در ارتباط با سرور";
|
||||
if (error instanceof Error) errorMessage = error.message;
|
||||
else if (error instanceof AxiosError)
|
||||
errorMessage = error.response?.data?.message ?? error.message;
|
||||
toast.error(errorMessage);
|
||||
setLiked(!newLiked);
|
||||
setLikesCount(likesCountState);
|
||||
}
|
||||
}, [
|
||||
requireAuth,
|
||||
user_id,
|
||||
postId,
|
||||
loading,
|
||||
liked,
|
||||
likesCountState,
|
||||
request,
|
||||
postType,
|
||||
]);
|
||||
|
||||
const toggleSave = useCallback(async () => {
|
||||
if (!requireAuth()) return;
|
||||
if (!postId || loading) return;
|
||||
|
||||
const newSaved = !saved;
|
||||
const newCount = newSaved ? savesCount + 1 : Math.max(0, savesCount - 1);
|
||||
setSaved(newSaved);
|
||||
setSavesCount(newCount);
|
||||
|
||||
try {
|
||||
const response = await request<{ savesCount?: number; is_saved?: boolean }>(
|
||||
"POST",
|
||||
"/posts/bookmark",
|
||||
{ postId },
|
||||
{ noToast: true }
|
||||
);
|
||||
setSavesCount(response?.savesCount ?? newCount);
|
||||
setSaved(response?.is_saved ?? newSaved);
|
||||
} catch (error: unknown) {
|
||||
toast.error("خطا در ذخیره پست");
|
||||
setSaved(!newSaved);
|
||||
setSavesCount(savesCount);
|
||||
}
|
||||
}, [requireAuth, postId, loading, saved, savesCount, request]);
|
||||
|
||||
const toggleFollow = useCallback(async () => {
|
||||
if (!requireAuth()) return;
|
||||
if (!user_id || isOwnPost || loading) return;
|
||||
|
||||
const newFollowing = !following;
|
||||
setFollowing(newFollowing);
|
||||
|
||||
try {
|
||||
const response = await request<{ is_following?: boolean }>(
|
||||
"POST",
|
||||
"/posts/follow",
|
||||
{ userId: user_id },
|
||||
{ noToast: true }
|
||||
);
|
||||
setFollowing(response?.is_following ?? newFollowing);
|
||||
toast.success(newFollowing ? "فالو شد" : "آنفالو شد");
|
||||
} catch {
|
||||
toast.error("خطا در فالو");
|
||||
setFollowing(!newFollowing);
|
||||
}
|
||||
}, [requireAuth, user_id, isOwnPost, loading, following, request]);
|
||||
|
||||
const trackShare = useCallback(() => {
|
||||
const token = Cookies.get("token") || "";
|
||||
if (token) {
|
||||
trackExploreInteraction(
|
||||
{
|
||||
targetType: "post",
|
||||
targetId: postId,
|
||||
authorId: user_id,
|
||||
contentType: postType === "video" ? "video" : "image",
|
||||
action: "share",
|
||||
},
|
||||
token
|
||||
);
|
||||
}
|
||||
}, [postId, user_id, postType]);
|
||||
|
||||
return {
|
||||
liked,
|
||||
likesCountState,
|
||||
saved,
|
||||
savesCount,
|
||||
sendsCount,
|
||||
setSendsCount,
|
||||
following,
|
||||
commentCount,
|
||||
shareUrl,
|
||||
currentUserId,
|
||||
isOwnPost,
|
||||
toggleLike,
|
||||
toggleSave,
|
||||
toggleFollow,
|
||||
trackShare,
|
||||
};
|
||||
}
|
||||
|
||||
export type PostSocialIconSize = "sm" | "md";
|
||||
|
||||
export const POST_SOCIAL_ICONS = {
|
||||
like: (liked: boolean, size: PostSocialIconSize) => ({
|
||||
src: liked ? "/images/icons/red-heart.svg" : "/images/icons/heart.svg",
|
||||
w: size === "sm" ? 20 : 28,
|
||||
className: liked
|
||||
? "drop-shadow-lg"
|
||||
: size === "sm"
|
||||
? "dark:invert drop-shadow-lg"
|
||||
: "invert drop-shadow-lg",
|
||||
}),
|
||||
comment: (size: PostSocialIconSize) => ({
|
||||
src: "/images/icons/message-text.svg",
|
||||
w: size === "sm" ? 20 : 28,
|
||||
className: size === "sm" ? "dark:invert" : "invert drop-shadow-lg",
|
||||
}),
|
||||
share: (size: PostSocialIconSize) => ({
|
||||
src: "/images/icons/share.svg",
|
||||
w: size === "sm" ? 20 : 28,
|
||||
className: size === "sm" ? "dark:invert" : "invert drop-shadow-lg",
|
||||
}),
|
||||
send: (size: PostSocialIconSize) => ({
|
||||
src: "/images/icons/send-post.svg",
|
||||
w: size === "sm" ? 20 : 28,
|
||||
className: size === "sm" ? "dark:invert" : "invert drop-shadow-lg",
|
||||
}),
|
||||
save: (saved: boolean, size: PostSocialIconSize) => ({
|
||||
src: "/images/icons/bookmark-post.svg",
|
||||
w: size === "sm" ? 20 : 28,
|
||||
className: saved
|
||||
? "drop-shadow-lg"
|
||||
: size === "sm"
|
||||
? "dark:invert"
|
||||
: "invert drop-shadow-lg",
|
||||
}),
|
||||
follow: (following: boolean, size: PostSocialIconSize) => ({
|
||||
src: "/images/icons/follow-user.svg",
|
||||
w: size === "sm" ? 20 : 28,
|
||||
className: following
|
||||
? "opacity-60 drop-shadow-lg"
|
||||
: size === "sm"
|
||||
? "dark:invert"
|
||||
: "invert drop-shadow-lg",
|
||||
}),
|
||||
collaboration: (size: PostSocialIconSize) => ({
|
||||
src: "/images/icons/collaboration-request.svg",
|
||||
w: size === "sm" ? 20 : 28,
|
||||
className: size === "sm" ? "dark:invert" : "invert drop-shadow-lg",
|
||||
}),
|
||||
} as const;
|
||||
|
||||
export function PostSocialIcon({
|
||||
src,
|
||||
w,
|
||||
className,
|
||||
alt = "",
|
||||
}: {
|
||||
src: string;
|
||||
w: number;
|
||||
className?: string;
|
||||
alt?: string;
|
||||
}) {
|
||||
return (
|
||||
<Image width={w} height={w} alt={alt} src={src} className={className} />
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user