Files
wish-pool/components/wish-card.tsx
2025-07-18 13:07:28 +08:00

430 lines
22 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

"use client"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
MessageCircle,
Target,
Lightbulb,
Calendar,
Sparkles,
Clock,
Zap,
ChevronDown,
ChevronUp,
Heart,
Users,
} from "lucide-react"
import { categorizeWishMultiple, type Wish } from "@/lib/categorization"
import { generateSolutionRecommendations, type SolutionCategory } from "@/lib/solution-recommendations"
import { useState, useEffect } from "react"
import { soundManager } from "@/lib/sound-effects"
interface WishCardProps {
wish: Wish
}
export default function WishCard({ wish }: WishCardProps) {
const [showSolutions, setShowSolutions] = useState(false)
const [selectedSolution, setSelectedSolution] = useState<SolutionCategory | null>(null)
const [likeCount, setLikeCount] = useState(0)
const [hasLiked, setHasLiked] = useState(false)
const [isLiking, setIsLiking] = useState(false)
// 載入點讚數據
useEffect(() => {
const likes = JSON.parse(localStorage.getItem("wishLikes") || "{}")
const likedWishes = JSON.parse(localStorage.getItem("userLikedWishes") || "[]")
setLikeCount(likes[wish.id] || 0)
setHasLiked(likedWishes.includes(wish.id))
}, [wish.id])
const handleLike = async () => {
if (hasLiked || isLiking) return
setIsLiking(true)
// 播放點讚音效
await soundManager.play("click")
// 更新點讚數據
const likes = JSON.parse(localStorage.getItem("wishLikes") || "{}")
const likedWishes = JSON.parse(localStorage.getItem("userLikedWishes") || "[]")
likes[wish.id] = (likes[wish.id] || 0) + 1
likedWishes.push(wish.id)
localStorage.setItem("wishLikes", JSON.stringify(likes))
localStorage.setItem("userLikedWishes", JSON.stringify(likedWishes))
setLikeCount(likes[wish.id])
setHasLiked(true)
// 播放成功音效
setTimeout(async () => {
await soundManager.play("success")
setIsLiking(false)
}, 300)
}
const formatDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString("zh-TW", {
year: "numeric",
month: "long",
day: "numeric",
})
}
// 多標籤自動分類最多3個
const categories = categorizeWishMultiple(wish).slice(0, 3)
// 生成解決方案建議
const solutionRecommendation = generateSolutionRecommendations(wish)
const getDifficultyColor = (difficulty: string) => {
switch (difficulty) {
case "easy":
return "bg-green-500/20 text-green-300 border-green-400/40"
case "medium":
return "bg-yellow-500/20 text-yellow-300 border-yellow-400/40"
case "hard":
return "bg-orange-500/20 text-orange-300 border-orange-400/40"
default:
return "bg-gray-500/20 text-gray-300 border-gray-400/40"
}
}
const getDifficultyLabel = (difficulty: string) => {
switch (difficulty) {
case "easy":
return "容易實現"
case "medium":
return "中等難度"
case "hard":
return "需要投入"
default:
return "未知"
}
}
return (
<Card className="group relative overflow-hidden bg-gradient-to-br from-slate-800/90 to-slate-900/90 backdrop-blur-sm border border-slate-600/50 hover:border-cyan-400/50 shadow-2xl hover:shadow-cyan-500/20 transition-all duration-500 transform hover:scale-[1.01] mx-2 md:mx-0">
{/* 背景光效 */}
<div className="absolute inset-0 bg-gradient-to-br from-cyan-500/5 via-transparent to-blue-500/5 opacity-0 group-hover:opacity-100 transition-opacity duration-500"></div>
{/* 頂部裝飾線 */}
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-cyan-400 via-blue-400 to-purple-400"></div>
<CardHeader className="relative pb-3 md:pb-4 px-4 md:px-6 pt-4 md:pt-6">
<div className="flex items-start justify-between gap-3 md:gap-4 mb-3">
<CardTitle className="text-xl md:text-2xl font-bold text-white group-hover:text-cyan-100 transition-colors duration-300 leading-tight flex-1">
{wish.title}
</CardTitle>
<div className="flex items-center gap-2 shrink-0">
<Badge className="bg-slate-700/80 hover:bg-slate-600/80 text-slate-200 border border-slate-500/50 px-2 md:px-3 py-1 text-xs md:text-sm">
<Calendar className="w-3 h-3 mr-1 md:mr-1.5" />
<span className="hidden sm:inline">{formatDate(wish.createdAt)}</span>
<span className="sm:hidden">
{new Date(wish.createdAt).toLocaleDateString("zh-TW", { month: "short", day: "numeric" })}
</span>
</Badge>
</div>
</div>
{/* 最多3個問題領域標籤 */}
<div className="flex items-center gap-2 flex-wrap">
{categories.map((category, index) => (
<Badge
key={`${category.name}-${index}`}
className={`bg-gradient-to-r ${category.bgColor} ${category.borderColor} ${category.textColor} border backdrop-blur-sm px-3 py-1.5 text-xs md:text-sm font-medium shadow-lg ${
index === 0 ? "ring-2 ring-white/20" : ""
}`}
>
<div className="w-2 h-2 rounded-full mr-2 shadow-sm" style={{ backgroundColor: category.color }}></div>
{category.name}
{index === 0 && categories.length > 1 && <span className="ml-1 text-xs opacity-75"></span>}
</Badge>
))}
</div>
</CardHeader>
<CardContent className="relative space-y-4 md:space-y-5 px-4 md:px-6 pb-4 md:pb-6">
{/* 目前困擾 - 手機優化 */}
<div className="group/section relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-slate-700/60 to-slate-800/60 border border-slate-600/40 hover:border-purple-400/30 p-4 md:p-5 backdrop-blur-sm cursor-pointer transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-lg hover:shadow-purple-500/10">
<div className="absolute inset-0 bg-gradient-to-r from-purple-500/8 to-indigo-500/8 group-hover/section:from-purple-500/15 group-hover/section:to-indigo-500/15 transition-all duration-300"></div>
{/* 懸停時的光暈效果 */}
<div className="absolute inset-0 bg-gradient-to-r from-purple-400/0 via-purple-400/5 to-purple-400/0 opacity-0 group-hover/section:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-purple-400/80 to-indigo-500/80 flex items-center justify-center shadow-lg shadow-purple-500/20 group-hover/section:shadow-purple-500/30 group-hover/section:scale-110 transition-all duration-300">
<MessageCircle className="w-3.5 h-3.5 md:w-4 md:h-4 text-white group-hover/section:rotate-12 transition-transform duration-300" />
</div>
<h4 className="font-semibold text-purple-200 group-hover/section:text-purple-100 text-base md:text-lg transition-colors duration-300">
</h4>
</div>
<CardDescription className="text-slate-200 group-hover/section:text-slate-100 text-sm md:text-base leading-relaxed font-medium transition-colors duration-300">
{wish.currentPain}
</CardDescription>
</div>
</div>
{/* 期望解決方式 - 手機優化 */}
<div className="group/section relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-slate-700/60 to-slate-800/60 border border-slate-600/40 hover:border-cyan-400/30 p-4 md:p-5 backdrop-blur-sm cursor-pointer transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-lg hover:shadow-cyan-500/10">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-500/8 to-blue-500/8 group-hover/section:from-cyan-500/15 group-hover/section:to-blue-500/15 transition-all duration-300"></div>
{/* 懸停時的光暈效果 */}
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400/0 via-cyan-400/5 to-cyan-400/0 opacity-0 group-hover/section:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-cyan-400/80 to-blue-500/80 flex items-center justify-center shadow-lg shadow-cyan-500/20 group-hover/section:shadow-cyan-500/30 group-hover/section:scale-110 transition-all duration-300">
<Lightbulb className="w-3.5 h-3.5 md:w-4 md:h-4 text-white group-hover/section:rotate-12 transition-transform duration-300" />
</div>
<h4 className="font-semibold text-cyan-200 group-hover/section:text-cyan-100 text-base md:text-lg transition-colors duration-300">
</h4>
</div>
<CardDescription className="text-slate-200 group-hover/section:text-slate-100 text-sm md:text-base leading-relaxed font-medium transition-colors duration-300">
{wish.expectedSolution}
</CardDescription>
</div>
</div>
{/* 預期效果 - 手機優化 */}
{wish.expectedEffect && (
<div className="group/section relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-slate-700/60 to-slate-800/60 border border-slate-600/40 hover:border-indigo-400/30 p-4 md:p-5 backdrop-blur-sm cursor-pointer transition-all duration-300 hover:transform hover:scale-[1.02] hover:shadow-lg hover:shadow-indigo-500/10">
<div className="absolute inset-0 bg-gradient-to-r from-indigo-500/8 to-purple-500/8 group-hover/section:from-indigo-500/15 group-hover/section:to-purple-500/15 transition-all duration-300"></div>
{/* 懸停時的光暈效果 */}
<div className="absolute inset-0 bg-gradient-to-r from-indigo-400/0 via-indigo-400/5 to-indigo-400/0 opacity-0 group-hover/section:opacity-100 transition-opacity duration-500"></div>
<div className="relative">
<div className="flex items-center gap-2 md:gap-3 mb-2 md:mb-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-indigo-400/80 to-purple-500/80 flex items-center justify-center shadow-lg shadow-indigo-500/20 group-hover/section:shadow-indigo-500/30 group-hover/section:scale-110 transition-all duration-300">
<Target className="w-3.5 h-3.5 md:w-4 md:h-4 text-white group-hover/section:rotate-12 transition-transform duration-300" />
</div>
<h4 className="font-semibold text-indigo-200 group-hover/section:text-indigo-100 text-base md:text-lg transition-colors duration-300">
</h4>
</div>
<CardDescription className="text-slate-200 group-hover/section:text-slate-100 text-sm md:text-base leading-relaxed font-medium transition-colors duration-300">
{wish.expectedEffect}
</CardDescription>
</div>
</div>
)}
{/* 共鳴支持區塊 - 新增 */}
<div className="relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-pink-800/30 to-rose-800/30 border border-pink-600/40 p-3 md:p-4 backdrop-blur-sm transition-all duration-300">
<div className="absolute inset-0 bg-gradient-to-r from-pink-500/10 to-rose-500/10"></div>
<div className="relative flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="flex items-center gap-2">
<Users className="w-4 h-4 text-pink-300" />
<span className="text-sm md:text-base text-pink-200 font-medium">
{likeCount > 0 ? `${likeCount} 人也遇到相同問題` : "成為第一個表達支持的人"}
</span>
</div>
{likeCount > 0 && (
<div className="flex items-center gap-1">
{[...Array(Math.min(likeCount, 5))].map((_, i) => (
<Heart
key={i}
className="w-3 h-3 text-pink-400 animate-pulse"
fill="currentColor"
style={{ animationDelay: `${i * 0.2}s` }}
/>
))}
{likeCount > 5 && <span className="text-xs text-pink-300 ml-1">+{likeCount - 5}</span>}
</div>
)}
</div>
<Button
onClick={handleLike}
disabled={hasLiked || isLiking}
size="sm"
className={`
transition-all duration-300 transform hover:scale-105 px-3 md:px-4 py-2
${
hasLiked
? "bg-pink-600/50 text-pink-200 border border-pink-500/50 cursor-not-allowed"
: "bg-gradient-to-r from-pink-500/80 to-rose-600/80 hover:from-pink-600/90 hover:to-rose-700/90 text-white shadow-lg shadow-pink-500/25"
}
${isLiking ? "animate-pulse" : ""}
`}
>
<Heart
className={`w-3 h-3 md:w-4 md:h-4 mr-1.5 md:mr-2 transition-all duration-300 ${
hasLiked ? "text-pink-300" : "text-white"
} ${isLiking ? "animate-bounce" : ""}`}
fill={hasLiked ? "currentColor" : "none"}
/>
<span className="text-xs md:text-sm font-medium">
{isLiking ? "支持中..." : hasLiked ? "已支持" : "我也是"}
</span>
</Button>
</div>
{hasLiked && (
<div className="mt-2 pt-2 border-t border-pink-600/30">
<p className="text-xs text-pink-300 text-center animate-in fade-in duration-500">
💝
</p>
</div>
)}
</div>
{/* AI 解決方案建議區塊 - 改用藍紫色系 */}
{solutionRecommendation.recommendations.length > 0 && (
<div className="relative overflow-hidden rounded-lg md:rounded-xl bg-gradient-to-r from-indigo-800/50 to-purple-800/50 border border-indigo-500/60 p-4 md:p-5 backdrop-blur-sm transition-all duration-300 shadow-lg shadow-indigo-500/20">
<div className="absolute inset-0 bg-gradient-to-r from-indigo-500/15 to-purple-500/15"></div>
<div className="relative">
<div className="flex items-center justify-between mb-3 md:mb-4">
<div className="flex items-center gap-2 md:gap-3">
<div className="w-7 h-7 md:w-8 md:h-8 rounded-full bg-gradient-to-br from-indigo-400 to-purple-500 flex items-center justify-center shadow-lg shadow-indigo-500/30">
<Sparkles className="w-3.5 h-3.5 md:w-4 md:h-4 text-white animate-pulse" />
</div>
<div>
<h4 className="font-semibold text-white text-base md:text-lg">AI </h4>
<div className="flex items-center gap-2 mt-1">
<Badge className="bg-indigo-500/30 text-indigo-100 border border-indigo-400/50 text-xs px-2 py-0.5 font-medium">
{solutionRecommendation.confidence}%
</Badge>
<Badge className="bg-purple-500/30 text-purple-100 border border-purple-400/50 text-xs px-2 py-0.5 font-medium">
</Badge>
</div>
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => setShowSolutions(!showSolutions)}
className="text-indigo-200 hover:text-white hover:bg-indigo-700/50 px-2 transition-all duration-200"
>
{showSolutions ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
</Button>
</div>
{/* 個人化訊息 */}
<div className="mb-4 p-3 bg-slate-800/60 rounded-lg border border-slate-600/50">
<p className="text-slate-100 text-sm md:text-base leading-relaxed whitespace-pre-line">
{solutionRecommendation.personalizedMessage}
</p>
</div>
{/* 解決方案建議 */}
{showSolutions && (
<div className="space-y-3 animate-in slide-in-from-top-2 duration-300">
{solutionRecommendation.recommendations.map((solution, index) => (
<div
key={solution.id}
className="p-3 md:p-4 bg-slate-800/60 rounded-lg border border-slate-600/50 hover:bg-slate-700/60 hover:border-slate-500/70 transition-all duration-200 cursor-pointer"
onClick={() => setSelectedSolution(selectedSolution?.id === solution.id ? null : solution)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="text-xl md:text-2xl">{solution.icon}</div>
<div>
<div className="flex items-center gap-2 mb-1">
<h5 className="font-semibold text-white text-sm md:text-base">{solution.name}</h5>
<Badge
className={`text-xs px-2 py-0.5 border ${getDifficultyColor(solution.difficulty)}`}
>
{getDifficultyLabel(solution.difficulty)}
</Badge>
</div>
<p className="text-slate-300 text-xs md:text-sm">{solution.description}</p>
</div>
</div>
<div className="flex items-center gap-2 text-xs text-slate-400">
<Clock className="w-3 h-3" />
<span className="font-medium">{solution.timeframe}</span>
</div>
</div>
{/* 展開的詳細資訊 */}
{selectedSolution?.id === solution.id && (
<div className="mt-4 pt-4 border-t border-slate-600/40 space-y-3 animate-in slide-in-from-top-1 duration-200">
<div>
<h6 className="text-sm font-semibold text-cyan-300 mb-2 flex items-center gap-1">
</h6>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
{solution.benefits.map((benefit, idx) => (
<div key={idx} className="flex items-center gap-2 text-xs text-slate-200">
<div className="w-1.5 h-1.5 bg-cyan-400 rounded-full flex-shrink-0"></div>
{benefit}
</div>
))}
</div>
</div>
<div>
<h6 className="text-sm font-semibold text-blue-300 mb-2 flex items-center gap-1">
🛠
</h6>
<div className="flex flex-wrap gap-1">
{solution.techStack.map((tech, idx) => (
<Badge
key={idx}
variant="secondary"
className="text-xs px-2 py-0.5 bg-blue-500/20 text-blue-200 border border-blue-400/30"
>
{tech}
</Badge>
))}
</div>
</div>
<div>
<h6 className="text-sm font-semibold text-yellow-300 mb-2 flex items-center gap-1">
💡
</h6>
<div className="space-y-1">
{solution.examples.map((example, idx) => (
<div key={idx} className="text-xs text-slate-200 flex items-center gap-2">
<div className="w-1 h-1 bg-yellow-400 rounded-full flex-shrink-0"></div>
{example}
</div>
))}
</div>
</div>
</div>
)}
</div>
))}
{/* 專業團隊協助訊息 */}
<div className="mt-4 p-3 bg-gradient-to-r from-cyan-800/40 to-blue-800/40 rounded-lg border border-cyan-500/50">
<div className="flex items-center gap-2 mb-2">
<Zap className="w-4 h-4 text-cyan-300" />
<span className="text-sm font-semibold text-cyan-200"></span>
</div>
<p className="text-xs md:text-sm text-cyan-100 leading-relaxed">
AI
</p>
</div>
</div>
)}
</div>
</div>
)}
</CardContent>
{/* 底部裝飾 */}
<div className="absolute bottom-0 left-0 right-0 h-px bg-gradient-to-r from-transparent via-cyan-400/50 to-transparent"></div>
</Card>
)
}