像票務平台一樣,
好看又清楚。
活動首頁先看到重點,點進活動後再看完整介紹、時間、地點與票種,整體像正式票務網站,而不是普通查詢頁。
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.description || "尚無活動介紹"}
{event.notice || "尚無注意事項"}
活動首頁先看到重點,點進活動後再看完整介紹、時間、地點與票種,整體像正式票務網站,而不是普通查詢頁。