fix(profile): wire blog activity data
This commit is contained in:
@@ -13,21 +13,23 @@ import {
|
|||||||
Loader2,
|
Loader2,
|
||||||
MessageSquareText,
|
MessageSquareText,
|
||||||
PencilLine,
|
PencilLine,
|
||||||
Reply,
|
|
||||||
Trash2,
|
Trash2,
|
||||||
UserRound,
|
UserRound,
|
||||||
XCircle,
|
XCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import BlogThumbnail from "@/components/BlogThumbnail";
|
||||||
import Markdown from "@/components/Markdown";
|
import Markdown from "@/components/Markdown";
|
||||||
import { Helmet } from "@/lib/helmet";
|
import { Helmet } from "@/lib/helmet";
|
||||||
import { Link, Navigate } from "@/lib/router";
|
import { Link, Navigate } from "@/lib/router";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
import { api } from "@/lib/api";
|
import { api } from "@/lib/api";
|
||||||
import { blogPostPath } from "@/lib/blog-routes";
|
import { blogPostPath } from "@/lib/blog-routes";
|
||||||
|
import { apiBaseUrl, toAbsoluteUrl } from "@/lib/site";
|
||||||
import type * as Types from "@/lib/types";
|
import type * as Types from "@/lib/types";
|
||||||
import {
|
import {
|
||||||
formatJalali,
|
formatJalali,
|
||||||
formatNumberPersian,
|
formatNumberPersian,
|
||||||
|
getBlogCardImageUrl,
|
||||||
resolveErrorMessage,
|
resolveErrorMessage,
|
||||||
toPersianDigits,
|
toPersianDigits,
|
||||||
} from "@/lib/utils";
|
} from "@/lib/utils";
|
||||||
@@ -41,7 +43,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
||||||
type EventTab = "confirmed" | "pending" | "cancelled";
|
type EventTab = "confirmed" | "pending" | "cancelled";
|
||||||
type BlogTab = "liked" | "saved" | "comments" | "replies";
|
type BlogTab = "liked" | "saved" | "comments";
|
||||||
|
|
||||||
function InfoRow({ label, value }: { label: string; value: ReactNode }) {
|
function InfoRow({ label, value }: { label: string; value: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
@@ -144,7 +146,12 @@ export default function Profile() {
|
|||||||
staleTime: 7 * 24 * 60 * 60 * 1000,
|
staleTime: 7 * 24 * 60 * 60 * 1000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: blogActivity } = useQuery({
|
const {
|
||||||
|
data: blogActivity,
|
||||||
|
isLoading: blogActivityLoading,
|
||||||
|
isError: blogActivityError,
|
||||||
|
refetch: refetchBlogActivity,
|
||||||
|
} = useQuery({
|
||||||
queryKey: ["my-blog-activity"],
|
queryKey: ["my-blog-activity"],
|
||||||
queryFn: () => api.getMyBlogActivity(),
|
queryFn: () => api.getMyBlogActivity(),
|
||||||
enabled: isAuthenticated,
|
enabled: isAuthenticated,
|
||||||
@@ -261,12 +268,20 @@ export default function Profile() {
|
|||||||
? pendingRegistrations
|
? pendingRegistrations
|
||||||
: canceledRegistrations;
|
: canceledRegistrations;
|
||||||
|
|
||||||
|
const blogCommentActivity = useMemo(
|
||||||
|
() =>
|
||||||
|
[...(blogActivity?.comments ?? []), ...(blogActivity?.replies ?? [])].sort(
|
||||||
|
(left, right) => new Date(right.created_at).getTime() - new Date(left.created_at).getTime(),
|
||||||
|
),
|
||||||
|
[blogActivity?.comments, blogActivity?.replies],
|
||||||
|
);
|
||||||
|
|
||||||
const blogCounts = {
|
const blogCounts = {
|
||||||
liked: blogActivity?.liked_posts.length ?? 0,
|
liked: blogActivity?.liked_posts.length ?? 0,
|
||||||
saved: blogActivity?.saved_posts.length ?? 0,
|
saved: blogActivity?.saved_posts.length ?? 0,
|
||||||
comments: blogActivity?.comments.length ?? 0,
|
comments: blogCommentActivity.length,
|
||||||
replies: blogActivity?.replies.length ?? 0,
|
|
||||||
};
|
};
|
||||||
|
const showBlogActivityTabs = !blogActivityLoading && !blogActivityError;
|
||||||
|
|
||||||
const pageTitle = `پروفایل ${fullName} | انجمن علمی مهندسی کامپیوتر`;
|
const pageTitle = `پروفایل ${fullName} | انجمن علمی مهندسی کامپیوتر`;
|
||||||
const pageDescription = `داشبورد حساب ${fullName} در انجمن علمی مهندسی کامپیوتر.`;
|
const pageDescription = `داشبورد حساب ${fullName} در انجمن علمی مهندسی کامپیوتر.`;
|
||||||
@@ -410,10 +425,21 @@ export default function Profile() {
|
|||||||
|
|
||||||
const renderPostRow = (post: Types.PostListSchema) => (
|
const renderPostRow = (post: Types.PostListSchema) => (
|
||||||
<div key={post.id} className="rounded-2xl border border-border/70 bg-background/80 p-4 text-right">
|
<div key={post.id} className="rounded-2xl border border-border/70 bg-background/80 p-4 text-right">
|
||||||
<Link to={blogPostPath(post.slug)} className="font-medium text-primary hover:underline">
|
<div className="flex flex-row gap-4">
|
||||||
{post.title}
|
<Link to={blogPostPath(post.slug)} className="block w-24 shrink-0 overflow-hidden rounded-2xl sm:w-28">
|
||||||
</Link>
|
<BlogThumbnail
|
||||||
<p className="mt-2 line-clamp-2 text-sm text-muted-foreground">{post.excerpt || "بدون خلاصه"}</p>
|
post={post}
|
||||||
|
imageUrl={toAbsoluteUrl(getBlogCardImageUrl(post), apiBaseUrl)}
|
||||||
|
className="aspect-square rounded-2xl"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<Link to={blogPostPath(post.slug)} className="font-medium text-primary hover:underline">
|
||||||
|
{post.title}
|
||||||
|
</Link>
|
||||||
|
<p className="mt-2 line-clamp-2 text-sm text-muted-foreground">{post.excerpt || "بدون خلاصه"}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -450,7 +476,7 @@ export default function Profile() {
|
|||||||
<div className="bg-background px-4 py-8 md:py-10" dir="rtl">
|
<div className="bg-background px-4 py-8 md:py-10" dir="rtl">
|
||||||
<div className="container mx-auto max-w-6xl space-y-6">
|
<div className="container mx-auto max-w-6xl space-y-6">
|
||||||
{(loading || fetching) && !me ? (
|
{(loading || fetching) && !me ? (
|
||||||
<div className="flex min-h-[50vh] items-center justify-center gap-3 text-muted-foreground">
|
<div className="flex min-h-[75vh] items-center justify-center gap-3 text-muted-foreground">
|
||||||
<Loader2 className="h-5 w-5 animate-spin" />
|
<Loader2 className="h-5 w-5 animate-spin" />
|
||||||
<span>در حال بارگذاری پروفایل...</span>
|
<span>در حال بارگذاری پروفایل...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -650,32 +676,46 @@ export default function Profile() {
|
|||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="flex flex-col gap-4 lg:flex-row-reverse">
|
<div className="flex flex-col gap-4 lg:flex-row-reverse">
|
||||||
<div className="min-w-0 flex-1 space-y-3">
|
<div className="min-w-0 flex-1 space-y-3">
|
||||||
{blogTab === "liked" ? (
|
{blogActivityLoading ? (
|
||||||
|
<div className="rounded-2xl border border-dashed p-8 text-center text-sm text-muted-foreground flex flex-col justify-center h-full">
|
||||||
|
<Loader2 className="mx-auto mb-3 h-5 w-5 animate-spin text-primary" />
|
||||||
|
در حال دریافت فعالیتهای بلاگ...
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{blogActivityError ? (
|
||||||
|
<div className="rounded-2xl border border-dashed p-8 text-center text-sm text-muted-foreground flex flex-col justify-center h-full">
|
||||||
|
دریافت فعالیتهای بلاگ ناموفق بود.
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="mx-auto mt-3 rounded-full"
|
||||||
|
onClick={() => void refetchBlogActivity()}
|
||||||
|
>
|
||||||
|
تلاش دوباره
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{showBlogActivityTabs && blogTab === "liked" ? (
|
||||||
blogActivity?.liked_posts.length ? blogActivity.liked_posts.map(renderPostRow) : (
|
blogActivity?.liked_posts.length ? blogActivity.liked_posts.map(renderPostRow) : (
|
||||||
<EmptyBlogActivity icon={Heart} text="هنوز پستی را نپسندیدهاید." />
|
<EmptyBlogActivity icon={Heart} text="هنوز پستی را نپسندیدهاید." />
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
{blogTab === "saved" ? (
|
{showBlogActivityTabs && blogTab === "saved" ? (
|
||||||
blogActivity?.saved_posts.length ? blogActivity.saved_posts.map(renderPostRow) : (
|
blogActivity?.saved_posts.length ? blogActivity.saved_posts.map(renderPostRow) : (
|
||||||
<EmptyBlogActivity icon={Bookmark} text="هنوز پستی را ذخیره نکردهاید." />
|
<EmptyBlogActivity icon={Bookmark} text="هنوز پستی را ذخیره نکردهاید." />
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
{blogTab === "comments" ? (
|
{showBlogActivityTabs && blogTab === "comments" ? (
|
||||||
blogActivity?.comments.length ? blogActivity.comments.map(renderCommentRow) : (
|
blogCommentActivity.length ? blogCommentActivity.map(renderCommentRow) : (
|
||||||
<EmptyBlogActivity icon={MessageSquareText} text="هنوز نظری ثبت نکردهاید." />
|
<EmptyBlogActivity icon={MessageSquareText} text="هنوز نظری ثبت نکردهاید." />
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
{blogTab === "replies" ? (
|
|
||||||
blogActivity?.replies.length ? blogActivity.replies.map(renderCommentRow) : (
|
|
||||||
<EmptyBlogActivity icon={Reply} text="هنوز پاسخی ثبت نکردهاید." />
|
|
||||||
)
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 overflow-x-auto pb-1 lg:w-60 lg:flex-col lg:overflow-visible lg:pb-0">
|
<div className="flex gap-2 overflow-x-auto pb-1 lg:w-60 lg:flex-col lg:overflow-visible lg:pb-0">
|
||||||
<TabButton value="liked" active={blogTab} label="پسندیدهها" count={blogCounts.liked} onClick={(value) => setBlogTab(value as BlogTab)} />
|
<TabButton value="liked" active={blogTab} label="پسندیدهها" count={blogCounts.liked} onClick={(value) => setBlogTab(value as BlogTab)} />
|
||||||
<TabButton value="saved" active={blogTab} label="ذخیرهشدهها" count={blogCounts.saved} onClick={(value) => setBlogTab(value as BlogTab)} />
|
<TabButton value="saved" active={blogTab} label="ذخیرهشدهها" count={blogCounts.saved} onClick={(value) => setBlogTab(value as BlogTab)} />
|
||||||
<TabButton value="comments" active={blogTab} label="نظرها" count={blogCounts.comments} onClick={(value) => setBlogTab(value as BlogTab)} />
|
<TabButton value="comments" active={blogTab} label="نظرات" count={blogCounts.comments} onClick={(value) => setBlogTab(value as BlogTab)} />
|
||||||
<TabButton value="replies" active={blogTab} label="پاسخها" count={blogCounts.replies} onClick={(value) => setBlogTab(value as BlogTab)} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
Reference in New Issue
Block a user