diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..0204dd2 --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { useEffect } from "react"; +import { motion } from "framer-motion"; +import { AlertTriangle, RotateCcw, Home } from "lucide-react"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error("[ErrorBoundary]", error); + }, [error]); + + return ( +
+ + +
+ + + +

出了点问题

+

+ 页面遇到了意外错误,请重试或返回首页 +

+ +
+ + + + 首页 + +
+ +
+ ); +} diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx new file mode 100644 index 0000000..6cfba47 --- /dev/null +++ b/src/app/global-error.tsx @@ -0,0 +1,45 @@ +"use client"; + +import { useEffect } from "react"; + +export default function GlobalError({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + console.error("[GlobalError]", error); + }, [error]); + + return ( + + +
+
⚠️
+

应用崩溃了

+

+ 发生了严重错误,请尝试刷新页面 +

+ +
+ + + ); +} diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index fbb50ac..ba1915d 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -24,6 +24,7 @@ import { Heart, } from "lucide-react"; import EmptyState from "@/components/EmptyState"; +import RestaurantImage from "@/components/RestaurantImage"; import { getUserId, getCachedProfile, setCachedProfile, setCachedPreferences, logout } from "@/lib/userId"; import { getAvatarBg, AVATARS } from "@/lib/avatars"; import type { UserProfile, UserPreferences, DecisionRecord, FavoriteRecord, Restaurant } from "@/types"; @@ -556,11 +557,10 @@ export default function ProfilePage() { className="flex gap-3 rounded-xl bg-elevated p-2.5 transition-colors active:bg-subtle" > {firstImage(d.restaurantData) && ( - {d.restaurantName} )}
@@ -638,11 +638,10 @@ export default function ProfilePage() { className="flex gap-3 rounded-xl bg-elevated p-2.5" > {firstImage(r) && ( - {r.name} )}
diff --git a/src/components/MatchResult.tsx b/src/components/MatchResult.tsx index 9e9f60c..a736f6c 100644 --- a/src/components/MatchResult.tsx +++ b/src/components/MatchResult.tsx @@ -32,6 +32,7 @@ import { import { fireCelebration, playChime } from "@/lib/celebrate"; import { isRegistered } from "@/lib/userId"; import ShareCardModal from "@/components/ShareCardModal"; +import RestaurantImage from "@/components/RestaurantImage"; import AuthModal from "@/components/AuthModal"; interface MatchResultProps { @@ -145,11 +146,10 @@ function RunnerUpCard({ className="flex gap-3 rounded-xl bg-surface/80 p-2.5 ring-1 ring-border/50 backdrop-blur-sm transition-colors hover:bg-elevated/80" > {restaurant.images?.[0] && ( - {restaurant.name} )}
@@ -397,11 +397,10 @@ export default function MatchResult({ )} {restaurant.images?.[0] && ( - {restaurant.name} )}
diff --git a/src/components/RestaurantCard.tsx b/src/components/RestaurantCard.tsx index 3569dfb..e4a810a 100644 --- a/src/components/RestaurantCard.tsx +++ b/src/components/RestaurantCard.tsx @@ -4,6 +4,7 @@ import { useCallback, useState, useEffect } from "react"; import { Star, MapPin, Clock, ExternalLink, Flame, Bookmark, ChevronLeft, ChevronRight } from "lucide-react"; import { Restaurant } from "@/types"; import { getUserId, isRegistered } from "@/lib/userId"; +import RestaurantImage from "@/components/RestaurantImage"; interface RestaurantCardProps { restaurant: Restaurant; @@ -57,16 +58,15 @@ function ImageGallery({ images, name }: { images: string[]; name: string }) { return (
- {name} {fadingOut !== null && ( - setFadingOut(null)} draggable={false} - referrerPolicy="no-referrer" /> )} diff --git a/src/components/RestaurantImage.tsx b/src/components/RestaurantImage.tsx new file mode 100644 index 0000000..f41a558 --- /dev/null +++ b/src/components/RestaurantImage.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { useState, useCallback } from "react"; +import { UtensilsCrossed } from "lucide-react"; + +interface RestaurantImageProps { + src: string; + alt: string; + className?: string; + draggable?: boolean; + style?: React.CSSProperties; + onAnimationEnd?: () => void; +} + +export default function RestaurantImage({ + src, + alt, + className = "", + draggable, + style, + onAnimationEnd, +}: RestaurantImageProps) { + const [failed, setFailed] = useState(false); + + const handleError = useCallback(() => setFailed(true), []); + + if (failed) { + return ( +
+ +
+ ); + } + + return ( + {alt} + ); +}