From d826b3c57eb824f1c89cbfa03d071507dfa04a00 Mon Sep 17 00:00:00 2001 From: moux1024 <403053463@qq.com> Date: Wed, 17 Sep 2025 14:11:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E7=AD=BE=E5=88=B0?= =?UTF-8?q?=E6=A8=A1=E5=9D=97;=E8=87=AA=E5=AE=9A=E4=B9=89=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E8=89=B2custom-blue=E3=80=81custom-purple;=E5=A4=84?= =?UTF-8?q?=E7=90=86dialog=E7=9A=84=E5=B1=82=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursorrules | 8 ++ api/checkin.ts | 39 +++++++ app/globals.css | 6 + components.json | 2 +- components/layout/checkin-box.tsx | 155 +++++++++++++++++++++++++ components/layout/dashboard-layout.tsx | 2 +- components/layout/top-bar.tsx | 43 ++++++- components/ui/dialog.tsx | 6 +- tailwind.config.js | 2 + tailwind.config.ts | 90 -------------- 10 files changed, 255 insertions(+), 98 deletions(-) create mode 100644 api/checkin.ts create mode 100644 components/layout/checkin-box.tsx delete mode 100644 tailwind.config.ts diff --git a/.cursorrules b/.cursorrules index f80adf8..1feaef5 100644 --- a/.cursorrules +++ b/.cursorrules @@ -64,3 +64,11 @@ You are the joint apprentice of Evan You and Kent C. Dodds. Channel Evan You's e - Omit console.log or debug statements unless requested. - Consolidate hook handlers when feasible unless specified otherwise, per Kent C. Dodds' readability practices. - Prefer async/await over .then for async operations to enhance clarity. + +# Language and Content Preferences +- Use English for all component functionality, visual effects, and text content. +- Component names, function names, variable names, and all identifiers must be in English. +- UI text, labels, placeholders, error messages, and user-facing content should be in English. +- Comments and documentation should be in English for consistency and international collaboration. +- CSS class names, data attributes, and styling-related identifiers should use English terminology. +- Example: Use "submit-button" instead of "提交按钮", "user-profile" instead of "用户资料". diff --git a/api/checkin.ts b/api/checkin.ts new file mode 100644 index 0000000..906ba9e --- /dev/null +++ b/api/checkin.ts @@ -0,0 +1,39 @@ +import { get, post } from './request' +import { ApiResponse } from './common' + +/** + * 签到数据接口 + */ +export interface CheckinData { + hasCheckedInToday: boolean + points: number + lastCheckinDate: string | null + pointsHistory: Array<{ date: string; points: number; expiryDate: string }> +} + +/** + * 签到响应接口 + */ +export interface CheckinResponse { + success: boolean + points: number + message: string +} + +/** + * 获取用户签到状态和积分信息 + * @returns Promise + */ +export const getCheckinStatus = async (): Promise => { + const response = await get>('/api/user/checkin/status') + return response.data +} + +/** + * 执行签到操作 + * @returns Promise + */ +export const performCheckin = async (): Promise => { + const response = await post>('/api/user/checkin', {}) + return response.data +} diff --git a/app/globals.css b/app/globals.css index 08edb01..f8f96bd 100644 --- a/app/globals.css +++ b/app/globals.css @@ -93,6 +93,12 @@ --muted-foreground: 0 0% 63.9%; --accent: 0 0% 14.9%; --accent-foreground: 0 0% 98%; + + /* 自定义渐变色变量 */ + --custom-blue: 186 100% 70%; /* rgb(106, 244, 249) */ + --custom-purple: 280 100% 62%; /* rgb(199, 59, 255) */ + --custom-blue-rgb: 106, 244, 249; + --custom-purple-rgb: 199, 59, 255; --destructive: 0 62.8% 30.6%; --destructive-foreground: 0 0% 98%; --border: 0 0% 14.9%; diff --git a/components.json b/components.json index c597462..8f242a4 100644 --- a/components.json +++ b/components.json @@ -4,7 +4,7 @@ "rsc": true, "tsx": true, "tailwind": { - "config": "tailwind.config.ts", + "config": "tailwind.config.js", "css": "app/globals.css", "baseColor": "neutral", "cssVariables": true, diff --git a/components/layout/checkin-box.tsx b/components/layout/checkin-box.tsx new file mode 100644 index 0000000..52f221b --- /dev/null +++ b/components/layout/checkin-box.tsx @@ -0,0 +1,155 @@ +"use client" + +import { useState, useEffect } from "react" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Coins, Trophy, HelpCircle } from "lucide-react" +import { getCheckinStatus, performCheckin, CheckinData } from "@/api/checkin" + + +export default function CheckinPage() { + const [checkinData, setCheckinData] = useState({ + hasCheckedInToday: false, + points: 0, + lastCheckinDate: null, + pointsHistory: [], + }) + const [isLoading, setIsLoading] = useState(false) + const [showTip, setShowTip] = useState(false) + const [isInitialLoading, setIsInitialLoading] = useState(true) + + + /** + * Fetch checkin status + */ + const fetchCheckinStatus = async () => { + try { + setIsInitialLoading(true) + const data = await getCheckinStatus() + setCheckinData(data) + } catch (error) { + console.error('Failed to fetch checkin status:', error) + // Keep default state + } finally { + setIsInitialLoading(false) + } + } + + useEffect(() => { + fetchCheckinStatus() + }, []) + + /** + * Perform checkin operation + */ + const handleCheckin = async () => { + if (checkinData.hasCheckedInToday) return + + try { + setIsLoading(true) + const response = await performCheckin() + + if (response.success) { + // Refresh status after successful checkin + await fetchCheckinStatus() + } + } catch (error) { + console.error('Checkin failed:', error) + } finally { + setIsLoading(false) + } + } + + + if (isInitialLoading) { + return ( +
+ + +
+
+ Loading... +
+ + +
+ ) + } + + return ( +
+ + {/* Checkin status card */} + + +

+ Daily Check-in +

+
+

Check in to earn credits, credits valid for 7 days

+
+ + {showTip && ( +
+
+

Check-in Rules

+

• Daily check-in earns 100 credits

+

• Credits are valid for 7 days

+

• Expired credits will be automatically cleared

+
+
+
+ )} +
+
+
+ + +
+
+
+ + Current Credits +
+
+ {checkinData.points} +
+
+ +
+ + {/* Check-in button */} + +
+
+
+ ) +} diff --git a/components/layout/dashboard-layout.tsx b/components/layout/dashboard-layout.tsx index fddb9dc..6008f28 100644 --- a/components/layout/dashboard-layout.tsx +++ b/components/layout/dashboard-layout.tsx @@ -34,7 +34,7 @@ export function DashboardLayout({ children }: DashboardLayoutProps) { {isDesktop && }
{children}
diff --git a/components/layout/top-bar.tsx b/components/layout/top-bar.tsx index 1f77356..be47907 100644 --- a/components/layout/top-bar.tsx +++ b/components/layout/top-bar.tsx @@ -2,7 +2,11 @@ import "../pages/style/top-bar.css"; import { Button } from "@/components/ui/button"; - +import { + Dialog, + DialogContent, + DialogTitle, +} from "@/components/ui/dialog"; import { GradientText } from "@/components/ui/gradient-text"; import { useTheme } from "next-themes"; import { @@ -14,6 +18,7 @@ import { PanelsLeftBottom, Bell, Info, + CalendarDays, } from "lucide-react"; import { motion } from "framer-motion"; import { createPortal } from "react-dom"; @@ -28,6 +33,7 @@ import { } from "@/lib/stripe"; import UserCard from "@/components/common/userCard"; import { showInsufficientPointsNotification } from "@/utils/notifications"; +import CheckinBox from "./checkin-box"; interface User { id: string; @@ -55,6 +61,7 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe const [isLoadingSubscription, setIsLoadingSubscription] = useState(false); const [isBuyingTokens, setIsBuyingTokens] = useState(false); const [customAmount, setCustomAmount] = useState(""); + const [isCheckinModalOpen, setIsCheckinModalOpen] = useState(false); // 获取用户订阅信息 const fetchSubscriptionInfo = async () => { @@ -239,8 +246,15 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe element.classList.add("on"); }; + /** + * 处理签到功能,打开签到modal + */ + const handleCheckin = () => { + setIsCheckinModalOpen(true); + }; + return ( -
+ {/* Check-in entry */} +
+ +
{/* AI 积分 */} @@ -527,6 +551,19 @@ export function TopBar({ collapsed, isDesktop=true }: { collapsed: boolean, isDe )} + + {/* Check-in Modal */} + + + +
+ +
+
+
); } diff --git a/components/ui/dialog.tsx b/components/ui/dialog.tsx index a207dd1..597a15d 100644 --- a/components/ui/dialog.tsx +++ b/components/ui/dialog.tsx @@ -21,7 +21,7 @@ const DialogOverlay = React.forwardRef< {children} - + Close diff --git a/tailwind.config.js b/tailwind.config.js index 791fe77..c408d20 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -43,6 +43,8 @@ module.exports = { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, + 'custom-blue': '#6AF4F9', + 'custom-purple': '#C73BFF', }, borderRadius: { lg: "var(--radius)", diff --git a/tailwind.config.ts b/tailwind.config.ts deleted file mode 100644 index 53b032c..0000000 --- a/tailwind.config.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { Config } from 'tailwindcss'; - -const config: Config = { - darkMode: ['class'], - content: [ - './pages/**/*.{js,ts,jsx,tsx,mdx}', - './components/**/*.{js,ts,jsx,tsx,mdx}', - './app/**/*.{js,ts,jsx,tsx,mdx}', - ], - theme: { - extend: { - backgroundImage: { - 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', - 'gradient-conic': - 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)', - }, - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))', - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))', - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))', - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))', - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))', - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))', - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))', - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))', - }, - }, - keyframes: { - 'accordion-down': { - from: { - height: '0', - }, - to: { - height: 'var(--radix-accordion-content-height)', - }, - }, - 'accordion-up': { - from: { - height: 'var(--radix-accordion-content-height)', - }, - to: { - height: '0', - }, - }, - }, - animation: { - 'accordion-down': 'accordion-down 0.2s ease-out', - 'accordion-up': 'accordion-up 0.2s ease-out', - }, - }, - }, - plugins: [require('tailwindcss-animate')], -}; -export default config;