Files
no-whatever/src/components/RestaurantShareCard.tsx
T

270 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* eslint-disable @next/next/no-img-element -- Share card rendering requires direct img tags for html-to-image output. */
import { Star, MapPin, Zap } from "lucide-react";
import type { Restaurant, MatchType, SceneType } from "@/types";
import { getSceneConfig } from "@/lib/sceneConfig";
import ShareCardShell from "./ShareCardShell";
import type { ShareCardTheme } from "./ShareCardShell";
export interface RestaurantShareData {
type: "restaurant";
restaurant: Restaurant;
matchType: MatchType;
matchLikes: number;
userCount: number;
scene?: SceneType;
}
function buildTheme(isUnanimous: boolean): ShareCardTheme & { accentText: string; accentBg: string } {
const accentFrom = isUnanimous ? "#059669" : "#b45309";
const accentTo = isUnanimous ? "#34d399" : "#fbbf24";
return {
emoji: "⚡",
tagline: "别说随便 · PANIC MODE",
bgColor: "#08080a",
gradientBorder: `linear-gradient(160deg, ${accentFrom}, ${accentTo}40, ${accentFrom}30)`,
accentLine: `linear-gradient(to right, transparent, ${accentTo}30, transparent)`,
glows: [
{ top: -40, right: -30, width: 160, height: 160, color: `${accentFrom}25` },
{ top: 9999, right: 9999, width: 120, height: 120, color: `${accentTo}12` },
],
qrFgColor: "#08080a",
accentText: isUnanimous ? "#6ee7b7" : "#fcd34d",
accentBg: isUnanimous ? "rgba(16, 185, 129, 0.12)" : "rgba(245, 158, 11, 0.12)",
};
}
export default function RestaurantShareCard({
data,
cardRef,
imageDataUrl,
bgDataUrl,
}: {
data: RestaurantShareData;
cardRef: React.RefObject<HTMLDivElement | null>;
imageDataUrl: string | null;
bgDataUrl?: string | null;
}) {
const { restaurant, matchType, matchLikes, userCount, scene } = data;
const isUnanimous = matchType === "unanimous";
const verb = getSceneConfig(scene ?? "eat").verb;
const theme = buildTheme(isUnanimous);
const { accentText, accentBg } = theme;
const accentTo = isUnanimous ? "#34d399" : "#fbbf24";
// Fix the second glow position (bottom-left, not the placeholder)
theme.glows[1] = { top: 9999, right: 9999, width: 120, height: 120, color: `${accentTo}12` };
return (
<ShareCardShell theme={theme} cardRef={cardRef} bgDataUrl={bgDataUrl}>
{/* Second glow override (bottom-left) */}
<div
style={{
position: "absolute",
bottom: 60,
left: -40,
width: 120,
height: 120,
borderRadius: "50%",
background: `radial-gradient(circle, ${accentTo}12, transparent 70%)`,
}}
/>
{/* Hero section */}
<div
style={{
padding: "24px 20px 20px",
textAlign: "center",
position: "relative",
}}
>
<div style={{ fontSize: 40, lineHeight: 1 }}>
{isUnanimous ? "🎉" : "🏆"}
</div>
<div
style={{
fontSize: 30,
fontWeight: 900,
color: "#ffffff",
marginTop: 12,
letterSpacing: "0.08em",
}}
>
{verb}
</div>
<div
style={{
display: "inline-flex",
alignItems: "center",
gap: 6,
marginTop: 12,
padding: "6px 16px",
borderRadius: 100,
background: accentBg,
border: `1px solid ${accentTo}20`,
}}
>
{isUnanimous && (
<Zap size={12} style={{ color: accentText, fill: accentText }} />
)}
<span style={{ fontSize: 11, fontWeight: 700, color: accentText }}>
{isUnanimous
? `默契度 100% · ${userCount}人全员一致`
: `${matchLikes}/${userCount} 人选了这家`}
</span>
{isUnanimous && (
<Zap size={12} style={{ color: accentText, fill: accentText }} />
)}
</div>
</div>
{/* Restaurant card */}
<div style={{ padding: "0 16px 16px" }}>
<div
style={{
borderRadius: 14,
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.06)",
overflow: "hidden",
}}
>
{imageDataUrl && (
<img
src={imageDataUrl}
alt={restaurant.name}
style={{
width: "100%",
height: 150,
objectFit: "cover",
display: "block",
}}
/>
)}
<div style={{ padding: "14px 16px 16px" }}>
<div
style={{
display: "flex",
alignItems: "flex-start",
justifyContent: "space-between",
gap: 8,
}}
>
<div
style={{
fontSize: 17,
fontWeight: 800,
color: "#ffffff",
lineHeight: 1.3,
}}
>
{restaurant.name}
</div>
{restaurant.category && (
<span
style={{
flexShrink: 0,
padding: "3px 8px",
borderRadius: 100,
background: accentBg,
fontSize: 10,
fontWeight: 600,
color: accentText,
whiteSpace: "nowrap",
}}
>
{restaurant.category}
</span>
)}
</div>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
marginTop: 10,
fontSize: 12,
}}
>
{restaurant.rating > 0 && (
<span
style={{
display: "flex",
alignItems: "center",
gap: 3,
color: "#e5e7eb",
fontWeight: 600,
}}
>
<Star size={13} style={{ color: "#fbbf24", fill: "#fbbf24" }} />
{restaurant.rating}
</span>
)}
{restaurant.price && restaurant.price !== "未知" && (
<span style={{ fontWeight: 700, color: accentText }}>
{restaurant.price}
</span>
)}
{restaurant.distance && (
<span
style={{
display: "flex",
alignItems: "center",
gap: 3,
color: "#9ca3af",
}}
>
<MapPin size={12} style={{ color: "#6b7280" }} />
{restaurant.distance}
</span>
)}
</div>
{restaurant.address && (
<div
style={{
marginTop: 8,
fontSize: 11,
color: "#6b7280",
lineHeight: 1.5,
}}
>
📍 {restaurant.address}
</div>
)}
{restaurant.tag && (
<div
style={{
display: "flex",
flexWrap: "wrap",
gap: 4,
marginTop: 10,
}}
>
{restaurant.tag
.split(",")
.slice(0, 4)
.map((t) => (
<span
key={t}
style={{
padding: "2px 8px",
borderRadius: 4,
background: "rgba(255,255,255,0.05)",
fontSize: 10,
fontWeight: 500,
color: "#9ca3af",
}}
>
{t.trim()}
</span>
))}
</div>
)}
</div>
</div>
</div>
</ShareCardShell>
);
}