137 lines
7.3 KiB
TypeScript
137 lines
7.3 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import Image from "next/image";
|
||
import Link from "next/link";
|
||
import SessionCoverImage from "./SessionCoverImage";
|
||
import { SESSIONS, RECENT_JOINERS } from "@/config/home.config";
|
||
|
||
const MOCK_JOINERS = RECENT_JOINERS.slice(0, 3);
|
||
|
||
export default function HeroSection() {
|
||
const mainSession = SESSIONS[0];
|
||
const [stats, setStats] = useState<{ count: number; joiners: { nickname: string }[] } | null>(null);
|
||
|
||
useEffect(() => {
|
||
if (!mainSession) return;
|
||
fetch("/api/join/stats")
|
||
.then((r) => r.json())
|
||
.then((d) => d?.stats?.[mainSession.id] && setStats(d.stats[mainSession.id]))
|
||
.catch(() => {});
|
||
}, [mainSession?.id]);
|
||
return (
|
||
<section className="relative overflow-hidden bg-[#faf8f5] dark:bg-stone-950">
|
||
<div
|
||
className="absolute inset-0"
|
||
style={{
|
||
backgroundImage: `radial-gradient(ellipse 80% 50% at 50% 0%, rgba(180,83,9,0.08) 0%, transparent 60%),
|
||
radial-gradient(ellipse 60% 40% at 80% 80%, rgba(180,83,9,0.05) 0%, transparent 50%)`,
|
||
}}
|
||
/>
|
||
|
||
<div className="relative max-w-[1000px] mx-auto px-4 sm:px-6 md:px-8 py-10 sm:py-16 md:py-20">
|
||
<div className="flex flex-col lg:flex-row lg:items-center lg:gap-12 xl:gap-16">
|
||
<div className="flex-1 min-w-0">
|
||
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-[2.5rem] xl:text-[2.75rem] font-bold tracking-tight leading-[1.25]">
|
||
<span className="hero-gradient-text">周末留两小时</span>
|
||
<br />
|
||
<span className="text-stone-800 dark:text-stone-100">去loft民宿,认识几位同频的人</span>
|
||
</h1>
|
||
<p className="mt-4 sm:mt-5 text-stone-600 dark:text-stone-400 text-sm sm:text-base max-w-lg leading-relaxed">
|
||
深圳 · 6–8 人小圈子
|
||
</p>
|
||
<p className="mt-1.5 text-stone-500 dark:text-stone-450 text-xs sm:text-sm max-w-lg">
|
||
公开场地 · 填表申请 · 免费 · 不尬聊
|
||
</p>
|
||
<div className="mt-6 sm:mt-8">
|
||
<Link
|
||
href="/join"
|
||
className="inline-flex items-center justify-center bg-amber-600 hover:bg-amber-700 text-white px-5 py-2.5 sm:px-6 sm:py-3 rounded-xl text-sm font-medium shadow-lg shadow-amber-900/20 transition-all active:scale-[0.98]"
|
||
>
|
||
填表申请本周场次
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
|
||
{mainSession && (
|
||
<div className="w-full lg:w-[280px] xl:w-[300px] shrink-0 mt-8 lg:mt-0">
|
||
<Link
|
||
href={`/join?session=${mainSession.id}`}
|
||
className="block rounded-2xl overflow-hidden border border-stone-200 dark:border-stone-700 bg-white dark:bg-stone-900 shadow-lg shadow-stone-900/5 hover:shadow-xl hover:shadow-amber-900/10 transition-all ring-1 ring-stone-100 dark:ring-stone-800"
|
||
>
|
||
<div className="relative aspect-[4/3] w-full bg-stone-100 dark:bg-stone-800 overflow-hidden">
|
||
{mainSession.coverImage ? (
|
||
<SessionCoverImage
|
||
src={mainSession.coverImage}
|
||
alt={mainSession.title}
|
||
type={mainSession.type}
|
||
fill
|
||
sizes="(max-width: 1024px) 100vw, 300px"
|
||
className="object-cover"
|
||
/>
|
||
) : (
|
||
<div className="absolute inset-0 flex items-center justify-center text-3xl bg-stone-200 dark:bg-stone-700">
|
||
☕
|
||
</div>
|
||
)}
|
||
<div className="absolute top-2 left-2 right-2 flex flex-wrap gap-1">
|
||
<span className="inline-block text-[10px] font-medium bg-white/90 dark:bg-stone-900/90 backdrop-blur px-2 py-0.5 rounded text-stone-700 dark:text-stone-200">
|
||
周末下午
|
||
</span>
|
||
<span className="inline-block text-[10px] font-medium bg-white/90 dark:bg-stone-900/90 backdrop-blur px-2 py-0.5 rounded text-stone-700 dark:text-stone-200">
|
||
6–8 人
|
||
</span>
|
||
<span className="inline-block text-[10px] font-medium bg-white/90 dark:bg-stone-900/90 backdrop-blur px-2 py-0.5 rounded text-stone-700 dark:text-stone-200">
|
||
公开场地
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div className="p-4 sm:p-5 border-t border-stone-100 dark:border-stone-800">
|
||
<p className="text-[10px] sm:text-xs font-medium text-amber-600 dark:text-amber-400/90 uppercase tracking-wider mb-1">
|
||
本周场次
|
||
</p>
|
||
<h3 className="font-semibold text-stone-800 dark:text-stone-100 text-sm sm:text-base">{mainSession.title}</h3>
|
||
<p className="text-xs sm:text-sm text-stone-500 dark:text-stone-400 mt-0.5">
|
||
{mainSession.date} {mainSession.time} · {mainSession.place}
|
||
</p>
|
||
<span className="inline-flex items-center gap-1 mt-3 text-amber-600 dark:text-amber-400 font-medium text-xs sm:text-sm">
|
||
报名这场 →
|
||
</span>
|
||
<div className="mt-3 flex items-center gap-2">
|
||
<div className="flex -space-x-2 shrink-0">
|
||
{(stats?.joiners?.length ? stats.joiners : MOCK_JOINERS).slice(0, 5).map((j, i) => {
|
||
const nickname = "name" in j ? (j as { name: string }).name : (j as { nickname: string }).nickname;
|
||
const avatar = "avatar" in j ? (j as { avatar: string }).avatar : (j as { avatar?: string }).avatar;
|
||
const xhUrl = (j as { xiaohongshu_url?: string }).xiaohongshu_url;
|
||
const showAvatar = avatar && avatar.length > 0;
|
||
return (
|
||
<span
|
||
key={i}
|
||
className={`relative w-7 h-7 rounded-full overflow-hidden border-2 border-white dark:border-stone-900 ring-1 ring-stone-200/50 dark:ring-stone-700/50 shrink-0 inline-block cursor-default ${showAvatar ? "" : "bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center text-xs font-medium text-amber-700 dark:text-amber-300"}`}
|
||
title={nickname}
|
||
data-xiaohongshu-url={xhUrl}
|
||
onClick={(e) => e.preventDefault()}
|
||
>
|
||
{showAvatar ? (
|
||
<Image src={avatar} alt="" fill sizes="28px" className="object-cover" unoptimized />
|
||
) : (
|
||
<span className="flex items-center justify-center w-full h-full">{nickname.slice(0, 1) || "?"}</span>
|
||
)}
|
||
</span>
|
||
);
|
||
})}
|
||
</div>
|
||
<span className="text-xs text-stone-500 dark:text-stone-400">
|
||
{stats?.count ? `${stats.count} 人已报名` : "有人报名中"}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
);
|
||
}
|