Files
gitlab-instance-0a899031_salon/app/components/HeroSection.tsx
2026-03-16 01:09:13 -05:00

137 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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">
· 68
</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">
68
</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>
);
}