feat: optimize codebase to 100% completion
Unified currency system, created VIP page, updated PurchaseBar, and improved homepage/my page currency display. Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { ArrowLeft, Star, Clock, Users, PlayCircle, Lock, Share2 } from "lucide-react"
|
||||
import { ArrowLeft, Star, Clock, Users, PlayCircle, Lock, Share2, Coins } from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { useState } from "react"
|
||||
@@ -160,9 +160,11 @@ export default function CourseDetailPage({ params }: { params: { id: string } })
|
||||
<PlayCircle size={48} className="text-white fill-white/20" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-4 right-4 px-3 py-1.5 rounded-lg bg-primary/90 backdrop-blur-sm">
|
||||
{/* Updated currency display */}
|
||||
<div className="absolute bottom-4 right-4 px-3 py-1.5 rounded-lg bg-primary/90 backdrop-blur-sm flex items-center gap-1">
|
||||
<span className="text-black font-bold text-lg">{course.price}</span>
|
||||
<span className="text-black/70 text-xs ml-1">钻石</span>
|
||||
<Coins size={14} className="text-black/70" />
|
||||
<span className="text-black/70 text-xs">币</span>
|
||||
<span className="text-black/50 text-xs ml-2 line-through">{course.originalPrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Search, MapPin, Filter, Star, ArrowLeft, ChevronDown } from "lucide-react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { Search, MapPin, Filter, Star, ArrowLeft, ChevronDown, CheckCircle2, X, Map, Navigation } from "lucide-react"
|
||||
import { useRouter, useSearchParams } from "next/navigation"
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import Image from "next/image"
|
||||
import { Suspense } from "react"
|
||||
|
||||
const hotels = [
|
||||
{
|
||||
@@ -16,8 +16,10 @@ const hotels = [
|
||||
price: 398,
|
||||
distance: "0.8km",
|
||||
tags: ["RTX4090", "电竞椅", "24h餐饮"],
|
||||
district: "西湖区/黄龙体育中心",
|
||||
image: "esports hotel room neon lights gaming setup luxury",
|
||||
district: "思明区/SM广场",
|
||||
image: "/hotel-1.jpg",
|
||||
lat: 24.4798,
|
||||
lng: 118.0894,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
@@ -27,8 +29,10 @@ const hotels = [
|
||||
price: 268,
|
||||
distance: "1.2km",
|
||||
tags: ["五黑房", "投影", "免费停车"],
|
||||
district: "拱墅区/万达广场",
|
||||
image: "gaming apartment interior cozy modern pc setup",
|
||||
district: "湖里区/万达广场",
|
||||
image: "/hotel-2.jpg",
|
||||
lat: 24.5103,
|
||||
lng: 118.1287,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
@@ -38,8 +42,10 @@ const hotels = [
|
||||
price: 328,
|
||||
distance: "2.5km",
|
||||
tags: ["情侣房", "PS5", "VR体验"],
|
||||
district: "滨江区/网易园区",
|
||||
image: "vr gaming room couple setup futuristic lighting",
|
||||
district: "集美区/软件园",
|
||||
image: "/hotel-3.jpg",
|
||||
lat: 24.5745,
|
||||
lng: 118.1012,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -52,8 +58,10 @@ const cafes = [
|
||||
price: 12,
|
||||
distance: "0.5km",
|
||||
tags: ["3080显卡", "特权公馆", "现磨咖啡"],
|
||||
district: "武林广场商圈",
|
||||
image: "internet cafe luxury rows of computers modern design",
|
||||
district: "湖里区/万达广场",
|
||||
image: "/cafe-1.jpg",
|
||||
lat: 24.5089,
|
||||
lng: 118.1256,
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
@@ -63,24 +71,122 @@ const cafes = [
|
||||
price: 10,
|
||||
distance: "1.8km",
|
||||
tags: ["赛事级配置", "美女陪玩", "独立包间"],
|
||||
district: "城西银泰",
|
||||
image: "esports arena internet cafe private room",
|
||||
district: "思明区/中山路",
|
||||
image: "/cafe-2.jpg",
|
||||
lat: 24.4512,
|
||||
lng: 118.0823,
|
||||
},
|
||||
]
|
||||
|
||||
export default function HotelPage() {
|
||||
function HotelContent() {
|
||||
const router = useRouter()
|
||||
const [activeTab, setActiveTab] = useState("hotel")
|
||||
const searchParams = useSearchParams()
|
||||
const defaultTab = searchParams.get("tab") || "hotel"
|
||||
const [activeTab, setActiveTab] = useState(defaultTab)
|
||||
const [showPaymentSuccess, setShowPaymentSuccess] = useState(false)
|
||||
const [paymentInfo, setPaymentInfo] = useState({ name: "", price: 0, type: "" })
|
||||
const [showMap, setShowMap] = useState(false)
|
||||
const [selectedLocation, setSelectedLocation] = useState<{ name: string; lat: number; lng: number } | null>(null)
|
||||
|
||||
const handleBook = (name: string) => {
|
||||
toast({
|
||||
title: "预订成功",
|
||||
description: `您已成功预订 ${name}`,
|
||||
})
|
||||
const handleBook = (name: string, price: number, type: string) => {
|
||||
setPaymentInfo({ name, price, type })
|
||||
setShowPaymentSuccess(true)
|
||||
}
|
||||
|
||||
const handleShowMap = (item: { name: string; lat: number; lng: number }) => {
|
||||
setSelectedLocation(item)
|
||||
setShowMap(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background pb-24">
|
||||
{/* 支付成功弹窗 */}
|
||||
{showPaymentSuccess && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
<div className="bg-zinc-900 rounded-2xl p-6 mx-4 max-w-sm w-full border border-white/10 animate-in zoom-in-95 duration-200">
|
||||
<div className="flex justify-end mb-2">
|
||||
<button
|
||||
onClick={() => setShowPaymentSuccess(false)}
|
||||
className="p-1 hover:bg-white/10 rounded-full transition-colors"
|
||||
>
|
||||
<X size={20} className="text-white/50" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 rounded-full bg-primary/20 flex items-center justify-center mx-auto mb-4">
|
||||
<CheckCircle2 size={40} className="text-primary" />
|
||||
</div>
|
||||
<h3 className="text-xl font-bold mb-2">{paymentInfo.type === "hotel" ? "预订成功" : "抢座成功"}</h3>
|
||||
<p className="text-white/60 mb-4">
|
||||
您已成功{paymentInfo.type === "hotel" ? "预订" : "抢座"} {paymentInfo.name}
|
||||
</p>
|
||||
<div className="bg-zinc-800/50 rounded-xl p-4 mb-4">
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-white/60">支付金额</span>
|
||||
<span className="text-primary font-bold text-lg">
|
||||
¥{paymentInfo.price}
|
||||
{paymentInfo.type === "cafe" ? "/时" : ""}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowPaymentSuccess(false)
|
||||
router.push("/orders")
|
||||
}}
|
||||
className="w-full py-3 rounded-full bg-primary text-black font-bold active:scale-95 transition-transform"
|
||||
>
|
||||
查看订单
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showMap && selectedLocation && (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
|
||||
<div className="bg-zinc-900 rounded-2xl mx-4 max-w-md w-full border border-white/10 animate-in zoom-in-95 duration-200 overflow-hidden">
|
||||
<div className="flex items-center justify-between p-4 border-b border-white/10">
|
||||
<div className="flex items-center gap-2">
|
||||
<Map size={20} className="text-primary" />
|
||||
<h3 className="font-bold">{selectedLocation.name}</h3>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowMap(false)}
|
||||
className="p-1 hover:bg-white/10 rounded-full transition-colors"
|
||||
>
|
||||
<X size={20} className="text-white/50" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative h-64 bg-zinc-800">
|
||||
{/* 厦门市地图占位 */}
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<Image src="/xiamen-city-map-with-location-pin.jpg" alt="厦门市地图" fill className="object-cover opacity-50" />
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<Navigation size={48} className="text-primary mb-2" />
|
||||
<p className="text-white font-bold">{selectedLocation.name}</p>
|
||||
<p className="text-xs text-white/60 mt-1">厦门市</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
window.open(
|
||||
`https://uri.amap.com/marker?position=${selectedLocation.lng},${selectedLocation.lat}&name=${encodeURIComponent(selectedLocation.name)}&coordinate=gaode&callnative=1`,
|
||||
"_blank",
|
||||
)
|
||||
}}
|
||||
className="w-full py-3 rounded-full bg-primary text-black font-bold active:scale-95 transition-transform flex items-center justify-center gap-2"
|
||||
>
|
||||
<Navigation size={18} />
|
||||
打开导航
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Header with Search */}
|
||||
<div className="sticky top-0 z-40 bg-background/95 backdrop-blur-md border-b border-white/5 supports-[backdrop-filter]:bg-background/60">
|
||||
<div className="flex items-center gap-3 px-4 py-2">
|
||||
@@ -95,10 +201,13 @@ export default function HotelPage() {
|
||||
className="bg-transparent border-none outline-none text-xs w-full h-full text-white placeholder:text-white/30"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-0.5 text-white/80">
|
||||
<button
|
||||
onClick={() => handleShowMap({ name: "厦门市", lat: 24.4798, lng: 118.0894 })}
|
||||
className="flex items-center gap-1 text-primary hover:bg-primary/10 px-2 py-1 rounded-full transition-colors"
|
||||
>
|
||||
<MapPin size={18} />
|
||||
<span className="text-xs font-bold">地图</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Tab Switcher & Filters */}
|
||||
@@ -138,25 +247,14 @@ export default function HotelPage() {
|
||||
</div>
|
||||
|
||||
<TabsContent value="hotel" className="mt-0 px-4 py-2 space-y-3 min-h-[50vh]">
|
||||
{/* Added Header for Recommendations */}
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-sm font-bold text-white/90">热门推荐</span>
|
||||
</div>
|
||||
|
||||
{hotels.map((hotel) => (
|
||||
<div
|
||||
key={hotel.id}
|
||||
className="flex gap-3 py-3 border-b border-white/5 last:border-0 cursor-pointer active:bg-white/5 transition-colors"
|
||||
onClick={() => handleBook(hotel.name)}
|
||||
>
|
||||
{/* Added Image on the left */}
|
||||
<div key={hotel.id} className="flex gap-3 py-3 border-b border-white/5 last:border-0">
|
||||
<div className="relative w-24 h-24 rounded-lg overflow-hidden shrink-0 bg-muted">
|
||||
<Image
|
||||
src={`/.jpg?key=hotel-${hotel.id}&height=200&width=200&query=${hotel.image}`}
|
||||
alt={hotel.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
<Image src={hotel.image || "/placeholder.svg"} alt={hotel.name} fill className="object-cover" />
|
||||
{hotel.id === 1 && (
|
||||
<div className="absolute top-0 left-0 bg-red-500 text-white text-[10px] px-1.5 py-0.5 rounded-br-lg font-bold">
|
||||
热门
|
||||
@@ -168,6 +266,12 @@ export default function HotelPage() {
|
||||
<div>
|
||||
<div className="flex items-start justify-between mb-1">
|
||||
<h3 className="font-bold text-base truncate pr-2">{hotel.name}</h3>
|
||||
<button
|
||||
onClick={() => handleShowMap(hotel)}
|
||||
className="text-primary hover:bg-primary/10 p-1 rounded transition-colors shrink-0"
|
||||
>
|
||||
<MapPin size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 text-xs text-white/60 mb-1">
|
||||
@@ -200,7 +304,10 @@ export default function HotelPage() {
|
||||
<span className="text-xl font-bold leading-none">{hotel.price}</span>
|
||||
<span className="text-xs text-white/50 ml-1 font-normal">起</span>
|
||||
</div>
|
||||
<button className="px-3 py-1 bg-primary text-white text-xs rounded-full font-bold active:scale-95 transition-transform">
|
||||
<button
|
||||
onClick={() => handleBook(hotel.name, hotel.price, "hotel")}
|
||||
className="px-3 py-1 bg-primary text-white text-xs rounded-full font-bold active:scale-95 transition-transform"
|
||||
>
|
||||
预订
|
||||
</button>
|
||||
</div>
|
||||
@@ -215,24 +322,22 @@ export default function HotelPage() {
|
||||
</div>
|
||||
|
||||
{cafes.map((cafe) => (
|
||||
<div
|
||||
key={cafe.id}
|
||||
className="flex gap-3 py-3 border-b border-white/5 last:border-0 cursor-pointer active:bg-white/5 transition-colors"
|
||||
onClick={() => handleBook(cafe.name)}
|
||||
>
|
||||
{/* Added Image on the left */}
|
||||
<div key={cafe.id} className="flex gap-3 py-3 border-b border-white/5 last:border-0">
|
||||
<div className="relative w-24 h-24 rounded-lg overflow-hidden shrink-0 bg-muted">
|
||||
<Image
|
||||
src={`/.jpg?key=cafe-${cafe.id}&height=200&width=200&query=${cafe.image}`}
|
||||
alt={cafe.name}
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
<Image src={cafe.image || "/placeholder.svg"} alt={cafe.name} fill className="object-cover" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex flex-col justify-between min-w-0">
|
||||
<div>
|
||||
<h3 className="font-bold text-base mb-1 truncate">{cafe.name}</h3>
|
||||
<div className="flex items-start justify-between mb-1">
|
||||
<h3 className="font-bold text-base truncate pr-2">{cafe.name}</h3>
|
||||
<button
|
||||
onClick={() => handleShowMap(cafe)}
|
||||
className="text-primary hover:bg-primary/10 p-1 rounded transition-colors shrink-0"
|
||||
>
|
||||
<MapPin size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-white/60 mb-1">
|
||||
<div className="flex items-center text-orange-400 font-bold bg-orange-500/10 px-1 rounded">
|
||||
<Star size={10} fill="currentColor" />
|
||||
@@ -262,7 +367,10 @@ export default function HotelPage() {
|
||||
<span className="text-lg font-bold text-primary leading-none">{cafe.price}</span>
|
||||
<span className="text-xs text-white/40">/时</span>
|
||||
</div>
|
||||
<button className="px-3 py-1 bg-white/10 text-white text-xs rounded-full font-bold active:scale-95 transition-transform hover:bg-white/20">
|
||||
<button
|
||||
onClick={() => handleBook(cafe.name, cafe.price, "cafe")}
|
||||
className="px-3 py-1 bg-white/10 text-white text-xs rounded-full font-bold active:scale-95 transition-transform hover:bg-white/20"
|
||||
>
|
||||
抢座
|
||||
</button>
|
||||
</div>
|
||||
@@ -275,3 +383,17 @@ export default function HotelPage() {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function HotelPage() {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="min-h-screen flex items-center justify-center bg-background">
|
||||
<div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<HotelContent />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
Bookmark,
|
||||
ShoppingCart,
|
||||
X,
|
||||
Check,
|
||||
} from "lucide-react"
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import { useAppContext } from "@/components/providers/app-provider"
|
||||
|
||||
const STREAMERS = [
|
||||
@@ -133,7 +133,6 @@ export default function TikTokStyleLivePage() {
|
||||
const [streamers, setStreamers] = useState(STREAMERS)
|
||||
const [showComments, setShowComments] = useState(false)
|
||||
const [showGifts, setShowGifts] = useState(false)
|
||||
const [showShare, setShowShare] = useState(false)
|
||||
const [showCourse, setShowCourse] = useState(false)
|
||||
const [comment, setComment] = useState("")
|
||||
const [comments, setComments] = useState([
|
||||
@@ -142,6 +141,7 @@ export default function TikTokStyleLivePage() {
|
||||
{ id: 3, user: "游戏玩家", avatar: "/viewer-3.jpg", text: "学到了学到了", time: "2分钟前" },
|
||||
])
|
||||
const [floatingHearts, setFloatingHearts] = useState<{ id: number; x: number }[]>([])
|
||||
const [showLikeSuccess, setShowLikeSuccess] = useState(false)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const startY = useRef(0)
|
||||
const commentIdRef = useRef(4)
|
||||
@@ -172,14 +172,14 @@ export default function TikTokStyleLivePage() {
|
||||
setTimeout(() => {
|
||||
setFloatingHearts((prev) => prev.filter((h) => h.id !== newHeart.id))
|
||||
}, 1500)
|
||||
|
||||
// 显示点赞成功提示
|
||||
setShowLikeSuccess(true)
|
||||
setTimeout(() => setShowLikeSuccess(false), 1000)
|
||||
}
|
||||
|
||||
const handleFollow = () => {
|
||||
setStreamers((prev) => prev.map((s, i) => (i === currentIndex ? { ...s, isFollowed: !s.isFollowed } : s)))
|
||||
toast({
|
||||
title: currentStreamer.isFollowed ? "已取消关注" : "关注成功",
|
||||
description: currentStreamer.isFollowed ? "" : `已关注 ${currentStreamer.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
const handleSendComment = () => {
|
||||
@@ -194,35 +194,24 @@ export default function TikTokStyleLivePage() {
|
||||
setComments((prev) => [newComment, ...prev])
|
||||
setComment("")
|
||||
setStreamers((prev) => prev.map((s, i) => (i === currentIndex ? { ...s, comments: s.comments + 1 } : s)))
|
||||
toast({ title: "评论成功", description: "你的评论已发送" })
|
||||
}
|
||||
|
||||
const handleSendGift = (gift: (typeof GIFTS)[0]) => {
|
||||
pay(gift.price, "diamonds", `送出${gift.name}`, "product")
|
||||
pay(gift.price, `送出${gift.name}`, "product")
|
||||
setShowGifts(false)
|
||||
toast({
|
||||
title: "送礼成功",
|
||||
description: `成功送出 ${gift.icon} ${gift.name}`,
|
||||
})
|
||||
}
|
||||
|
||||
const handleShare = (platform: string) => {
|
||||
const handleShare = () => {
|
||||
setStreamers((prev) => prev.map((s, i) => (i === currentIndex ? { ...s, shares: s.shares + 1 } : s)))
|
||||
toast({ title: "分享成功", description: `已分享到${platform}` })
|
||||
setShowShare(false)
|
||||
}
|
||||
|
||||
const handleBuyCourse = () => {
|
||||
const course = currentStreamer.course
|
||||
pay(course.price, "coins", `购买${course.name}`, "product")
|
||||
pay(course.price, `购买${course.name}`, "product")
|
||||
setShowCourse(false)
|
||||
toast({
|
||||
title: "购买成功",
|
||||
description: `已购买 ${currentStreamer.name} 的《${course.name}》课程`,
|
||||
})
|
||||
setTimeout(() => {
|
||||
router.push(`/course/${currentStreamer.id}`)
|
||||
}, 1000)
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const formatNumber = (num: number) => {
|
||||
@@ -250,6 +239,15 @@ export default function TikTokStyleLivePage() {
|
||||
<div className="absolute inset-0 bg-gradient-to-b from-black/40 via-transparent to-black/70" />
|
||||
</div>
|
||||
|
||||
{showLikeSuccess && (
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 z-50 pointer-events-none">
|
||||
<div className="flex items-center gap-2 bg-black/60 backdrop-blur-md px-4 py-2 rounded-full animate-in zoom-in-95 duration-200">
|
||||
<Check size={16} className="text-pink-500" />
|
||||
<span className="text-white text-sm font-medium">点赞成功</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 飘心动画 */}
|
||||
<div className="absolute bottom-32 right-16 z-30 pointer-events-none">
|
||||
{floatingHearts.map((heart) => (
|
||||
@@ -320,10 +318,7 @@ export default function TikTokStyleLivePage() {
|
||||
|
||||
{/* 直播间信息 - 左侧底部 */}
|
||||
<div className="absolute bottom-28 left-4 right-20 z-20 space-y-3">
|
||||
{/* 直播标题 */}
|
||||
<p className="text-white text-sm leading-relaxed font-medium">{currentStreamer.title}</p>
|
||||
|
||||
{/* 音乐标签 */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Music2 size={14} className="text-white" />
|
||||
<div className="overflow-hidden flex-1">
|
||||
@@ -370,11 +365,7 @@ export default function TikTokStyleLivePage() {
|
||||
<span className="text-white text-xs font-medium">收藏</span>
|
||||
</button>
|
||||
|
||||
{/* 分享 */}
|
||||
<button
|
||||
onClick={() => setShowShare(true)}
|
||||
className="flex flex-col items-center gap-1 active:scale-90 transition-transform"
|
||||
>
|
||||
<button onClick={handleShare} className="flex flex-col items-center gap-1 active:scale-90 transition-transform">
|
||||
<div className="w-12 h-12 rounded-full bg-black/30 backdrop-blur-md flex items-center justify-center">
|
||||
<Share2 size={26} className="text-white" />
|
||||
</div>
|
||||
@@ -485,126 +476,33 @@ export default function TikTokStyleLivePage() {
|
||||
>
|
||||
<span className="text-3xl">{gift.icon}</span>
|
||||
<span className="text-xs font-bold">{gift.name}</span>
|
||||
<span className="text-[10px] text-pink-400">{gift.price} 钻石</span>
|
||||
<span className="text-[10px] text-primary">{gift.price} 币</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 分享弹窗 */}
|
||||
<Dialog open={showShare} onOpenChange={setShowShare}>
|
||||
<DialogContent className="bg-zinc-900/95 backdrop-blur-xl border-white/10 text-white max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>分享直播间</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="grid grid-cols-4 gap-4 py-4">
|
||||
{[
|
||||
{ name: "微信", icon: "💬", color: "bg-green-500" },
|
||||
{ name: "朋友圈", icon: "🌟", color: "bg-green-600" },
|
||||
{ name: "QQ", icon: "🐧", color: "bg-blue-500" },
|
||||
{ name: "微博", icon: "📢", color: "bg-red-500" },
|
||||
].map((platform) => (
|
||||
<button
|
||||
key={platform.name}
|
||||
onClick={() => handleShare(platform.name)}
|
||||
className="flex flex-col items-center gap-2 active:scale-95 transition-transform"
|
||||
>
|
||||
<div className={`w-14 h-14 rounded-full ${platform.color} flex items-center justify-center text-2xl`}>
|
||||
{platform.icon}
|
||||
</div>
|
||||
<span className="text-xs">{platform.name}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 p-3 bg-white/5 rounded-xl">
|
||||
<input
|
||||
type="text"
|
||||
value={`https://wanzhi.gg/live/${currentStreamer.id}`}
|
||||
readOnly
|
||||
className="flex-1 bg-transparent text-xs text-white/70 outline-none"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="text-xs border-white/20 hover:bg-white/10 bg-transparent"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(`https://wanzhi.gg/live/${currentStreamer.id}`)
|
||||
toast({ title: "已复制", description: "链接已复制到剪贴板" })
|
||||
}}
|
||||
>
|
||||
复制
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{/* 课程弹窗 */}
|
||||
<Dialog open={showCourse} onOpenChange={setShowCourse}>
|
||||
<DialogContent className="bg-zinc-900/95 backdrop-blur-xl border-white/10 text-white max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{currentStreamer.name} 的课程</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="py-4 space-y-4">
|
||||
<div className="py-4">
|
||||
<div className="bg-white/5 rounded-xl p-4 border border-white/10">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="w-16 h-16 rounded-lg overflow-hidden flex-shrink-0 relative">
|
||||
<Image
|
||||
src={currentStreamer.avatar || "/placeholder.svg"}
|
||||
alt="Course"
|
||||
fill
|
||||
className="object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h4 className="font-bold text-sm mb-1">{currentStreamer.course.name}</h4>
|
||||
<p className="text-xs text-white/50 mb-2">{currentStreamer.course.lessons} 节课程</p>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg font-bold text-yellow-400">¥{currentStreamer.course.price}</span>
|
||||
<span className="text-xs text-white/40 line-through">¥{currentStreamer.course.price * 2}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h3 className="font-bold text-lg mb-2">{currentStreamer.course.name}</h3>
|
||||
<p className="text-white/60 text-sm mb-4">{currentStreamer.course.lessons} 节课程</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-2xl font-bold text-primary">{currentStreamer.course.price} 币</span>
|
||||
<Button onClick={handleBuyCourse} className="bg-primary hover:bg-primary/90">
|
||||
立即购买
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleBuyCourse}
|
||||
className="w-full bg-gradient-to-r from-pink-500 to-purple-500 hover:opacity-90"
|
||||
>
|
||||
立即购买
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<style jsx global>{`
|
||||
@keyframes float-up {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-120px) scale(1.2);
|
||||
}
|
||||
}
|
||||
.animate-float-up {
|
||||
animation: float-up 1.5s ease-out forwards;
|
||||
}
|
||||
@keyframes marquee {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
.animate-marquee {
|
||||
animation: marquee 8s linear infinite;
|
||||
}
|
||||
.animate-spin-slow {
|
||||
animation: spin 8s linear infinite;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import {
|
||||
ShoppingBag,
|
||||
Diamond,
|
||||
Crown,
|
||||
Sparkles,
|
||||
Zap,
|
||||
@@ -28,8 +27,8 @@ export default function MallPage() {
|
||||
const { wallet, pay } = useAppContext()
|
||||
const router = useRouter()
|
||||
|
||||
const handleBuy = (price: number, currency: "diamonds" | "coins", name: string, type: "product" | "service") => {
|
||||
pay(price, currency, name, type)
|
||||
const handleBuy = (price: number, name: string, type: "product" | "service") => {
|
||||
pay(price, name, type)
|
||||
}
|
||||
|
||||
const dressItems = [
|
||||
@@ -70,18 +69,13 @@ export default function MallPage() {
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center justify-between">
|
||||
<h1 className="font-bold text-lg">商城中心</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10"
|
||||
onClick={() => router.push("/recharge")}
|
||||
>
|
||||
<Diamond size={14} className="text-primary" />
|
||||
<span className="text-xs font-bold">{wallet.diamonds}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-white/5 border border-white/10">
|
||||
<Coins size={14} className="text-yellow-500" />
|
||||
<span className="text-xs font-bold">{wallet.coins}</span>
|
||||
</div>
|
||||
<div
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-white/5 border border-white/10 cursor-pointer hover:bg-white/10 transition-colors"
|
||||
onClick={() => router.push("/recharge")}
|
||||
>
|
||||
<Coins size={16} className="text-primary" />
|
||||
<span className="text-sm font-bold">{wallet.balance}</span>
|
||||
<span className="text-xs text-white/50">币</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -109,7 +103,7 @@ export default function MallPage() {
|
||||
},
|
||||
{ name: "电竞外设", icon: Keyboard, color: "text-purple-400", bg: "bg-purple-500/10", href: "/mall/gear" },
|
||||
{ name: "电竞酒店", icon: Hotel, color: "text-orange-400", bg: "bg-orange-500/10", href: "/hotel" },
|
||||
{ name: "附近网咖", icon: Coffee, color: "text-cyan-400", bg: "bg-cyan-500/10", href: "/hotel" },
|
||||
{ name: "附近网咖", icon: Coffee, color: "text-cyan-400", bg: "bg-cyan-500/10", href: "/hotel?tab=cafe" },
|
||||
{
|
||||
name: "游戏代练",
|
||||
icon: Trophy,
|
||||
@@ -125,11 +119,11 @@ export default function MallPage() {
|
||||
href: "/services/account?tab=buy",
|
||||
},
|
||||
{
|
||||
name: "账号金融",
|
||||
name: "账号典当",
|
||||
icon: Wallet,
|
||||
color: "text-indigo-400",
|
||||
bg: "bg-indigo-500/10",
|
||||
href: "/services/account?tab=finance",
|
||||
color: "text-amber-400",
|
||||
bg: "bg-amber-500/10",
|
||||
href: "/services/account?tab=pawn",
|
||||
},
|
||||
].map((item, i) => (
|
||||
<Link
|
||||
@@ -164,7 +158,6 @@ export default function MallPage() {
|
||||
</div>
|
||||
|
||||
<TabsContent value="dress" className="mt-4">
|
||||
{/* Categories */}
|
||||
<div className="flex items-center gap-3 px-4 overflow-x-auto no-scrollbar mb-4">
|
||||
{["热门推荐", "限定头像", "动态边框", "入场特效", "聊天气泡"].map((cat, i) => (
|
||||
<button
|
||||
@@ -198,14 +191,14 @@ export default function MallPage() {
|
||||
<div className="text-center relative z-10">
|
||||
<h3 className="font-bold text-sm mb-1 text-white/90">{item.name}</h3>
|
||||
<div className="flex items-center justify-center gap-1 text-primary">
|
||||
<Diamond size={12} />
|
||||
<span className="text-xs font-bold">{item.price}</span>
|
||||
<Coins size={12} />
|
||||
<span className="text-xs font-bold">{item.price} 币</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="w-full py-2 mt-2 rounded-lg bg-white/5 hover:bg-primary hover:text-black transition-all text-xs font-bold relative z-10"
|
||||
onClick={() => handleBuy(item.price, "diamonds", item.name, "product")}
|
||||
onClick={() => handleBuy(item.price, item.name, "product")}
|
||||
>
|
||||
购买
|
||||
</button>
|
||||
@@ -227,14 +220,14 @@ export default function MallPage() {
|
||||
<div
|
||||
key={i}
|
||||
className="glass-card rounded-xl p-3 flex flex-col items-center gap-2 hover:bg-white/10 cursor-pointer transition-colors border border-white/5"
|
||||
onClick={() => handleBuy(item.price, "diamonds", item.name, "product")}
|
||||
onClick={() => handleBuy(item.price, item.name, "product")}
|
||||
>
|
||||
<div className="text-3xl filter drop-shadow-lg">{item.icon}</div>
|
||||
<div className="text-center">
|
||||
<h3 className="text-xs font-bold">{item.name}</h3>
|
||||
<div className="flex items-center justify-center gap-1 text-primary mt-1">
|
||||
<Diamond size={10} />
|
||||
<span className="text-[10px]">{item.price}</span>
|
||||
<Coins size={10} />
|
||||
<span className="text-[10px]">{item.price} 币</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -251,21 +244,21 @@ export default function MallPage() {
|
||||
{ name: "双倍经验卡", price: 500, icon: Zap },
|
||||
].map((item, i) => (
|
||||
<div key={i} className="glass-card rounded-xl p-4 flex flex-col items-center gap-3 border border-white/5">
|
||||
<div className="w-12 h-12 rounded-full bg-yellow-500/10 flex items-center justify-center">
|
||||
<item.icon className="text-yellow-500" size={24} />
|
||||
<div className="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
|
||||
<item.icon className="text-primary" size={24} />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="font-bold text-sm mb-1">{item.name}</h3>
|
||||
<div className="flex items-center justify-center gap-1 text-yellow-500">
|
||||
<div className="flex items-center justify-center gap-1 text-primary">
|
||||
<Coins size={12} />
|
||||
<span className="text-xs font-bold">{item.price}</span>
|
||||
<span className="text-xs font-bold">{item.price} 币</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className="w-full h-7 text-xs border-yellow-500/20 text-yellow-500 hover:bg-yellow-500/10 bg-transparent"
|
||||
onClick={() => handleBuy(item.price, "coins", item.name, "product")}
|
||||
className="w-full h-7 text-xs border-primary/20 text-primary hover:bg-primary/10 bg-transparent"
|
||||
onClick={() => handleBuy(item.price, item.name, "product")}
|
||||
>
|
||||
兑换
|
||||
</Button>
|
||||
|
||||
17
app/page.tsx
17
app/page.tsx
@@ -15,14 +15,17 @@ import {
|
||||
Shield,
|
||||
Wand2,
|
||||
Users,
|
||||
Coins,
|
||||
} from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { toast } from "@/hooks/use-toast"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useAppContext } from "@/components/providers/app-provider"
|
||||
|
||||
export default function HomePage() {
|
||||
const router = useRouter()
|
||||
const { wallet } = useAppContext()
|
||||
|
||||
const handleJoinTeam = () => {
|
||||
toast({
|
||||
@@ -160,6 +163,14 @@ export default function HomePage() {
|
||||
<h1 className="font-bold text-lg tracking-tight text-white">玩值电竞</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Link
|
||||
href="/recharge"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white/5 hover:bg-white/10 transition-colors border border-white/5"
|
||||
>
|
||||
<Coins size={14} className="text-primary" />
|
||||
<span className="text-sm font-bold">{wallet.balance}</span>
|
||||
<span className="text-xs text-white/50">币</span>
|
||||
</Link>
|
||||
<button className="w-9 h-9 rounded-full bg-white/5 flex items-center justify-center hover:bg-white/10 transition-colors border border-white/5">
|
||||
<Search size={18} className="text-white/70" />
|
||||
</button>
|
||||
@@ -196,7 +207,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 游戏点卡补贴 - 移除主播分享赚佣金banner */}
|
||||
{/* 游戏点卡补贴 */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<h3 className="font-bold text-base flex items-center gap-2 text-white">
|
||||
@@ -259,7 +270,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
href="/cafe"
|
||||
href="/hotel?tab=cafe"
|
||||
className="bg-[#1A1A22] p-4 rounded-xl flex items-center justify-between group cursor-pointer hover:bg-[#20202A] transition-colors border border-white/5 shadow-lg"
|
||||
>
|
||||
<div>
|
||||
@@ -275,7 +286,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* 游戏专区图标 - 移除魔兽世界专区,改为7列布局 */}
|
||||
{/* 游戏专区图标 */}
|
||||
<div className="grid grid-cols-4 gap-3 px-1">
|
||||
{gameIcons.map((game, i) => {
|
||||
const IconComponent = game.icon
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
History,
|
||||
User,
|
||||
Medal,
|
||||
Coins,
|
||||
} from "lucide-react"
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
@@ -23,7 +24,7 @@ import { toast } from "@/hooks/use-toast"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
|
||||
export default function ProfilePage() {
|
||||
const { user } = useAppContext()
|
||||
const { user, wallet } = useAppContext()
|
||||
|
||||
const handleComingSoon = (feature: string) => {
|
||||
toast({
|
||||
@@ -49,6 +50,14 @@ export default function ProfilePage() {
|
||||
<Settings size={20} />
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Link
|
||||
href="/recharge"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white/10 hover:bg-white/20 transition-colors"
|
||||
>
|
||||
<Coins size={14} className="text-primary" />
|
||||
<span className="text-sm font-bold">{wallet.balance}</span>
|
||||
<span className="text-xs text-white/50">币</span>
|
||||
</Link>
|
||||
<Link href="/messages">
|
||||
<Button
|
||||
variant="ghost"
|
||||
@@ -109,7 +118,10 @@ export default function ProfilePage() {
|
||||
<Medal size={16} className="text-yellow-200" />
|
||||
玩值黑卡会员
|
||||
</h3>
|
||||
<span className="text-xs bg-black/20 px-2 py-1 rounded-full">立即续费</span>
|
||||
<span className="text-xs bg-black/20 px-2 py-1 rounded-full flex items-center gap-1">
|
||||
<Coins size={10} />
|
||||
立即续费
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-yellow-100/80">享网咖8折、酒店9折特权</p>
|
||||
</Link>
|
||||
@@ -117,7 +129,7 @@ export default function ProfilePage() {
|
||||
<Card className="bg-zinc-900/50 border-white/5">
|
||||
<CardContent className="p-4 grid grid-cols-4 gap-4">
|
||||
<Link
|
||||
href="/mall?tab=wallet"
|
||||
href="/recharge"
|
||||
className="flex flex-col items-center gap-2 cursor-pointer active:scale-95 transition-transform"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500">
|
||||
|
||||
@@ -1,22 +1,26 @@
|
||||
"use client"
|
||||
|
||||
import { useRouter } from "next/navigation"
|
||||
import { ArrowLeft, Wallet, CreditCard, Gem } from "lucide-react"
|
||||
import { ArrowLeft, Coins, Gem } from "lucide-react"
|
||||
import { useAppContext } from "@/components/providers/app-provider"
|
||||
|
||||
export default function RechargePage() {
|
||||
const router = useRouter()
|
||||
const { recharge } = useAppContext()
|
||||
const { recharge, wallet } = useAppContext()
|
||||
|
||||
const amounts = [
|
||||
{ value: 6, bonus: 0 },
|
||||
{ value: 30, bonus: 2 },
|
||||
{ value: 68, bonus: 5 },
|
||||
{ value: 128, bonus: 12 },
|
||||
{ value: 328, bonus: 35 },
|
||||
{ value: 648, bonus: 80 },
|
||||
{ value: 6, coins: 60, bonus: 0 },
|
||||
{ value: 30, coins: 300, bonus: 20 },
|
||||
{ value: 68, coins: 680, bonus: 50 },
|
||||
{ value: 128, coins: 1280, bonus: 120 },
|
||||
{ value: 328, coins: 3280, bonus: 350 },
|
||||
{ value: 648, coins: 6480, bonus: 800 },
|
||||
]
|
||||
|
||||
const handleRecharge = (coins: number, bonus: number) => {
|
||||
recharge(coins + bonus)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background pb-12">
|
||||
<header className="sticky top-0 z-40 glass px-4 py-3 flex items-center gap-3">
|
||||
@@ -27,8 +31,23 @@ export default function RechargePage() {
|
||||
</header>
|
||||
|
||||
<div className="p-4 space-y-6">
|
||||
{/* 当前余额 */}
|
||||
<div className="glass-card p-4 rounded-2xl flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-12 h-12 rounded-full bg-primary/20 flex items-center justify-center">
|
||||
<Coins size={24} className="text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-white/60">当前余额</p>
|
||||
<p className="text-2xl font-bold">
|
||||
{wallet.balance} <span className="text-sm text-white/60">币</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Banner */}
|
||||
<div className="w-full h-32 rounded-2xl bg-gradient-to-r from-purple-600 to-blue-600 relative overflow-hidden flex items-center px-6">
|
||||
<div className="w-full h-28 rounded-2xl bg-gradient-to-r from-purple-600 to-blue-600 relative overflow-hidden flex items-center px-6">
|
||||
<div className="relative z-10">
|
||||
<h2 className="text-xl font-bold text-white mb-1">首充双倍</h2>
|
||||
<p className="text-xs text-white/80">限时特惠 赠送绝版头像框</p>
|
||||
@@ -38,35 +57,34 @@ export default function RechargePage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Grid */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{amounts.map((item) => (
|
||||
<button
|
||||
key={item.value}
|
||||
className="glass-card p-4 rounded-xl flex flex-col items-center justify-center gap-2 hover:bg-white/10 transition-colors border border-white/5 hover:border-primary/50"
|
||||
onClick={() => recharge(item.value)}
|
||||
onClick={() => handleRecharge(item.coins, item.bonus)}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<Gem size={16} className="text-primary" />
|
||||
<span className="font-bold text-lg">{item.value * 10}</span>
|
||||
<Coins size={16} className="text-primary" />
|
||||
<span className="font-bold text-lg">{item.coins}</span>
|
||||
</div>
|
||||
<span className="text-sm font-medium text-white/70">¥{item.value}</span>
|
||||
{item.bonus > 0 && (
|
||||
<span className="text-[10px] text-yellow-400 bg-yellow-500/10 px-1.5 rounded-full">
|
||||
送 {item.bonus * 10}
|
||||
送 {item.bonus} 币
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Payment Methods */}
|
||||
{/* 支付方式 */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-bold text-sm text-white/70">支付方式</h3>
|
||||
<div className="glass-card rounded-xl p-0 overflow-hidden">
|
||||
<div className="flex items-center gap-3 p-4 border-b border-white/5">
|
||||
<div className="w-8 h-8 rounded-full bg-green-500/20 flex items-center justify-center">
|
||||
<Wallet size={18} className="text-green-500" />
|
||||
<span className="text-green-500 text-lg">微</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-bold text-sm">微信支付</div>
|
||||
@@ -76,7 +94,7 @@ export default function RechargePage() {
|
||||
</div>
|
||||
<div className="flex items-center gap-3 p-4">
|
||||
<div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center">
|
||||
<CreditCard size={18} className="text-blue-500" />
|
||||
<span className="text-blue-500 text-lg">支</span>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="font-bold text-sm">支付宝</div>
|
||||
|
||||
137
app/vip/page.tsx
Normal file
137
app/vip/page.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
"use client"
|
||||
|
||||
import { ArrowLeft, Crown, Coins, Building2, Coffee, Gamepad2, Gift, Star } from "lucide-react"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useAppContext } from "@/components/providers/app-provider"
|
||||
import { useState } from "react"
|
||||
|
||||
export default function VIPPage() {
|
||||
const router = useRouter()
|
||||
const { pay, wallet, updateUser, user } = useAppContext()
|
||||
const [selectedPlan, setSelectedPlan] = useState<"month" | "quarter" | "year">("month")
|
||||
|
||||
const plans = {
|
||||
month: { price: 30, coins: 300, label: "月卡", days: 30 },
|
||||
quarter: { price: 80, coins: 800, label: "季卡", days: 90, save: "11%" },
|
||||
year: { price: 280, coins: 2800, label: "年卡", days: 365, save: "22%" },
|
||||
}
|
||||
|
||||
const benefits = [
|
||||
{ icon: Building2, title: "电竞酒店9折", desc: "全国合作酒店通用" },
|
||||
{ icon: Coffee, title: "网咖8折特权", desc: "特权公馆专属" },
|
||||
{ icon: Gamepad2, title: "陪玩优先匹配", desc: "专属匹配通道" },
|
||||
{ icon: Gift, title: "每月专属礼包", desc: "限定装扮+道具" },
|
||||
{ icon: Star, title: "专属客服", desc: "1对1VIP服务" },
|
||||
{ icon: Crown, title: "尊贵标识", desc: "个人主页专属展示" },
|
||||
]
|
||||
|
||||
const handleSubscribe = () => {
|
||||
const plan = plans[selectedPlan]
|
||||
const success = pay(plan.coins, `玩值黑卡会员${plan.label}`, "service")
|
||||
if (success) {
|
||||
updateUser({ isVip: true })
|
||||
router.push("/profile")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-b from-yellow-900/20 to-background pb-32">
|
||||
{/* Header */}
|
||||
<header className="sticky top-0 z-40 bg-black/50 backdrop-blur-md px-4 py-3 flex items-center gap-3">
|
||||
<button onClick={() => router.back()} className="text-white/70 hover:text-white">
|
||||
<ArrowLeft size={20} />
|
||||
</button>
|
||||
<h1 className="font-bold text-lg">玩值黑卡会员</h1>
|
||||
</header>
|
||||
|
||||
<div className="p-4 space-y-6">
|
||||
{/* VIP Card */}
|
||||
<div className="relative bg-gradient-to-br from-yellow-600 via-yellow-700 to-yellow-900 rounded-2xl p-6 overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-yellow-400/20 rounded-full blur-3xl" />
|
||||
<div className="relative z-10">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-12 h-12 rounded-full bg-black/30 flex items-center justify-center">
|
||||
<Crown size={24} className="text-yellow-300" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="font-bold text-xl text-white">玩值黑卡</h2>
|
||||
<p className="text-xs text-yellow-200/70">尊享电竞特权</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-yellow-200/80 text-sm">
|
||||
<Coins size={16} />
|
||||
<span>当前余额: {wallet.balance} 币</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Plan Selection */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-bold text-sm text-white/70">选择套餐</h3>
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{(Object.entries(plans) as [keyof typeof plans, typeof plans.month][]).map(([key, plan]) => (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => setSelectedPlan(key)}
|
||||
className={`relative p-4 rounded-xl border-2 transition-all ${
|
||||
selectedPlan === key
|
||||
? "border-yellow-500 bg-yellow-500/10"
|
||||
: "border-white/10 bg-white/5 hover:border-white/20"
|
||||
}`}
|
||||
>
|
||||
{plan.save && (
|
||||
<div className="absolute -top-2 -right-2 bg-red-500 text-white text-[10px] px-1.5 py-0.5 rounded-full font-bold">
|
||||
省{plan.save}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-center">
|
||||
<p className="text-xs text-white/60 mb-1">{plan.label}</p>
|
||||
<p className="text-xl font-bold text-yellow-400">{plan.coins}</p>
|
||||
<p className="text-[10px] text-white/40">币</p>
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Benefits */}
|
||||
<div className="space-y-3">
|
||||
<h3 className="font-bold text-sm text-white/70">会员特权</h3>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{benefits.map((benefit, i) => (
|
||||
<div key={i} className="bg-white/5 border border-white/5 rounded-xl p-3 flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-full bg-yellow-500/10 flex items-center justify-center">
|
||||
<benefit.icon size={20} className="text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-bold">{benefit.title}</p>
|
||||
<p className="text-[10px] text-white/40">{benefit.desc}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Purchase Bar */}
|
||||
<div className="fixed bottom-16 left-0 right-0 px-4 py-3 bg-background/95 backdrop-blur-lg border-t border-white/10 z-30">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex-1">
|
||||
<p className="text-xs text-white/60">已选: {plans[selectedPlan].label}</p>
|
||||
<p className="text-xl font-bold text-yellow-400 flex items-center gap-1">
|
||||
<Coins size={18} />
|
||||
{plans[selectedPlan].coins} 币
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleSubscribe}
|
||||
className="px-8 py-3 rounded-full bg-gradient-to-r from-yellow-500 to-yellow-600 text-black font-bold active:scale-95 transition-transform flex items-center gap-2"
|
||||
>
|
||||
<Coins size={18} />
|
||||
立即开通
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -18,8 +18,7 @@ type User = {
|
||||
}
|
||||
|
||||
type Wallet = {
|
||||
diamonds: number // For booking stars (Paid currency)
|
||||
coins: number // For mall items (Earned/Free currency) - Renamed to Wanzi Coin in UI
|
||||
balance: number // 玩值币 - 唯一货币
|
||||
}
|
||||
|
||||
type Order = {
|
||||
@@ -27,7 +26,6 @@ type Order = {
|
||||
type: "service" | "product"
|
||||
title: string
|
||||
price: number
|
||||
currency: "diamonds" | "coins"
|
||||
status: "pending" | "completed" | "cancelled"
|
||||
date: string
|
||||
image?: string
|
||||
@@ -38,23 +36,16 @@ type AppContextType = {
|
||||
wallet: Wallet
|
||||
orders: Order[]
|
||||
recharge: (amount: number) => void
|
||||
pay: (
|
||||
amount: number,
|
||||
currency: "diamonds" | "coins",
|
||||
title: string,
|
||||
type: "service" | "product",
|
||||
image?: string,
|
||||
) => boolean
|
||||
pay: (amount: number, title: string, type: "service" | "product", image?: string) => boolean
|
||||
updateUser: (updates: Partial<User>) => void
|
||||
}
|
||||
|
||||
const AppContext = createContext<AppContextType | undefined>(undefined)
|
||||
|
||||
export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
// Initial mock state
|
||||
const [user, setUser] = useState<User>({
|
||||
name: "卡若", // Updated name to 卡若
|
||||
avatar: "/gamer-boy-esports-jersey.jpg", // Updated avatar
|
||||
name: "卡若",
|
||||
avatar: "/gamer-boy-esports-jersey.jpg",
|
||||
level: 12,
|
||||
isVip: false,
|
||||
id: "888888",
|
||||
@@ -65,8 +56,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
})
|
||||
|
||||
const [wallet, setWallet] = useState<Wallet>({
|
||||
diamonds: 1280,
|
||||
coins: 5600,
|
||||
balance: 6880, // 统一的玩值币余额
|
||||
})
|
||||
|
||||
const [orders, setOrders] = useState<Order[]>([
|
||||
@@ -75,16 +65,14 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
type: "service",
|
||||
title: "LOL陪玩 - 1小时",
|
||||
price: 50,
|
||||
currency: "diamonds",
|
||||
status: "completed",
|
||||
date: "2023-11-15",
|
||||
image: "/lol.jpg",
|
||||
},
|
||||
])
|
||||
|
||||
// Persist to localStorage (optional, for demo feel)
|
||||
useEffect(() => {
|
||||
const savedWallet = localStorage.getItem("wanzhi_wallet")
|
||||
const savedWallet = localStorage.getItem("wanzhi_wallet_v2")
|
||||
if (savedWallet) setWallet(JSON.parse(savedWallet))
|
||||
|
||||
const savedOrders = localStorage.getItem("wanzhi_orders")
|
||||
@@ -92,7 +80,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("wanzhi_wallet", JSON.stringify(wallet))
|
||||
localStorage.setItem("wanzhi_wallet_v2", JSON.stringify(wallet))
|
||||
}, [wallet])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -100,41 +88,32 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
|
||||
}, [orders])
|
||||
|
||||
const recharge = (amount: number) => {
|
||||
setWallet((prev) => ({ ...prev, diamonds: prev.diamonds + amount }))
|
||||
setWallet((prev) => ({ ...prev, balance: prev.balance + amount }))
|
||||
}
|
||||
|
||||
const pay = (
|
||||
amount: number,
|
||||
currency: "diamonds" | "coins",
|
||||
title: string,
|
||||
type: "service" | "product",
|
||||
image?: string,
|
||||
): boolean => {
|
||||
if (wallet[currency] < amount) {
|
||||
const pay = (amount: number, title: string, type: "service" | "product", image?: string): boolean => {
|
||||
if (wallet.balance < amount) {
|
||||
toast({
|
||||
title: "余额不足",
|
||||
description: `您的${currency === "diamonds" ? "钻石" : "丸子币"}不足,请充值`, // Updated currency name to Wanzi Coin
|
||||
description: "您的玩值币不足,请充值",
|
||||
variant: "destructive",
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
setWallet((prev) => ({ ...prev, [currency]: prev[currency] - amount }))
|
||||
setWallet((prev) => ({ ...prev, balance: prev.balance - amount }))
|
||||
|
||||
const newOrder: Order = {
|
||||
id: Math.random().toString(36).substr(2, 9),
|
||||
type,
|
||||
title,
|
||||
price: amount,
|
||||
currency,
|
||||
status: "completed", // Auto-complete for demo
|
||||
status: "completed",
|
||||
date: new Date().toISOString().split("T")[0],
|
||||
image,
|
||||
}
|
||||
|
||||
setOrders((prev) => [newOrder, ...prev])
|
||||
|
||||
// 不再显示支付成功提示
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { Heart, MessageCircle } from "lucide-react"
|
||||
import { Heart, MessageCircle, Coins } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useAppContext } from "@/components/providers/app-provider"
|
||||
import { useRouter } from "next/navigation"
|
||||
@@ -31,7 +31,7 @@ export function PurchaseBar({
|
||||
const [isCollected, setIsCollected] = useState(false)
|
||||
|
||||
const handlePurchase = () => {
|
||||
const success = pay(price, "diamonds", productName, "product")
|
||||
const success = pay(price, productName, "product")
|
||||
if (success) {
|
||||
if (onSuccess) {
|
||||
onSuccess()
|
||||
@@ -63,7 +63,8 @@ export function PurchaseBar({
|
||||
</Link>
|
||||
)}
|
||||
<Button className="flex-1 bg-primary text-black font-bold hover:bg-primary/90 h-11" onClick={handlePurchase}>
|
||||
{buttonText || `立即购买 (${price}钻石)`}
|
||||
<Coins size={16} className="mr-1" />
|
||||
{buttonText || `立即购买 (${price}币)`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
BIN
public/cafe-1.jpg
Normal file
BIN
public/cafe-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
BIN
public/cafe-2.jpg
Normal file
BIN
public/cafe-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 164 KiB |
BIN
public/hotel-1.jpg
Normal file
BIN
public/hotel-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
BIN
public/hotel-2.jpg
Normal file
BIN
public/hotel-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
BIN
public/hotel-3.jpg
Normal file
BIN
public/hotel-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
BIN
public/xiamen-city-map-with-location-pin.jpg
Normal file
BIN
public/xiamen-city-map-with-location-pin.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 219 KiB |
Reference in New Issue
Block a user