"use client"; import { useState, useEffect } from "react"; import { createPortal } from "react-dom"; import { useTranslation } from "@/i18n/navigation"; import { City } from "@/app/data/cities"; const CITY_COORDS: Record = { 大理: [25.7, 100.2], 成都: [30.6, 104.1], 深圳: [22.5, 114.1], 上海: [31.2, 121.5], 杭州: [30.3, 120.2], 厦门: [24.5, 118.1], 北京: [39.9, 116.4], 广州: [23.1, 113.3], 重庆: [29.6, 106.6], 昆明: [25.0, 102.7], 三亚: [18.3, 109.5], 丽江: [26.9, 100.2], 西安: [34.3, 108.9], 南京: [32.1, 118.8], 长沙: [28.2, 112.9], 青岛: [36.1, 120.4], 苏州: [31.3, 120.6], 珠海: [22.3, 113.6], }; const CITY_IMAGES: Record = { 大理: "https://images.unsplash.com/photo-1590856029826-c7a73142bbf1?w=1200", 成都: "https://images.unsplash.com/photo-1547981609-4b6bfe67ca0b?w=1200", 深圳: "https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=1200", 上海: "https://images.unsplash.com/photo-1542051841857-5f90071e7989?w=1200", 杭州: "https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1200", 厦门: "https://images.unsplash.com/photo-1590559899731-a382839e5549?w=1200", 北京: "https://images.unsplash.com/photo-1508804185872-d7badad00f7d?w=1200", 广州: "https://images.unsplash.com/photo-1596422846543-75c6fc197f07?w=1200", 重庆: "https://images.unsplash.com/photo-1563841930606-67e2cce48b78?w=1200", 昆明: "https://images.unsplash.com/photo-1589553416260-f586c8f1514f?w=1200", 三亚: "https://images.unsplash.com/photo-1590523277543-a94d2e4eb00b?w=1200", 丽江: "https://images.unsplash.com/photo-1548013146-72479768bada?w=1200", 西安: "https://images.unsplash.com/photo-1590559899731-a382839e5549?w=1200", 南京: "https://images.unsplash.com/photo-1589829545856-d10d557cf95f?w=1200", 长沙: "https://images.unsplash.com/photo-1589829545856-d10d557cf95f?w=1200", 青岛: "https://images.unsplash.com/photo-1552465011-b4e21bf6e79a?w=1200", 苏州: "https://images.unsplash.com/photo-1589829545856-d10d557cf95f?w=1200", 珠海: "https://images.unsplash.com/photo-1590523277543-a94d2e4eb00b?w=1200", }; type ScoreLabel = "great" | "good" | "okay" | "bad"; function scoreToLabel(score: number): ScoreLabel { if (score >= 4.2) return "great"; if (score >= 3.5) return "good"; if (score >= 2.5) return "okay"; return "bad"; } interface CityModalProps { city: City | null; isOpen: boolean; onClose: () => void; } export default function CityModal({ city, isOpen, onClose }: CityModalProps) { const { t } = useTranslation("cityModal"); const [activeTab, setActiveTab] = useState("scores"); const [mounted, setMounted] = useState(false); const [favorited, setFavorited] = useState(false); useEffect(() => setMounted(true), []); if (!isOpen || !city || !mounted) return null; const coverImage = city.coverImage ?? CITY_IMAGES[city.name] ?? `linear-gradient(135deg, ${city.gradientFrom}, ${city.gradientTo})`; const reviewsCount = city.reviewsCount ?? city.nomadsNow * 4 + Math.floor(Math.random() * 500); const likedCount = city.likedCount ?? Math.floor(reviewsCount * 0.55); const dislikedCount = city.dislikedCount ?? Math.floor(reviewsCount * 0.08); const qualityOfLife = (city.qualityOfLife?.toLowerCase() as ScoreLabel) ?? scoreToLabel(city.overallScore); const familyScore = (city.familyScore?.toLowerCase() as ScoreLabel) ?? scoreToLabel(city.overallScore - 0.2); const communityScore = (city.communityScore?.toLowerCase() as ScoreLabel) ?? scoreToLabel(city.overallScore + 0.1); const nomadPercent = city.nomadPercent ?? Math.min(5, Math.ceil((city.nomadsNow / 500) * 2)); const funLabel = (city.funLabel?.toLowerCase() as ScoreLabel) ?? scoreToLabel(city.overallScore); const feelsLike = city.feelsLikeTemp ?? city.temperature + 2; const acPercent = city.acPercent ?? Math.min(95, 60 + city.temperature); const [lat, lng] = city.lat && city.lng ? [city.lat, city.lng] : (CITY_COORDS[city.name] ?? [30, 120]); const region = t("region"); const internetInfo = getInternetInfo(city.internetSpeed, t); const tempInfo = getTempInfo(city.temperature, feelsLike, t); const costLabel = getCostLabel(city.costPerMonth, t); const humidityLabel = getHumidityLabel(city.humidity, t); const acLabel = getAcLabel(acPercent, t); const tabs = [ { id: "guide", labelKey: "tabs.guide" }, { id: "pros", labelKey: "tabs.pros" }, { id: "reviews", labelKey: "tabs.reviews" }, { id: "cost", labelKey: "tabs.cost" }, { id: "people", labelKey: "tabs.people" }, { id: "chat", labelKey: "tabs.chat" }, { id: "photos", labelKey: "tabs.photos" }, { id: "weather", labelKey: "tabs.weather" }, { id: "trends", labelKey: "tabs.trends" }, { id: "demographics", labelKey: "tabs.demographics" }, { id: "scores", labelKey: "tabs.scores" }, ]; const scoresList = [ { icon: "🚩", labelKey: "scores.nomadScore", value: `${Math.round(city.overallScore)}/5 (${t("scores.rank")} #${city.id})`, good: true }, { icon: "❤️", labelKey: "scores.likedByMembers", value: `${likedCount} ${t("scores.likedIt")} ${dislikedCount} ${t("scores.dislikedIt")}`, good: null }, { icon: "⭐", labelKey: "scores.qualityOfLife", value: t(`scores.scoreLabels.${qualityOfLife}`), good: qualityOfLife !== "bad" }, { icon: "👨‍👩‍👧", labelKey: "scores.familyScore", value: t(`scores.scoreLabels.${familyScore}`), good: familyScore !== "bad" }, { icon: "👥", labelKey: "scores.communityScore", value: t(`scores.scoreLabels.${communityScore}`), good: communityScore !== "bad" }, { icon: "🏙️", labelKey: "scores.nomadDensity", value: `${nomadPercent}% ${t("scores.nomadHere")}`, good: true }, { icon: "💰", labelKey: "scores.cost", value: `${costLabel}: ¥${city.costPerMonth.toLocaleString()}/${t("scores.perMonth")}`, good: city.costPerMonth <= 6000 }, { icon: "📡", labelKey: "scores.internet", value: internetInfo.text, good: internetInfo.isGood }, { icon: "😊", labelKey: "scores.fun", value: t(`scores.scoreLabels.${funLabel}`), good: funLabel !== "bad" }, { icon: "🌡️", labelKey: "scores.temperature", value: tempInfo.text, good: tempInfo.isGood }, { icon: "💧", labelKey: "scores.humidity", value: `${humidityLabel}: ${city.humidity}%`, good: true }, { icon: "❄️", labelKey: "scores.acPercent", value: `${acLabel}: ${acPercent}%`, good: acPercent >= 70 }, ]; const mapUrl = `https://www.openstreetmap.org/export/embed.html?bbox=${lng - 0.2},${lat - 0.15},${lng + 0.2},${lat + 0.15}&layer=mapnik&marker=${lat},${lng}`; const modalContent = (
e.stopPropagation()} > {/* Header with cover image */}
{typeof coverImage === "string" && coverImage.startsWith("http") ? ( {city.name} ) : (
)}

{city.name}

{city.countryEmoji} {city.country}

{reviewsCount.toLocaleString()} {t("reviews")}
{/* Breadcrumb */}

{t("breadcrumbPrefix")} > {region} > {city.country} > {city.name}

{/* Tabs */}
{tabs.map((tab) => ( ))}
{/* Content - scrollable */}
{activeTab === "scores" ? (
{scoresList.map((s) => (
{s.icon} {t(s.labelKey)} {String(s.value)}
))}