diff --git a/next.config.ts b/next.config.ts index 24f61e8..c20e489 100644 --- a/next.config.ts +++ b/next.config.ts @@ -4,6 +4,7 @@ import createNextIntlPlugin from 'next-intl/plugin'; const withNextIntl = createNextIntlPlugin('./src/lib/i18n.ts'); const nextConfig: NextConfig = { + reactStrictMode: false, /* config options here */ images: { remotePatterns: [ diff --git a/src/app/(main)/crushcoin/components/CheckInCard.tsx b/src/app/(main)/crushcoin/components/CheckInCard.tsx index 4ce34b1..c1a7700 100644 --- a/src/app/(main)/crushcoin/components/CheckInCard.tsx +++ b/src/app/(main)/crushcoin/components/CheckInCard.tsx @@ -1,15 +1,16 @@ -'use client' -import { Checkbox } from '@/components/ui/checkbox' -import { cn } from '@/lib/utils' -import Image from 'next/image' +'use client'; +import { Checkbox } from '@/components/ui/checkbox'; +import { cn } from '@/lib/utils'; +import Image from 'next/image'; +import { useTranslations } from 'next-intl'; interface CheckInCardProps { - day: number - coinNum: number - signIn: boolean - isToday: boolean - loading?: boolean - className?: string + day: number; + coinNum: number; + signIn: boolean; + isToday: boolean; + loading?: boolean; + className?: string; } export function CheckInCard({ @@ -20,27 +21,28 @@ export function CheckInCard({ loading, className, }: CheckInCardProps) { - const isChecked = signIn - const canCheckIn = isToday && !signIn + const t = useTranslations('crushcoin'); + const isChecked = signIn; + const isSignInLoading = isToday && loading; // 根据状态决定样式 const getCardStyles = () => { if (isChecked) { // 已签到 - 灰色背景 - return 'bg-[#282233] border-outline-normal' + return 'bg-[#282233] border-outline-normal'; } else { // 未来日期 - 渐变背景 - return 'bg-gradient-to-bl from-[#f157ff] to-[#3337ff] border-[#ffd7b8]' + return 'bg-gradient-to-bl from-[#f157ff] to-[#3337ff] border-[#ffd7b8]'; } - } + }; const getDayLabelStyles = () => { if (isChecked) { - return 'bg-surface-nest-disabled' + return 'bg-surface-nest-disabled'; } else { - return 'bg-gradient-to-b from-[#ff9156] to-[#bf00ff]' + return 'bg-gradient-to-b from-[#ff9156] to-[#bf00ff]'; } - } + }; return (
-
{`Day ${day}`}
+
{t('day', { day })}
{/* 内容区域 */} @@ -72,9 +74,13 @@ export function CheckInCard({ className="h-full w-full" /> -
{coinNum / 100}
+
+ {coinNum / 100} +
{/* 装饰星星 */} -
+
-
Checked
+
{t('checked')}
+
+ ) : isSignInLoading ? ( +
+ {t('loading')}
) : (
- Not Started + {t('notStarted')}
)}
- ) + ); } diff --git a/src/app/(main)/crushcoin/components/CheckInGrid.tsx b/src/app/(main)/crushcoin/components/CheckInGrid.tsx index 8e14b92..4d7667a 100644 --- a/src/app/(main)/crushcoin/components/CheckInGrid.tsx +++ b/src/app/(main)/crushcoin/components/CheckInGrid.tsx @@ -1,54 +1,20 @@ 'use client'; -import { useGetSevenDaysSignList, useSignIn } from '@/hooks/useHome'; import { SignInListOutput } from '@/services/home/types'; -import { useQueryClient } from '@tanstack/react-query'; -import { homeKeys } from '@/lib/query-keys'; import { CheckInCard } from './CheckInCard'; -import { useEffect, useRef } from 'react'; -import { toast } from 'sonner'; +import { useEffect } from 'react'; +import { useSignIn } from '@/hooks/services/signin'; export function CheckInGrid() { - const queryClient = useQueryClient(); - const { data: signListData, isLoading } = useGetSevenDaysSignList(); - const signInMutation = useSignIn(); - const hasSignRef = useRef(false); + const { signInListData, fetchSignInListLoading, handleSignIn, signInLoading } = useSignIn(); useEffect(() => { - const initializeCheckIn = async () => { - if (hasSignRef.current) return; - hasSignRef.current = true; - try { - // 先进行签到 - const resp = await signInMutation.mutateAsync(); - if (resp) { - toast.success('Check-in Successful!'); - } - // 签到成功后再获取列表数据 - await queryClient.invalidateQueries({ - queryKey: homeKeys.getSevenDaysSignList(), - }); - await queryClient.invalidateQueries({ - queryKey: ['wallet'], - }); - } catch (error) { - console.error('初始化签到失败:', error); - // 即使签到失败,也要获取列表数据显示界面 - queryClient.invalidateQueries({ - queryKey: homeKeys.getSevenDaysSignList(), - }); - queryClient.invalidateQueries({ - queryKey: ['wallet'], - }); - } - }; - - if (signListData) { - initializeCheckIn(); + if (signInListData?.list?.length) { + handleSignIn(); } - }, [signListData]); + }, [signInListData?.list?.length]); - if (isLoading) { + if (fetchSignInListLoading) { return (
{Array.from({ length: 6 }).map((_, index) => ( @@ -61,7 +27,7 @@ export function CheckInGrid() { ); } - const signList = signListData?.list || []; + const signList = signInListData?.list || []; const today = new Date(); const todayStr = today.toISOString().split('T')[0]; // yyyy-MM-dd 格式 @@ -91,23 +57,12 @@ export function CheckInGrid() { const currentDayIndex = todayIndex >= 0 ? todayIndex : fullSignList.findIndex((item) => !item.signIn); + const day7 = fullSignList[6]; + return ( -
- {fullSignList.map((item, index) => { - if (index === 3) { - return ( - - ); - } - if (index < 3) { +
+
+ {fullSignList.slice(0, 6).map((item, index) => { return ( ); - } else { - return ( - - ); - } - })} + })} +
+
); } diff --git a/src/app/(main)/crushcoin/components/CrushcoinBackground.tsx b/src/app/(main)/crushcoin/components/CrushcoinBackground.tsx index d00ef23..b0f6631 100644 --- a/src/app/(main)/crushcoin/components/CrushcoinBackground.tsx +++ b/src/app/(main)/crushcoin/components/CrushcoinBackground.tsx @@ -1,9 +1,9 @@ -'use client' +'use client'; const CrushcoinBackground = () => { return (
-
+
{ />
- ) -} + ); +}; -export default CrushcoinBackground +export default CrushcoinBackground; diff --git a/src/app/(main)/crushcoin/crushcoin-page.tsx b/src/app/(main)/crushcoin/crushcoin-page.tsx index a18c10b..de83e8f 100644 --- a/src/app/(main)/crushcoin/crushcoin-page.tsx +++ b/src/app/(main)/crushcoin/crushcoin-page.tsx @@ -1,42 +1,49 @@ -'use client' +'use client'; -import CrushcoinBackground from './components/CrushcoinBackground' -import { GradientDivider } from '@/components/ui/gradient-divider' -import { useGetSevenDaysSignList } from '@/hooks/useHome' -import CheckInGrid from './components/CheckInGrid' +import CrushcoinBackground from './components/CrushcoinBackground'; +import { GradientDivider } from '@/components/ui/gradient-divider'; +import CheckInGrid from './components/CheckInGrid'; +import { IconButton } from '@/components/ui/button'; +import { useRouter } from 'next/navigation'; +import { useSignIn } from '@/hooks/services/signin'; +import { useTranslations } from 'next-intl'; const CrushCoinPage = () => { - const { data: signListData } = useGetSevenDaysSignList() - - // 计算连续签到天数 - const consecutiveDays = signListData?.continuousDays || 0 + const { signInListData } = useSignIn(); + const router = useRouter(); + const t = useTranslations('crushcoin'); return ( -
+
-
-

Daily Free CrushCoins

-
- {/* You have checked in for {consecutiveDays} Consecutive days Consecutive days */} - You’ve checked in for {consecutiveDays} consecutive days. +
+ router.back()} + className="absolute top-1 left-0 z-10" + > + + +

{t('title')}

+
+ {t('consecutiveDays', { days: signInListData?.continuousDays || '-' })}
{/* 渐变分割线 */} {/* 签到网格 */} -
+
-

Diamonds can be used to pay for chat services and unlock other items.

-

- If you miss a check-in, the check-in count will reset and start again from day one. -

+

{t('description1')}

+

{t('description2')}

- ) -} + ); +}; -export default CrushCoinPage +export default CrushCoinPage; diff --git a/src/app/(main)/home/components/Header.tsx b/src/app/(main)/home/components/Header.tsx index d772dee..ee16daa 100644 --- a/src/app/(main)/home/components/Header.tsx +++ b/src/app/(main)/home/components/Header.tsx @@ -12,53 +12,58 @@ const Header = React.memo(() => { const t = useTranslations('home'); return ( - // -
-
- header-bg -
-
- {t('check_in')}{' '} - header-bg -
-
- - {t('check_in_desc')} - - + +
+
+ header-bg +
+
+ {t('check_in')}{' '} + header-bg +
+
+ + {t('check_in_desc')} + + +
+ {response?.lg && ( + banner-header + )}
- {response?.lg && ( - banner-header - )} -
- // + ); }); diff --git a/src/app/(main)/home/service.ts b/src/app/(main)/home/service.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/(main)/home/type.d.ts b/src/app/(main)/home/type.d.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/ui/gradient-divider.tsx b/src/components/ui/gradient-divider.tsx index a0c159d..cf9144c 100644 --- a/src/components/ui/gradient-divider.tsx +++ b/src/components/ui/gradient-divider.tsx @@ -1,36 +1,11 @@ -'use client' +'use client'; -import React from 'react' -import { cn } from '@/lib/utils' +import { cn } from '@/lib/utils'; -interface GradientDividerProps { - className?: string -} - -export function GradientDivider({ className }: GradientDividerProps) { +export function GradientDivider() { return ( -
+
- ) - return ( -
-
- -
-
-
-
- ) + ); } diff --git a/src/css/tailwindcss.css b/src/css/tailwindcss.css index c4200b2..c1de7d5 100644 --- a/src/css/tailwindcss.css +++ b/src/css/tailwindcss.css @@ -651,6 +651,13 @@ line-height: var(--glo-font-lineheight-size16); } +@utility txt-title-xs { + font-family: var(--font-poppins); + font-size: var(--glo-font-size-14); + font-weight: var(--glo-font-weight-semibold); + line-height: var(--glo-font-lineheight-size14); +} + @utility txt-bodySemibold-l { font-family: var(--font-poppins); font-size: var(--glo-font-size-16); diff --git a/src/hooks/services/signin.ts b/src/hooks/services/signin.ts new file mode 100644 index 0000000..dcd92f0 --- /dev/null +++ b/src/hooks/services/signin.ts @@ -0,0 +1,33 @@ +import { getSevenDaysSignList, signIn } from '@/services/editor'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useCurrentUser } from '../auth'; +import { toast } from 'sonner'; +import { useTranslations } from 'next-intl'; + +export function useSignIn() { + const { data: user } = useCurrentUser(); + const t = useTranslations('crushcoin'); + const queryClient = useQueryClient(); + const { data: signInListData, isLoading: fetchSignInListLoading } = useQuery({ + queryKey: ['signInList'], + enabled: !!user?.userId, + queryFn: () => getSevenDaysSignList({ userId: user?.userId }), + }); + + const { mutate: handleSignIn, isPending: signInLoading } = useMutation({ + mutationFn: () => signIn({ userId: user?.userId }), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ['signInList'], + }); + toast.success(t('checked_success')); + }, + }); + + return { + signInListData, + fetchSignInListLoading, + signInLoading, + handleSignIn, + }; +} diff --git a/src/i18n/en-US.ts b/src/i18n/en-US.ts index b61aca1..866d006 100644 --- a/src/i18n/en-US.ts +++ b/src/i18n/en-US.ts @@ -152,6 +152,18 @@ export default { female: 'Female', }, }, + crushcoin: { + title: 'Daily Free CrushCoins', + consecutiveDays: "You've checked in for {days} consecutive days.", + description1: 'Diamonds can be used to pay for chat services and unlock other items.', + description2: + 'If you miss a check-in, the check-in count will reset and start again from day one.', + day: 'Day {day}', + checked: 'Checked', + notStarted: 'Not Started', + loading: 'Signing in...', + checked_success: 'Check-in successful', + }, footer: { slogan: "Grow your love story with Spicyxx.AI AI—From 'Hi' to 'I Do', sparked by every chat", features: 'Features', diff --git a/src/i18n/zh-CN.ts b/src/i18n/zh-CN.ts index ee0f19a..8b4426a 100644 --- a/src/i18n/zh-CN.ts +++ b/src/i18n/zh-CN.ts @@ -150,6 +150,17 @@ export default { female: '女性', }, }, + crushcoin: { + title: '每日免费金币', + consecutiveDays: '你已经连续签到 {days} 天', + description1: '金币可用于支付聊天服务和解锁其他物品。', + description2: '如果错过签到,签到计数将重置并从第一天重新开始。', + day: '第 {day} 天', + checked: '已签到', + notStarted: '未开始', + loading: '签到中ing', + checked_success: '签到成功', + }, footer: { slogan: '用 Spicyxx.AI 成长你的爱情故事——从"你好"到"我愿意",每一次对话都点燃火花', features: '功能', diff --git a/src/layout/BasicLayout/Topbar.tsx b/src/layout/BasicLayout/Topbar.tsx index 32fe44a..e6116e8 100644 --- a/src/layout/BasicLayout/Topbar.tsx +++ b/src/layout/BasicLayout/Topbar.tsx @@ -114,9 +114,12 @@ function Topbar() { return (
{shouldBlur &&
}
diff --git a/src/layout/BasicLayout/config.ts b/src/layout/BasicLayout/config.ts index 2aacadd..505e2f1 100644 --- a/src/layout/BasicLayout/config.ts +++ b/src/layout/BasicLayout/config.ts @@ -22,6 +22,7 @@ export const topbarRouteConfigs: Record = { '/profile/account': { hideOnMobile: true }, '/character/:id': { hideOnMobile: true }, '/chat/:id': { enableBlur: true }, + '/crushcoin': { hideOnMobile: true }, }; /** diff --git a/src/services/editor/index.ts b/src/services/editor/index.ts index b48f44a..94f85a1 100644 --- a/src/services/editor/index.ts +++ b/src/services/editor/index.ts @@ -1,5 +1,5 @@ import { editorRequest } from '@/lib/client'; -import { LikeObjectParamsType } from './type'; +import { LikeObjectParamsType, SignInListType } from './type'; export async function fetchCharacters({ index, limit, query }: any) { const { data } = await editorRequest('/api/character/list', { @@ -25,3 +25,12 @@ export async function thmubObject(params: LikeObjectParamsType) { export async function getLikeStatus(params: Pick) { return editorRequest('/api/like/getLikeStatus', { method: 'POST', data: params }); } + +export async function getSevenDaysSignList(params: any = {}): Promise { + const { data } = await editorRequest('/api/sign/list', { method: 'POST', data: params }); + return data; +} + +export async function signIn(params: any = {}) { + return editorRequest('/api/sign/asi', { method: 'POST', data: params }); +} diff --git a/src/services/editor/type.ts b/src/services/editor/type.ts index 87e8545..d39c0bc 100644 --- a/src/services/editor/type.ts +++ b/src/services/editor/type.ts @@ -22,6 +22,7 @@ export type CharacterType = { [x: string]: any; }; +// 点赞点踩 export enum LikeTargetType { Character = 0, Story = 1, @@ -36,3 +37,13 @@ export type LikeObjectParamsType = { userId?: number; likeType?: LikeType; }; + +// 签到 +export type SignInListType = { + continuousDays: number; + list: { + coinNum: number; + dayStr: string; + signIn: boolean; + }[]; +};