diff --git a/app/components/AvatarStack.tsx b/app/components/AvatarStack.tsx new file mode 100644 index 0000000..e693318 --- /dev/null +++ b/app/components/AvatarStack.tsx @@ -0,0 +1,35 @@ +"use client"; + +import Image from "next/image"; +import { RECENT_JOINERS } from "@/config/home.config"; + +export default function AvatarStack() { + return ( +
+
+ {RECENT_JOINERS.slice(0, 6).map((u, i) => ( +
+ {u.name} +
+ ))} +
+
+ + {RECENT_JOINERS.map((u) => u.name).join(" ")} + + 等本月新加入 +
+
+ ); +} diff --git a/app/components/CitySelect.tsx b/app/components/CitySelect.tsx new file mode 100644 index 0000000..ea245c9 --- /dev/null +++ b/app/components/CitySelect.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { CITIES } from "@/config/home.config"; + +interface CitySelectProps { + value: string; + onChange: (v: string) => void; + className?: string; + required?: boolean; +} + +export default function CitySelect({ value, onChange, className = "", required }: CitySelectProps) { + return ( + + ); +} diff --git a/app/components/Footer.tsx b/app/components/Footer.tsx index a7c53d3..b3f3d5a 100644 --- a/app/components/Footer.tsx +++ b/app/components/Footer.tsx @@ -2,7 +2,7 @@ export default function Footer() { return ( ); diff --git a/app/components/Header.tsx b/app/components/Header.tsx new file mode 100644 index 0000000..662ed8a --- /dev/null +++ b/app/components/Header.tsx @@ -0,0 +1,17 @@ +"use client"; + +import Link from "next/link"; +import ThemeToggle from "./ThemeToggle"; + +export default function Header() { + return ( +
+
+ + Salon + + +
+
+ ); +} diff --git a/app/components/HeroSection.tsx b/app/components/HeroSection.tsx index f2e472f..22b3844 100644 --- a/app/components/HeroSection.tsx +++ b/app/components/HeroSection.tsx @@ -1,6 +1,7 @@ "use client"; import Link from "next/link"; +import SessionCoverImage from "./SessionCoverImage"; import { SESSIONS } from "@/config/home.config"; export default function HeroSection() { @@ -14,63 +15,56 @@ export default function HeroSection() { radial-gradient(ellipse 60% 40% at 80% 80%, rgba(180,83,9,0.05) 0%, transparent 50%)`, }} /> -
-
-
-
-

- 北京 · 周末线下 +

+
+
+

+ 📍 深圳 · 广州 · 惠州

-

+

周末抽两小时
- 在咖啡馆认识几个新朋友 + 认识几个新朋友

-

- 氛围轻松,不用尬聊。公开场地,填表即报。 +

+ 😌 氛围轻松 · 📍 公开场地 · 填表即报

-
+
- 报名参加 + ✨ 报名参加 - {mainSession && ( - - {mainSession.title} · {mainSession.date} - - )}
- {mainSession && ( -
+ {mainSession && mainSession.coverImage && ( +
-
- +
+
-
-

+

+

本周主推

-

{mainSession.title}

-

+

{mainSession.title}

+

{mainSession.date} {mainSession.time}

- + 报名这场 →
diff --git a/app/components/SessionCoverImage.tsx b/app/components/SessionCoverImage.tsx new file mode 100644 index 0000000..78782a5 --- /dev/null +++ b/app/components/SessionCoverImage.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useState } from "react"; +import Image from "next/image"; + +interface SessionCoverImageProps { + src: string; + alt: string; + type?: "skill-exchange" | "side-hustle"; + className?: string; + fill?: boolean; + sizes?: string; +} + +export default function SessionCoverImage({ src, alt, type, className = "", fill, sizes }: SessionCoverImageProps) { + const [error, setError] = useState(false); + + if (error) { + return ( +
+ {type === "skill-exchange" ? "🔄" : "☕"} +
+ ); + } + + return ( + {alt} setError(true)} + /> + ); +} diff --git a/app/components/ThemeProvider.tsx b/app/components/ThemeProvider.tsx new file mode 100644 index 0000000..4a2935d --- /dev/null +++ b/app/components/ThemeProvider.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { ThemeProvider as NextThemesProvider } from "next-themes"; + +export default function ThemeProvider({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/app/components/ThemeToggle.tsx b/app/components/ThemeToggle.tsx new file mode 100644 index 0000000..c006a94 --- /dev/null +++ b/app/components/ThemeToggle.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useEffect, useState } from "react"; + +export default function ThemeToggle() { + const [mounted, setMounted] = useState(false); + const { theme, setTheme } = useTheme(); + + useEffect(() => setMounted(true), []); + + if (!mounted) { + return ( + + ); + } + + const isDark = theme === "dark"; + + return ( + + ); +} diff --git a/app/globals.css b/app/globals.css index 1338f31..da50790 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,5 +1,8 @@ @import "tailwindcss"; +/* 使 Tailwind dark: 变体响应 class="dark",与 next-themes 配合 */ +@custom-variant dark (&:where(.dark, .dark *)); + :root { --background: #faf8f5; --foreground: #1c1917; @@ -7,11 +10,10 @@ --accent-hover: #92400e; } -@media (prefers-color-scheme: dark) { - :root { - --background: #0c0a09; - --foreground: #fafaf9; - } +html.dark { + --background: #0c0a09; + --foreground: #fafaf9; + color-scheme: dark; } body { @@ -19,6 +21,10 @@ body { background: var(--background); } +.safe-area-pb { + padding-bottom: env(safe-area-inset-bottom, 0); +} + .hero-gradient-text { background: linear-gradient(135deg, #b45309 0%, #d97706 50%, #f59e0b 100%); -webkit-background-clip: text; diff --git a/app/join/page.tsx b/app/join/page.tsx index f2dc7ed..5865948 100644 --- a/app/join/page.tsx +++ b/app/join/page.tsx @@ -2,7 +2,10 @@ import { useState, useEffect, type FormEvent } from "react"; import Link from "next/link"; +import SessionCoverImage from "../components/SessionCoverImage"; import { SESSIONS } from "@/config/home.config"; +import CitySelect from "../components/CitySelect"; +import ThemeToggle from "../components/ThemeToggle"; const ageOptions = ["18-24", "25-30", "31-35", "36-40", "40+"]; const firstTimeOptions = ["是,第一次参加", "否,参加过类似活动"]; @@ -29,7 +32,7 @@ function getSessionInfo(sessionId: string) { export default function JoinPage() { const [form, setForm] = useState({ - city: "北京", + city: "", session: "", nickname: "", ageRange: "", @@ -44,7 +47,14 @@ export default function JoinPage() { useEffect(() => { const sid = getSessionFromUrl(); - if (sid) setForm((prev) => ({ ...prev, session: sid })); + if (sid) { + const s = SESSIONS.find((x) => x.id === sid); + setForm((prev) => ({ + ...prev, + session: sid, + city: s?.city || prev.city, + })); + } }, []); const set = (key: keyof FormData, value: string | boolean) => @@ -85,18 +95,18 @@ export default function JoinPage() { if (submitted) { return ( -
-
-
+
+
+
-

报名成功

-

+

报名成功

+

我们会尽快通过您留下的联系方式与您确认

← 返回首页 @@ -107,69 +117,83 @@ export default function JoinPage() { return (
-
+ {/* 顶部栏 */} +
+
+ + ← 返回 + + +
+
+ +
-
+
-
-
- 北京周末新朋友活动 +
+
+ 📍 深圳 · 广州 · 惠州
-
-

+
+

活动报名

-

- 填个简单表单即可,无需注册登录,不收费 +

+ ✍️ 填表即可 · 无需注册 · 不收费

{selectedSession && ( -
-

已选场次

-

{selectedSession.title}

-

{selectedSession.date} {selectedSession.time} · {selectedSession.place}

+
+ {selectedSession.coverImage && ( +
+ +
+ )} +
+

已选场次

+

{selectedSession.title}

+

{selectedSession.date} {selectedSession.time} · {selectedSession.place}

+
)}
-
+
- set("city", e.target.value)} - required - className="form-input" - /> + set("city", v)} required />
-
+
set("ageRange", e.target.value)} required - className={`form-input appearance-none ${form.ageRange ? "text-slate-800 dark:text-slate-100" : "text-slate-400 dark:text-slate-500"}`} + className={`form-input appearance-none ${form.ageRange ? "text-stone-800 dark:text-stone-100" : "text-stone-400 dark:text-stone-500"}`} > {ageOptions.map((o) => ( @@ -197,8 +221,8 @@ export default function JoinPage() {
-
-