2026-10-07 19:40:26 +03:30
|
|
|
"use client";
|
|
|
|
|
|
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
2026-10-20 17:51:23 +03:30
|
|
|
import { ExternalLink, Share2, Tag, Users } from "lucide-react";
|
2026-10-07 19:40:26 +03:30
|
|
|
import { http, toUserError } from "@/lib/api";
|
|
|
|
|
import { Panel } from "@/components/panel";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
2026-10-20 17:51:23 +03:30
|
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
|
import type { Contributor } from "@/lib/types";
|
2026-10-07 19:40:26 +03:30
|
|
|
|
2026-10-10 16:42:35 +03:30
|
|
|
export type SocialLink = { name: string; url: string };
|
|
|
|
|
|
2026-10-07 19:40:26 +03:30
|
|
|
export type CommunityPayload = {
|
2026-10-10 16:42:35 +03:30
|
|
|
socials: SocialLink[];
|
2026-10-20 17:51:23 +03:30
|
|
|
contributors: Contributor[];
|
2026-10-07 19:40:26 +03:30
|
|
|
};
|
|
|
|
|
|
2026-10-20 17:51:23 +03:30
|
|
|
function ContributorAvatar({ contributor }: { contributor: Contributor }) {
|
|
|
|
|
if (contributor.avatar) {
|
|
|
|
|
return (
|
|
|
|
|
<img
|
|
|
|
|
src={contributor.avatar}
|
|
|
|
|
alt={contributor.name}
|
|
|
|
|
className="h-12 w-12 shrink-0 rounded-full border-2 border-border object-cover"
|
|
|
|
|
onError={(e) => {
|
|
|
|
|
(e.currentTarget as HTMLImageElement).style.display = "none";
|
|
|
|
|
(e.currentTarget.nextElementSibling as HTMLElement | null)?.removeAttribute("hidden");
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
const initials = contributor.name
|
|
|
|
|
.split(" ")
|
|
|
|
|
.map((w) => w[0])
|
|
|
|
|
.slice(0, 2)
|
|
|
|
|
.join("")
|
|
|
|
|
.toUpperCase();
|
|
|
|
|
return (
|
|
|
|
|
<span
|
|
|
|
|
aria-hidden
|
|
|
|
|
className="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border-2 border-border bg-primary/10 text-sm font-bold text-primary"
|
|
|
|
|
>
|
|
|
|
|
{initials}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-07 19:40:26 +03:30
|
|
|
export function CommunityPanel() {
|
|
|
|
|
const { data, isLoading, isError, error, refetch } = useQuery({
|
2026-10-10 16:42:35 +03:30
|
|
|
queryKey: ["community"],
|
2026-10-07 19:40:26 +03:30
|
|
|
queryFn: () => http.get<CommunityPayload>("/api/community"),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (isLoading) {
|
2026-10-20 17:51:23 +03:30
|
|
|
return (
|
|
|
|
|
<div className="mx-auto max-w-2xl space-y-4">
|
|
|
|
|
<div className="h-40 animate-pulse rounded-2xl border-2 border-border bg-muted/40" />
|
|
|
|
|
<div className="h-64 animate-pulse rounded-2xl border-2 border-border bg-muted/40" />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-10-07 19:40:26 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isError) {
|
|
|
|
|
return (
|
2026-10-20 17:51:23 +03:30
|
|
|
<div className="mx-auto max-w-2xl rounded-2xl border-2 border-destructive/30 bg-destructive/5 p-8 text-center">
|
2026-10-07 19:40:26 +03:30
|
|
|
<p className="font-medium text-destructive">{toUserError(error)}</p>
|
|
|
|
|
<Button className="mt-4" variant="outline" onClick={() => refetch()}>
|
|
|
|
|
تلاش دوباره
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-10 16:42:35 +03:30
|
|
|
const socials = data?.socials ?? [];
|
2026-10-20 17:51:23 +03:30
|
|
|
const contributors = data?.contributors ?? [];
|
2026-10-07 19:40:26 +03:30
|
|
|
|
|
|
|
|
return (
|
2026-10-20 17:51:23 +03:30
|
|
|
<div className="mx-auto max-w-2xl space-y-4">
|
|
|
|
|
{/* Social links */}
|
2026-10-07 19:40:26 +03:30
|
|
|
<Panel
|
2026-10-10 16:42:35 +03:30
|
|
|
title="شبکههای پرگار"
|
|
|
|
|
description="راههای رسمی ارتباط با برنامه. لینک شخصی هر کاربر روی پروفایل خودش است."
|
|
|
|
|
icon={<Share2 className="h-5 w-5" />}
|
|
|
|
|
tint="bg-primary/15 text-primary"
|
2026-10-07 19:40:26 +03:30
|
|
|
>
|
2026-10-10 16:42:35 +03:30
|
|
|
{socials.length === 0 ? (
|
|
|
|
|
<p className="py-8 text-center text-sm text-muted-foreground">هنوز شبکهای ثبت نشده است.</p>
|
2026-10-07 19:40:26 +03:30
|
|
|
) : (
|
2026-10-10 16:42:35 +03:30
|
|
|
<ul className="space-y-2 pt-1">
|
|
|
|
|
{socials.map((s) => (
|
|
|
|
|
<li key={s.url}>
|
|
|
|
|
<a
|
|
|
|
|
href={s.url}
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
className="flex items-center justify-between gap-3 rounded-xl border-2 border-border/70 px-3 py-2.5 text-sm font-bold transition-colors hover:border-primary/50 hover:bg-primary/5"
|
|
|
|
|
>
|
|
|
|
|
<span>{s.name}</span>
|
|
|
|
|
<ExternalLink className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
|
|
|
</a>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
2026-10-07 19:40:26 +03:30
|
|
|
)}
|
|
|
|
|
</Panel>
|
2026-10-20 17:51:23 +03:30
|
|
|
|
|
|
|
|
{/* Contributors */}
|
|
|
|
|
<Panel
|
|
|
|
|
title="مشارکتکنندگان"
|
|
|
|
|
description="افرادی که در ساخت پرگار سهیم بودهاند. ممنون از هر کمکی."
|
|
|
|
|
icon={<Users className="h-5 w-5" />}
|
|
|
|
|
tint="bg-emerald-500/15 text-emerald-600 dark:text-emerald-400"
|
|
|
|
|
>
|
|
|
|
|
{contributors.length === 0 ? (
|
|
|
|
|
<p className="py-8 text-center text-sm text-muted-foreground">اطلاعات مشارکتکنندگان هنوز ثبت نشده است.</p>
|
|
|
|
|
) : (
|
|
|
|
|
<ul className="divide-y divide-border/60">
|
|
|
|
|
{contributors.map((c, i) => (
|
|
|
|
|
<li key={i} className="flex flex-col gap-3 py-4 first:pt-0 last:pb-0 sm:flex-row sm:items-start">
|
|
|
|
|
<ContributorAvatar contributor={c} />
|
|
|
|
|
<div className="min-w-0 flex-1 space-y-1.5">
|
|
|
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
|
|
|
{c.url ? (
|
|
|
|
|
<a
|
|
|
|
|
href={c.url}
|
|
|
|
|
target="_blank"
|
|
|
|
|
rel="noreferrer"
|
|
|
|
|
className="flex items-center gap-1 font-bold hover:text-primary hover:underline"
|
|
|
|
|
>
|
|
|
|
|
{c.name}
|
|
|
|
|
<ExternalLink className="h-3 w-3 shrink-0 text-muted-foreground" />
|
|
|
|
|
</a>
|
|
|
|
|
) : (
|
|
|
|
|
<span className="font-bold">{c.name}</span>
|
|
|
|
|
)}
|
|
|
|
|
{c.role && (
|
|
|
|
|
<span className="text-xs text-muted-foreground">{c.role}</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{c.bio && <p className="text-sm leading-relaxed text-muted-foreground">{c.bio}</p>}
|
|
|
|
|
{c.contributions.length > 0 && (
|
|
|
|
|
<div className="flex flex-wrap gap-1.5">
|
|
|
|
|
{c.contributions.map((cat) => (
|
|
|
|
|
<Badge
|
|
|
|
|
key={cat}
|
|
|
|
|
variant="secondary"
|
|
|
|
|
className="gap-1 px-2 py-0.5 text-xs"
|
|
|
|
|
>
|
|
|
|
|
<Tag className="h-3 w-3" />
|
|
|
|
|
{cat}
|
|
|
|
|
</Badge>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</li>
|
|
|
|
|
))}
|
|
|
|
|
</ul>
|
|
|
|
|
)}
|
|
|
|
|
</Panel>
|
2026-10-07 19:40:26 +03:30
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|