This commit is contained in:
eric
2026-06-06 04:29:46 -05:00
parent 88aa96a2a1
commit 41493c35ca
50 changed files with 4666 additions and 416 deletions

View File

@@ -1,7 +1,8 @@
"use client";
import { useState, useMemo } from "react";
import { useEffect, useMemo, useState } from "react";
import { Link, useTranslation } from "@/i18n/navigation";
import { apiFetch } from "@/app/lib/api-client";
interface Attendee {
name: string;
@@ -42,7 +43,10 @@ const allMeetups: Meetup[] = [
const weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
function formatMeetupDate(dateStr: string, time: string): string {
const d = new Date(dateStr + "T" + time);
const normalizedDate = dateStr.includes("T") || dateStr.includes(" ")
? dateStr.slice(0, 10)
: dateStr;
const d = new Date(normalizedDate + "T" + time);
const weekday = weekdays[d.getDay()];
const year = d.getFullYear();
const month = d.getMonth() + 1;
@@ -51,6 +55,24 @@ function formatMeetupDate(dateStr: string, time: string): string {
return `${weekday}, ${year}${month}${day}日, ${timeFormatted}`;
}
function mapMeetup(item: Partial<Meetup> & { id: string; status?: string }): Meetup {
return {
id: item.id,
city: item.city || "线上",
emoji: item.emoji || "📍",
date: item.date || new Date().toISOString().slice(0, 10),
time: item.time || "19:00",
venue: item.venue || "待定地点",
address: item.address || "活动地址确认中",
description: item.description || "数字游民同城交流活动。",
rsvpCount: Number(item.rsvpCount || 0),
gradientFrom: item.gradientFrom || "#ff4d4f",
gradientTo: item.gradientTo || "#ff7a45",
attendees: Array.isArray(item.attendees) ? item.attendees : [],
isUpcoming: Boolean(item.isUpcoming ?? true),
};
}
function MeetupCard({ meetup }: { meetup: Meetup }) {
const formattedDate = formatMeetupDate(meetup.date, meetup.time);
return (
@@ -83,8 +105,20 @@ function MeetupCard({ meetup }: { meetup: Meetup }) {
export default function MeetupsPage() {
const { t } = useTranslation("meetups");
const [activeTab, setActiveTab] = useState<"upcoming" | "previous">("upcoming");
const upcomingMeetups = useMemo(() => allMeetups.filter((m) => m.isUpcoming), []);
const previousMeetups = useMemo(() => allMeetups.filter((m) => !m.isUpcoming), []);
const [meetups, setMeetups] = useState<Meetup[]>(allMeetups);
useEffect(() => {
apiFetch<{ items: Array<Partial<Meetup> & { id: string; status?: string }> }>("/api/meetups")
.then((data) => {
if (data.items?.length) {
setMeetups(data.items.map(mapMeetup));
}
})
.catch(() => setMeetups(allMeetups));
}, []);
const upcomingMeetups = useMemo(() => meetups.filter((m) => m.isUpcoming), [meetups]);
const previousMeetups = useMemo(() => meetups.filter((m) => !m.isUpcoming), [meetups]);
const displayedMeetups = activeTab === "upcoming" ? upcomingMeetups : previousMeetups;
const totalCount = activeTab === "upcoming" ? upcomingMeetups.length : previousMeetups.length;