feat: add ads/sponsor/captcha modules and polish home & chat UI
This commit is contained in:
@@ -13,6 +13,8 @@ import {
|
||||
TrendingUp,
|
||||
Pin,
|
||||
ChevronRight,
|
||||
CircleDollarSign,
|
||||
Handshake,
|
||||
} from "lucide-react";
|
||||
import type {
|
||||
PostListItem,
|
||||
@@ -22,13 +24,17 @@ import type {
|
||||
NewUser,
|
||||
CheckinStatus,
|
||||
SidebarPageItem,
|
||||
PublicAdItem,
|
||||
PublicSponsorItem,
|
||||
} from "@/lib/api";
|
||||
import Avatar from "@/components/Avatar";
|
||||
import { AdText } from "@/components/AdText";
|
||||
import { PulseCheckin } from "./CheckinCard";
|
||||
import { FRIEND_LINKS } from "@/lib/friendLinks";
|
||||
import { formatCompact, formatShortDate } from "@/lib/format";
|
||||
import { pageGlyph } from "@/lib/pageGlyph";
|
||||
import { AboutSiteHeader } from "@/components/site-doc";
|
||||
import { AD_IMAGE_HEIGHT, AD_IMAGE_SIZE_LABEL, AD_IMAGE_WIDTH } from "@/lib/ads";
|
||||
|
||||
/* ---------- 站点数据(2×2 bento 统计瓦片) ---------- */
|
||||
|
||||
@@ -427,8 +433,157 @@ type PanelsProps = {
|
||||
canManagePages?: boolean;
|
||||
sidebarPages?: SidebarPageItem[];
|
||||
siteDescription?: string;
|
||||
ads?: PublicAdItem[];
|
||||
adsPanelTitle?: string;
|
||||
adsEnabled?: boolean;
|
||||
sponsors?: PublicSponsorItem[];
|
||||
sponsorsPanelTitle?: string;
|
||||
sponsorsEnabled?: boolean;
|
||||
};
|
||||
|
||||
function AdsPanel({
|
||||
items,
|
||||
title,
|
||||
enabled,
|
||||
}: {
|
||||
items: PublicAdItem[];
|
||||
title: string;
|
||||
enabled: boolean;
|
||||
}) {
|
||||
if (!enabled) return null;
|
||||
|
||||
const IMAGE_SLOTS = 5;
|
||||
const TEXT_SLOTS = 6;
|
||||
const images = items.filter((a) => a.kind === "image").slice(0, IMAGE_SLOTS);
|
||||
const texts = items.filter((a) => a.kind === "text").slice(0, TEXT_SLOTS);
|
||||
const imageSlots = Array.from({ length: IMAGE_SLOTS }, (_, i) => images[i] ?? null);
|
||||
const textSlots = Array.from({ length: TEXT_SLOTS }, (_, i) => texts[i] ?? null);
|
||||
|
||||
return (
|
||||
<section className="j13-module" aria-label={title || "自助推广"}>
|
||||
<p className="j13-module-title mb-3">
|
||||
<CircleDollarSign size={15} style={{ color: "var(--accent)" }} />
|
||||
{title || "自助推广"}
|
||||
<Link
|
||||
href="/ads/buy"
|
||||
className="meta ml-auto text-[12px] font-normal transition-colors hover:text-[var(--accent)]"
|
||||
>
|
||||
购买广告位
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
<div className="j13-ad-promo">
|
||||
<ul className="space-y-2" aria-label="图片广告位">
|
||||
{imageSlots.map((ad, i) =>
|
||||
ad ? (
|
||||
<li key={`img-${ad.id}`}>
|
||||
<a
|
||||
href={ad.link_url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
className="j13-ad-image-link"
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src={ad.image_url}
|
||||
alt="推广"
|
||||
className="j13-ad-image"
|
||||
width={AD_IMAGE_WIDTH}
|
||||
height={AD_IMAGE_HEIGHT}
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
) : (
|
||||
<li key={`img-slot-${i}`}>
|
||||
<Link href="/ads/buy" className="j13-ad-slot j13-ad-slot-image">
|
||||
<span>图片广告位 {AD_IMAGE_SIZE_LABEL}</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
|
||||
<ul className="j13-ad-text-grid" aria-label="文字广告位">
|
||||
{textSlots.map((ad, i) =>
|
||||
ad ? (
|
||||
<li key={`txt-${ad.id}`}>
|
||||
<AdText
|
||||
title={ad.title ?? ""}
|
||||
textColor={ad.text_color || "#1E293B"}
|
||||
bgColor={ad.bg_color || "#F1F5F9"}
|
||||
href={ad.link_url}
|
||||
/>
|
||||
</li>
|
||||
) : (
|
||||
<li key={`txt-slot-${i}`}>
|
||||
<Link href="/ads/buy" className="j13-ad-slot j13-ad-slot-text">
|
||||
文字广告位
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SponsorsPanel({
|
||||
items,
|
||||
title,
|
||||
enabled,
|
||||
}: {
|
||||
items: PublicSponsorItem[];
|
||||
title: string;
|
||||
enabled: boolean;
|
||||
}) {
|
||||
if (!enabled || items.length === 0) return null;
|
||||
return (
|
||||
<section className="j13-module" aria-label={title || "赞助商"}>
|
||||
<p className="j13-module-title mb-3">
|
||||
<Handshake size={15} style={{ color: "var(--accent)" }} />
|
||||
{title || "赞助商"}
|
||||
</p>
|
||||
<ul className="j13-sponsors-grid">
|
||||
{items.map((sp) => {
|
||||
const tip = (sp.hover_text || sp.name || "").trim();
|
||||
const href = sp.link_url?.trim() || undefined;
|
||||
const inner = (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={sp.logo_url} alt={sp.name} className="j13-sponsor-logo" loading="lazy" />
|
||||
);
|
||||
return (
|
||||
<li key={sp.id} className="j13-sponsor">
|
||||
{href ? (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
className="j13-sponsor-hit"
|
||||
aria-label={sp.name}
|
||||
data-tip={tip || undefined}
|
||||
>
|
||||
{inner}
|
||||
</a>
|
||||
) : (
|
||||
<span
|
||||
className="j13-sponsor-hit"
|
||||
tabIndex={0}
|
||||
aria-label={sp.name}
|
||||
data-tip={tip || undefined}
|
||||
>
|
||||
{inner}
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function CommunityPanelsBody({
|
||||
stats,
|
||||
hot,
|
||||
@@ -441,11 +596,19 @@ function CommunityPanelsBody({
|
||||
canManagePages,
|
||||
sidebarPages = [],
|
||||
siteDescription,
|
||||
ads = [],
|
||||
adsPanelTitle = "自助推广",
|
||||
adsEnabled = true,
|
||||
sponsors = [],
|
||||
sponsorsPanelTitle = "赞助商",
|
||||
sponsorsEnabled = true,
|
||||
}: PanelsProps) {
|
||||
return (
|
||||
<>
|
||||
<PulseCard stats={stats} checkin={checkin} allowComments={allowComments} />
|
||||
<Announcements items={announcements} canManageAnnouncements={canManageAnnouncements} />
|
||||
<AdsPanel items={ads} title={adsPanelTitle} enabled={adsEnabled} />
|
||||
<SponsorsPanel items={sponsors} title={sponsorsPanelTitle} enabled={sponsorsEnabled} />
|
||||
<HotRanking posts={hot} allowComments={allowComments} />
|
||||
<NewUsers users={newUsers} />
|
||||
<ActiveUsers users={activeUsers} allowComments={allowComments} />
|
||||
|
||||
Reference in New Issue
Block a user