import React, { useEffect, useMemo, useState } from "react"; import { initializeApp, getApps } from "firebase/app"; import { getFirestore, collection, getDocs, query, where, doc, getDoc, } from "firebase/firestore"; const firebaseConfig = { apiKey: "AIzaSyDtF2pHo84vy2n_dJAkCisopllaLAWjaj0", authDomain: "concert-1ff7e.firebaseapp.com", projectId: "concert-1ff7e", storageBucket: "concert-1ff7e.firebasestorage.app", messagingSenderId: "704757384914", appId: "1:704757384914:web:9f58e4085dd71180c5cf0b", }; const app = getApps().length ? getApps()[0] : initializeApp(firebaseConfig); const db = getFirestore(app); function toDate(value: any): Date | null { if (!value) return null; if (typeof value?.toDate === "function") return value.toDate(); if (value instanceof Date) return value; const d = new Date(value); return Number.isNaN(d.getTime()) ? null : d; } function formatDate(value: any) { const d = toDate(value); if (!d) return "-"; return d.toLocaleDateString("zh-Hant", { year: "numeric", month: "2-digit", day: "2-digit", }); } function formatDateTime(value: any) { const d = toDate(value); if (!d) return "-"; return d.toLocaleString("zh-Hant", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", hour12: false, }); } function getStatus(event: any) { const now = Date.now(); const saleStart = toDate(event.saleStartAt)?.getTime() ?? null; const saleEnd = toDate(event.saleEndAt)?.getTime() ?? null; const eventEnd = toDate(event.eventEndAt)?.getTime() ?? null; if (event.published !== true) { return { text: "未公開", key: "hidden", className: "bg-slate-100 text-slate-500" }; } if (saleStart && now < saleStart) { return { text: "尚未開賣", key: "upcoming", className: "bg-blue-50 text-blue-700" }; } if (saleStart && saleEnd && now >= saleStart && now <= saleEnd) { return { text: "開賣中", key: "selling", className: "bg-emerald-50 text-emerald-700" }; } if (eventEnd && now > eventEnd) { return { text: "活動已結束", key: "ended", className: "bg-slate-100 text-slate-500" }; } if (saleEnd && now > saleEnd) { return { text: "已截止", key: "ended", className: "bg-slate-100 text-slate-500" }; } return { text: "活動資訊", key: "all", className: "bg-slate-100 text-slate-500" }; } async function getPublishedApprovedEvents() { const q = query( collection(db, "events"), where("published", "==", true), where("approvalStatus", "==", "approved") ); const snap = await getDocs(q); return snap.docs.map((d) => ({ id: d.id, ...d.data() })); } async function getEventById(eventId: string) { const snap = await getDoc(doc(db, "events", eventId)); if (!snap.exists()) return null; return { id: snap.id, ...snap.data() }; } function EventCard({ event, onOpen }: { event: any; onOpen: (id: string) => void }) { const status = getStatus(event); return ( ); } function EventDetail({ event, onBack }: { event: any; onBack: () => void }) { const status = getStatus(event); return (
{event.coverImage ? ( {event.title ) : (
EVENT COVER
)}
{status.text}

{event.title || "未命名活動"}

活動日期
{formatDate(event.eventStartAt)}
活動地點
{event.location || "未提供地點"}
開始時間
{formatDateTime(event.eventStartAt)}
結束時間
{formatDateTime(event.eventEndAt)}

活動介紹

{event.description || "尚無活動介紹"}

注意事項

{event.notice || "尚無注意事項"}

); } export default function TicketFrontpageMockup() { const [events, setEvents] = useState([]); const [loading, setLoading] = useState(true); const [selectedEventId, setSelectedEventId] = useState(null); const [selectedEvent, setSelectedEvent] = useState(null); const [search, setSearch] = useState(""); const [statusFilter, setStatusFilter] = useState("all"); useEffect(() => { let alive = true; (async () => { try { const rows = await getPublishedApprovedEvents(); rows.sort((a: any, b: any) => { const ta = toDate(a.eventStartAt)?.getTime() ?? 0; const tb = toDate(b.eventStartAt)?.getTime() ?? 0; return ta - tb; }); if (alive) setEvents(rows); } finally { if (alive) setLoading(false); } })(); return () => { alive = false; }; }, []); useEffect(() => { if (!selectedEventId) { setSelectedEvent(null); return; } let alive = true; (async () => { const data = await getEventById(selectedEventId); if (alive) setSelectedEvent(data); })(); return () => { alive = false; }; }, [selectedEventId]); const filtered = useMemo(() => { return events.filter((event) => { const q = search.trim().toLowerCase(); const status = getStatus(event); if (statusFilter !== "all" && status.key !== statusFilter) return false; if (!q) return true; return [event.title, event.location, event.description] .map((v) => String(v ?? "").toLowerCase()) .some((v) => v.includes(q)); }); }, [events, search, statusFilter]); if (selectedEvent && selectedEventId) { return setSelectedEventId(null)} />; } return (
K
KMSH TICKET
EVENT PLATFORM
活動列表 購票說明 平台介紹
已發布活動・立即查看

像票務平台一樣,
好看又清楚。

活動首頁先看到重點,點進活動後再看完整介紹、時間、地點與票種,整體像正式票務網站,而不是普通查詢頁。

2026 Ticket
EVENT PASS
QR
Event
校園音樂祭
Date
07 / 26
Location
Tainan Arena
Status
SELLING
Events

熱門活動

setSearch(e.target.value)} placeholder="搜尋活動名稱" className="rounded-2xl border border-black/10 bg-white px-4 py-3 text-sm outline-none transition focus:border-indigo-300 focus:ring-4 focus:ring-indigo-100" />
{loading ? (
載入中...
) : filtered.length === 0 ? (
目前沒有符合條件的活動。
) : (
{filtered.map((event) => ( ))}
)}
); }