charles_c 5 mesi fa
parent
commit
ad8467ccb2

+ 138 - 0
src/app/api/activity/route.js

@@ -0,0 +1,138 @@
+import dbConnect from "../../lib/dbConnect";
+import Activity from "../../models/Activity";
+import { NextResponse } from "next/server";
+import { setCORSHeaders, handleError } from "../../lib/apiUtils";
+
+export async function GET(request) {
+  await dbConnect();
+  console.log("GET request received");
+  try {
+    const isFromFrontend = request.headers.get("x-from-frontend") === "true";
+
+    let activities;
+    let message;
+
+    if (isFromFrontend) {
+      // 前端请求,只返回 isActive 为 true 的记录
+      activities = await Activity.find({ isActive: true }).sort({
+        createdAt: -1,
+      });
+      message = activities.length > 0 ? "成功获取活跃活动" : "当前没有活跃活动";
+    } else {
+      // 其他请求,返回所有记录
+      activities = await Activity.find().sort({ createdAt: -1 });
+      message = activities.length > 0 ? "成功获取所有活动" : "当前没有任何活动";
+    }
+
+    const response = NextResponse.json(
+      {
+        success: true,
+        data: activities,
+        message,
+        total: activities.length,
+      },
+      { status: 200 }
+    );
+
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("Error in GET /api/activity:", error);
+    return handleError(error);
+  }
+}
+
+export async function POST(request) {
+  await dbConnect();
+  try {
+    const activityData = await request.json();
+    const activity = await Activity.create(activityData);
+    const response = NextResponse.json(
+      {
+        success: true,
+        message: "Activity created successfully",
+        data: activity,
+      },
+      { status: 201 }
+    );
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("Error creating activity:", error);
+    return handleError(error);
+  }
+}
+
+export async function PUT(request) {
+  await dbConnect();
+  try {
+    const { id, ...updateData } = await request.json();
+    console.log("Updating activity with id:", id);
+    console.log("Update data:", updateData);
+
+    if (!id) {
+      return setCORSHeaders(
+        NextResponse.json(
+          { success: false, error: "Activity ID is required" },
+          { status: 400 }
+        )
+      );
+    }
+
+    const updatedActivity = await Activity.findByIdAndUpdate(id, updateData, {
+      new: true,
+      runValidators: true,
+    });
+
+    if (!updatedActivity) {
+      return setCORSHeaders(
+        NextResponse.json(
+          { success: false, error: "Activity not found" },
+          { status: 404 }
+        )
+      );
+    }
+
+    const response = NextResponse.json(
+      {
+        success: true,
+        data: updatedActivity,
+        message: "Activity updated successfully",
+      },
+      { status: 200 }
+    );
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("Error updating activity:", error);
+    return handleError(error);
+  }
+}
+
+export async function DELETE(request) {
+  await dbConnect();
+  try {
+    const { searchParams } = new URL(request.url);
+    const id = searchParams.get("id");
+
+    const deletedActivity = await Activity.findByIdAndDelete(id);
+
+    if (!deletedActivity) {
+      return NextResponse.json(
+        { success: false, error: "Activity not found" },
+        { status: 404 }
+      );
+    }
+
+    const response = NextResponse.json(
+      { success: true, message: "Activity deleted successfully" },
+      { status: 200 }
+    );
+    return setCORSHeaders(response);
+  } catch (error) {
+    console.error("Error deleting activity:", error);
+    return handleError(error);
+  }
+}
+
+export async function OPTIONS() {
+  const response = new NextResponse(null, { status: 204 });
+  return setCORSHeaders(response);
+}

+ 3 - 3
src/app/api/prediction/route.js

@@ -31,9 +31,9 @@ export async function GET(request) {
         case "totalGoals":
           matchStage[key] = parseInt(value);
           break;
-        case "isCorrect":
-          matchStage[key] = value.toLowerCase() === "true";
-          break;
+        // case "isCorrect":
+        //   matchStage[key] = value.toLowerCase() === "true";
+        //   break;
         case "user":
           try {
             matchStage[key] = new mongoose.Types.ObjectId(value);

+ 1 - 1
src/app/api/updateForMatch/route.js

@@ -63,7 +63,7 @@ export async function POST(request) {
       }
 
       prediction.pointsEarned = pointsEarned;
-      prediction.isCorrect = pointsEarned > 0;
+      // prediction.isCorrect = pointsEarned > 0;
 
       await prediction.save();
 

+ 20 - 0
src/app/models/Activity.js

@@ -0,0 +1,20 @@
+import mongoose from "mongoose";
+
+const ActivitySchema = new mongoose.Schema(
+  {
+    title: {
+      type: String,
+    },
+    content: {
+      type: String,
+    },
+    isActive: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  { timestamps: true }
+);
+
+export default mongoose.models.Activity ||
+  mongoose.model("Activity", ActivitySchema);

+ 0 - 1
src/app/models/Prediction.js

@@ -37,7 +37,6 @@ const PredictionSchema = new mongoose.Schema(
     },
 
     pointsEarned: { type: Number, default: null }, // 用户预测获得的积分
-    isCorrect: { type: Boolean, default: null },
   },
   { timestamps: true }
 );

+ 7 - 14
src/app/page.js

@@ -3,8 +3,8 @@ import { useState, useEffect } from "react";
 import MatchDays from "./ui/MatchDays";
 import MatchPrediction from "./ui/MatchPrediction";
 import Image from "next/image";
-import { User } from "lucide-react";
 import { useRouter } from "next/navigation";
+import ActivityModal from "./ui/components/ActivityModal";
 
 export default function Home() {
   const [selectedDayMatches, setSelectedDayMatches] = useState([]);
@@ -17,30 +17,24 @@ export default function Home() {
   }, []);
 
   const handlePersonalCenterClick = () => {
-    // 实现个人中心点击逻辑
     console.log("Personal center clicked");
     router.push("/personal-center");
   };
 
   return (
     <div className="bg-blue-600 text-white min-h-screen">
-      {/* Floating Header */}
       <header className="h-16 fixed top-0 left-0 right-0 bg-[#ea1c24] shadow-md z-10 transition-all duration-300">
         <div className="max-w-md mx-auto p-4">
           <div className="flex justify-between items-center">
-            {/* Logo */}
             <div className="flex items-center">
               <Image
-                src="/images/header_logo.png" // 请替换为实际的 logo 路径
+                src="/images/header_logo.png"
                 alt="Logo"
-                width={75}
-                height={75}
-                // className="mr-2"
+                width={100}
+                height={100}
               />
-              {/* <span className="text-lg font-bold">智博体育</span> */}
             </div>
 
-            {/* User Info or Login Button */}
             {currentUser ? (
               <div
                 className="flex items-center cursor-pointer"
@@ -55,9 +49,7 @@ export default function Home() {
                     className="rounded-full mr-2"
                   />
                 ) : (
-                  // <div className="w-7 h-7 bg-gray-300 rounded-full flex items-center justify-center mr-2">
                   <div className="w-7 h-7  mr-2">
-                    {/* <User className="w-4 h-4 text-blue-600" /> */}
                     <Image
                       src="/images/cluo.webp"
                       alt="User Avatar"
@@ -83,14 +75,15 @@ export default function Home() {
         </div>
       </header>
 
-      {/* Main Content */}
-      <main className="max-w-md mx-auto  pt-16">
+      <main className="max-w-md mx-auto pt-16">
         <MatchDays onSelectMatch={setSelectedDayMatches} />
         <MatchPrediction
           selectedDayMatches={selectedDayMatches}
           currentUser={currentUser}
         />
       </main>
+
+      <ActivityModal />
     </div>
   );
 }

+ 91 - 14
src/app/personal-center/page.jsx

@@ -3,14 +3,20 @@
 import React, { useState, useEffect, useCallback } from "react";
 import { useRouter } from "next/navigation";
 import {
-  User,
   ChevronLeft,
   MessageSquare,
   ArrowUpCircle,
   ArrowDownCircle,
+  Gift,
+  Users,
+  Video,
+  DollarSign,
+  Zap,
 } from "lucide-react";
+
 import Image from "next/image";
 import InfiniteScroll from "react-infinite-scroll-component";
+import Link from "next/link";
 
 const PAGE_SIZE = 10;
 
@@ -131,6 +137,11 @@ const PersonalCenter = () => {
     }
   };
 
+  const handleExchangePoints = () => {
+    console.log("兑换积分");
+    // router.push('/exchange-points');
+  };
+
   if (isLoading) {
     return (
       <div className="bg-blue-600 text-white min-h-screen p-6">Loading...</div>
@@ -156,9 +167,8 @@ const PersonalCenter = () => {
         <h1 className="text-2xl font-bold ml-4">个人中心</h1>
       </div>
 
-      <div className="bg-white text-black rounded-lg p-4 mb-6">
+      {/* <div className="bg-white text-black rounded-lg p-4 mb-6">
         <div className="flex items-center">
-          {/* <User className="w-12 h-12 text-blue-600 mr-4" /> */}
           <Image
             src="/images/cluo.webp"
             alt="User Avatar"
@@ -171,6 +181,83 @@ const PersonalCenter = () => {
             <p className="text-gray-600">积分: {user.points}</p>
           </div>
         </div>
+      </div> */}
+
+      <div className="bg-white text-black rounded-lg p-4 mb-6">
+        <div className="flex items-center justify-between">
+          <div className="flex items-center">
+            <Image
+              src="/images/cluo.webp"
+              alt="User Avatar"
+              width={50}
+              height={50}
+              className="rounded-full mr-2"
+            />
+            <div>
+              <h2 className="text-xl font-bold">{user.username}</h2>
+              <p className="text-gray-600">积分: {user.points}</p>
+            </div>
+          </div>
+          <button
+            className="bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-2 rounded transition duration-300"
+            onClick={() => handleExchangePoints()}
+          >
+            兑换积分
+          </button>
+        </div>
+      </div>
+
+      {/* <div className="bg-white text-black rounded-lg p-4 mb-6">
+        <h3 className="text-lg font-bold mb-2">最新活动</h3>
+        <p className="mb-2">🔉最新福利活动</p>
+        <p className="mb-2">🔜加入千人福利群</p>
+        <div className="flex items-center">
+          <MessageSquare className="w-5 h-5 mr-2" />
+          <p>在线客服</p>
+        </div>
+      </div> */}
+
+      <div className="bg-white text-black rounded-lg p-4 mb-6 shadow-md">
+        <h3 className="text-xl font-bold mb-2 text-blue-600">最新活动</h3>
+        <Link
+          href="/betting"
+          className="flex items-center hover:bg-gray-100 p-2 rounded transition duration-300"
+        >
+          {/* <DollarSign className="w-5 h-5 mr-1 text-red-500" /> */}
+          <Zap className="w-5 h-5 mr-1 text-[#FFD700]" />
+          <p className="text-lg">1919智博投注</p>
+        </Link>
+        <Link
+          href="/activity"
+          className="flex items-center hover:bg-gray-100 p-2 rounded transition duration-300"
+        >
+          <Gift className="w-5 h-5 mr-1 text-red-500" />
+          <p className="text-lg">最新福利活动</p>
+        </Link>
+
+        <Link
+          href="/live-stream"
+          className="flex items-center hover:bg-gray-100 p-2 rounded transition duration-300"
+        >
+          <Video className="w-5 h-5 mr-1 text-green-500" />
+          <p className="text-lg">高清直播美女解说</p>
+        </Link>
+
+        <Link
+          href="/join-group"
+          className="flex items-center hover:bg-gray-100 p-2 rounded transition duration-300"
+        >
+          <Users className="w-5 h-5 mr-1 text-purple-500" />
+          <p className="text-lg">加入千人福利群</p>
+        </Link>
+
+        <Link
+          href="/customer-service"
+          className="flex items-center hover:bg-gray-100 p-2 rounded transition duration-300"
+        >
+          <MessageSquare className="w-5 h-5 mr-1 text-blue-500" />
+          <p className="text-lg">在线客服</p>
+        </Link>
       </div>
 
       <div className="bg-white text-black rounded-lg p-4 mb-6">
@@ -188,7 +275,7 @@ const PersonalCenter = () => {
           >
             {predictions.map((prediction) => (
               <div
-                key={prediction.id}
+                key={prediction._id}
                 className="mb-4 border-b border-gray-200 pb-4 hover:bg-gray-50 transition duration-150 ease-in-out"
               >
                 <p className="font-bold text-lg text-blue-700 mb-2">
@@ -351,16 +438,6 @@ const PersonalCenter = () => {
           </InfiniteScroll>
         </div>
       </div>
-
-      <div className="bg-white text-black rounded-lg p-4">
-        <h3 className="text-lg font-bold mb-2">最新活动</h3>
-        <p className="mb-2">🔉最新福利活动</p>
-        <p className="mb-2">🔜加入千人福利群</p>
-        <div className="flex items-center">
-          <MessageSquare className="w-5 h-5 mr-2" />
-          <p>在线客服</p>
-        </div>
-      </div>
     </div>
   );
 };

+ 70 - 0
src/app/ui/components/ActivityModal.jsx

@@ -0,0 +1,70 @@
+import React, { useState, useEffect } from "react";
+import { X } from "lucide-react";
+
+const ActivityModal = () => {
+  const [isOpen, setIsOpen] = useState(false);
+  const [activityContent, setActivityContent] = useState(null);
+  const [isLoading, setIsLoading] = useState(true);
+
+  useEffect(() => {
+    const fetchActivityContent = async () => {
+      try {
+        setIsLoading(true);
+        const response = await fetch("/api/activity", {
+          headers: {
+            "x-from-frontend": "true",
+          },
+        });
+        if (!response.ok) {
+          throw new Error("Failed to fetch activity content");
+        }
+        const data = await response.json();
+        console.log("data", data);
+
+        if (data.success && data.data) {
+          setActivityContent(data.data[0]);
+          setIsOpen(true);
+        } else {
+          // 处理没有活动数据的情况
+          console.log(data.message || "No active activity");
+          setActivityContent(null);
+          // 可以选择不打开模态框,或者显示一个提示信息
+          // setIsOpen(false);
+        }
+      } catch (error) {
+        console.error("Error fetching activity content:", error);
+        setActivityContent(null);
+      } finally {
+        setIsLoading(false);
+      }
+    };
+
+    fetchActivityContent();
+  }, []);
+
+  if (!isOpen || !activityContent || isLoading) return null;
+
+  return (
+    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
+      <div className="bg-white rounded-lg p-6 max-w-sm w-full mx-4">
+        <div className="flex justify-between items-center mb-4">
+          <h2 className="text-xl font-bold text-blue-600">
+            {activityContent.title}
+          </h2>
+          <button
+            onClick={() => setIsOpen(false)}
+            className="text-gray-500 hover:text-gray-700"
+          >
+            <X size={24} />
+          </button>
+        </div>
+        <div
+          className="text-gray-700"
+          dangerouslySetInnerHTML={{ __html: activityContent.content }}
+        />
+      </div>
+    </div>
+  );
+};
+
+export default ActivityModal;