207 lines
8.7 KiB
TypeScript
207 lines
8.7 KiB
TypeScript
import ExternalLinkIcon from "@/assets/icons/external-link.svg?react";
|
|
import EyeIcon from "@/assets/icons/eye.svg?react";
|
|
import TrendingUpIcon from "@/assets/icons/trending-up.svg?react";
|
|
import AlertTriangleIcon from "@/assets/report/alert-triangle.svg?react";
|
|
import CheckCircleIcon from "@/assets/report/check-circle.svg?react";
|
|
import FacebookMarkIcon from "@/assets/report/facebook-mark.svg?react";
|
|
import ImageIcon from "@/assets/report/image.svg?react";
|
|
import Link2Icon from "@/assets/report/link-2.svg?react";
|
|
import MessageCircleIcon from "@/assets/report/message-circle.svg?react";
|
|
import type { FacebookPage } from "@/features/report/types/facebookAudit";
|
|
import { facebookLangBadgeClass } from "@/features/report/ui/facebook/langBadgeClass";
|
|
import { formatCompactNumber } from "@/lib/formatNumber";
|
|
import { safeUrl } from "@/lib/safeUrl";
|
|
|
|
export type FacebookPageCardProps = {
|
|
page: FacebookPage;
|
|
index: number;
|
|
};
|
|
|
|
export function FacebookPageCard({ page, index }: FacebookPageCardProps) {
|
|
const isKR = page.language === "KR";
|
|
const isLogoMismatch = page.logo.includes("불일치");
|
|
const isLowFollowers = page.followers < 500;
|
|
const lowEngagement = page.engagement?.includes("0~3") ?? false;
|
|
const domainMismatch = page.linkedDomain?.includes("다름") ?? false;
|
|
|
|
return (
|
|
<article
|
|
className={`rounded-2xl border p-6 card-shadow animate-fade-in-up ${
|
|
isKR && isLowFollowers
|
|
? "bg-[var(--color-status-critical-bg)]/30 border-[var(--color-status-critical-border)]/60"
|
|
: "bg-white border-neutral-20"
|
|
}`}
|
|
style={{ animationDelay: `${index * 100}ms` }}
|
|
>
|
|
<div className="flex items-center justify-between gap-2 mb-4 flex-wrap">
|
|
<div className="flex items-center gap-2 flex-wrap min-w-0">
|
|
<span
|
|
className={`label-12 font-medium px-3 py-1 rounded-full break-keep ${facebookLangBadgeClass(page.language)}`}
|
|
>
|
|
{page.label}
|
|
</span>
|
|
<div className="w-8 h-8 rounded-lg bg-[#1877F2] flex items-center justify-center text-white shrink-0 [&_svg]:block">
|
|
<FacebookMarkIcon width={16} height={16} aria-hidden />
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-wrap gap-2 justify-end">
|
|
{isKR && isLowFollowers ? (
|
|
<span className="label-12 font-medium px-3 py-1 rounded-full bg-[var(--color-status-critical-bg)] text-[var(--color-status-critical-text)] break-keep">
|
|
방치 상태
|
|
</span>
|
|
) : null}
|
|
{page.hasWhatsApp ? (
|
|
<span className="label-12 font-medium px-3 py-1 rounded-full bg-[var(--color-status-good-bg)] text-[var(--color-status-good-text)] break-keep">
|
|
WhatsApp 연결
|
|
</span>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
<h3 className="title-18 text-navy-900 mb-1 break-keep">{page.pageName}</h3>
|
|
<p className="label-12 text-neutral-60 mb-4 break-keep">{page.category}</p>
|
|
|
|
<div className="grid grid-cols-3 gap-2 mb-5">
|
|
<div className="rounded-xl bg-neutral-10 p-3 text-center">
|
|
<p className="label-12 text-neutral-60 break-keep">팔로워</p>
|
|
<p
|
|
className={`title-18 ${
|
|
isLowFollowers ? "text-[var(--color-status-critical-text)]" : "text-navy-900"
|
|
}`}
|
|
>
|
|
{formatCompactNumber(page.followers)}
|
|
</p>
|
|
</div>
|
|
<div className="rounded-xl bg-neutral-10 p-3 text-center">
|
|
<p className="label-12 text-neutral-60 break-keep">리뷰</p>
|
|
<p
|
|
className={`title-18 ${
|
|
page.reviews === 0 ? "text-[var(--color-status-critical-text)]" : "text-navy-900"
|
|
}`}
|
|
>
|
|
{page.reviews}
|
|
</p>
|
|
</div>
|
|
<div className="rounded-xl bg-neutral-10 p-3 text-center">
|
|
<p className="label-12 text-neutral-60 break-keep">팔로잉</p>
|
|
<p className="title-18 text-navy-900">{formatCompactNumber(page.following)}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-3 mb-5 body-14">
|
|
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
|
|
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
|
|
<EyeIcon width={13} height={13} className="shrink-0" aria-hidden />
|
|
최근 게시물
|
|
</span>
|
|
<span className="body-14-medium text-navy-900 text-right break-keep">{page.recentPostAge}</span>
|
|
</div>
|
|
{page.postFrequency ? (
|
|
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
|
|
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
|
|
<TrendingUpIcon width={13} height={13} className="shrink-0" aria-hidden />
|
|
게시 빈도
|
|
</span>
|
|
<span className="body-14-medium text-navy-900 text-right break-keep min-w-0">{page.postFrequency}</span>
|
|
</div>
|
|
) : null}
|
|
{page.topContentType ? (
|
|
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
|
|
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
|
|
<ImageIcon width={13} height={13} className="shrink-0" aria-hidden />
|
|
콘텐츠 유형
|
|
</span>
|
|
<span className="body-14-medium text-navy-900 text-right text-xs max-w-[180px] min-w-0 break-keep">
|
|
{page.topContentType}
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
{page.engagement ? (
|
|
<div className="flex items-center justify-between gap-2 py-1 border-b border-neutral-20 last:border-0">
|
|
<span className="text-neutral-60 flex items-center gap-2 shrink-0 break-keep">
|
|
<MessageCircleIcon width={13} height={13} className="shrink-0" aria-hidden />
|
|
참여율
|
|
</span>
|
|
<span
|
|
className={`body-14-medium text-right text-xs max-w-[180px] min-w-0 break-keep ${
|
|
lowEngagement ? "text-[var(--color-status-critical-text)]" : "text-navy-900"
|
|
}`}
|
|
>
|
|
{page.engagement}
|
|
</span>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
|
|
<div
|
|
className={`rounded-xl p-4 mb-4 ${
|
|
isLogoMismatch
|
|
? "bg-[var(--color-status-critical-bg)] border border-[var(--color-status-critical-border)]"
|
|
: "bg-[var(--color-status-good-bg)] border border-[var(--color-status-good-border)]"
|
|
}`}
|
|
>
|
|
<div className="flex items-start gap-2 mb-2">
|
|
{isLogoMismatch ? (
|
|
<AlertTriangleIcon
|
|
width={16}
|
|
height={16}
|
|
className="text-[var(--color-status-critical-text)] shrink-0 mt-0.5"
|
|
aria-hidden
|
|
/>
|
|
) : (
|
|
<CheckCircleIcon
|
|
width={16}
|
|
height={16}
|
|
className="text-[var(--color-status-good-dot)] shrink-0 mt-0.5"
|
|
aria-hidden
|
|
/>
|
|
)}
|
|
<p
|
|
className={`body-14-medium break-keep ${
|
|
isLogoMismatch ? "text-[var(--color-status-critical-text)]" : "text-[var(--color-status-good-text)]"
|
|
}`}
|
|
>
|
|
로고 {page.logo}
|
|
</p>
|
|
</div>
|
|
<p
|
|
className={`label-12 leading-relaxed ml-6 break-keep ${
|
|
isLogoMismatch ? "text-[var(--color-status-critical-text)]" : "text-[var(--color-status-good-text)]"
|
|
}`}
|
|
>
|
|
{page.logoDescription}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="rounded-xl bg-neutral-10 p-3 mb-4">
|
|
<div className="flex items-center gap-2 mb-1">
|
|
<Link2Icon width={12} height={12} className="text-neutral-60 shrink-0" aria-hidden />
|
|
<p className="label-12 text-neutral-60 uppercase tracking-wide break-keep">연결 도메인</p>
|
|
</div>
|
|
<p
|
|
className={`body-14 font-mono break-keep ${
|
|
domainMismatch ? "text-[var(--color-status-warning-text)]" : "text-neutral-70"
|
|
}`}
|
|
>
|
|
{page.linkedDomain || page.link}
|
|
</p>
|
|
</div>
|
|
|
|
<div className="rounded-xl bg-neutral-10 p-3 mb-4">
|
|
<p className="label-12 text-neutral-60 uppercase tracking-wide mb-1 break-keep">Bio</p>
|
|
<p className="body-14 text-neutral-70 italic break-keep">"{page.bio}"</p>
|
|
</div>
|
|
|
|
<a
|
|
href={safeUrl(page.url)}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="inline-flex items-center gap-1 label-12 text-violet-700 hover:underline break-all cursor-pointer"
|
|
>
|
|
<ExternalLinkIcon aria-hidden />
|
|
<span className="break-keep">{page.url}</span>
|
|
</a>
|
|
</article>
|
|
);
|
|
}
|