feat: 增加签到功能
This commit is contained in:
parent
e6b28751bd
commit
f477cc95f8
|
|
@ -4,6 +4,7 @@ import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
const withNextIntl = createNextIntlPlugin('./src/lib/i18n.ts');
|
const withNextIntl = createNextIntlPlugin('./src/lib/i18n.ts');
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
|
reactStrictMode: false,
|
||||||
/* config options here */
|
/* config options here */
|
||||||
images: {
|
images: {
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
'use client'
|
'use client';
|
||||||
import { Checkbox } from '@/components/ui/checkbox'
|
import { Checkbox } from '@/components/ui/checkbox';
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils';
|
||||||
import Image from 'next/image'
|
import Image from 'next/image';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
interface CheckInCardProps {
|
interface CheckInCardProps {
|
||||||
day: number
|
day: number;
|
||||||
coinNum: number
|
coinNum: number;
|
||||||
signIn: boolean
|
signIn: boolean;
|
||||||
isToday: boolean
|
isToday: boolean;
|
||||||
loading?: boolean
|
loading?: boolean;
|
||||||
className?: string
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CheckInCard({
|
export function CheckInCard({
|
||||||
|
|
@ -20,27 +21,28 @@ export function CheckInCard({
|
||||||
loading,
|
loading,
|
||||||
className,
|
className,
|
||||||
}: CheckInCardProps) {
|
}: CheckInCardProps) {
|
||||||
const isChecked = signIn
|
const t = useTranslations('crushcoin');
|
||||||
const canCheckIn = isToday && !signIn
|
const isChecked = signIn;
|
||||||
|
const isSignInLoading = isToday && loading;
|
||||||
|
|
||||||
// 根据状态决定样式
|
// 根据状态决定样式
|
||||||
const getCardStyles = () => {
|
const getCardStyles = () => {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
// 已签到 - 灰色背景
|
// 已签到 - 灰色背景
|
||||||
return 'bg-[#282233] border-outline-normal'
|
return 'bg-[#282233] border-outline-normal';
|
||||||
} else {
|
} 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 = () => {
|
const getDayLabelStyles = () => {
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
return 'bg-surface-nest-disabled'
|
return 'bg-surface-nest-disabled';
|
||||||
} else {
|
} else {
|
||||||
return 'bg-gradient-to-b from-[#ff9156] to-[#bf00ff]'
|
return 'bg-gradient-to-b from-[#ff9156] to-[#bf00ff]';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -56,7 +58,7 @@ export function CheckInCard({
|
||||||
|
|
||||||
{/* 天数标签 */}
|
{/* 天数标签 */}
|
||||||
<div className={`absolute top-0 left-0 rounded-br-lg px-2 py-1 ${getDayLabelStyles()}`}>
|
<div className={`absolute top-0 left-0 rounded-br-lg px-2 py-1 ${getDayLabelStyles()}`}>
|
||||||
<div className="txt-label-s text-center">{`Day ${day}`}</div>
|
<div className="txt-label-s text-center">{t('day', { day })}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 内容区域 */}
|
{/* 内容区域 */}
|
||||||
|
|
@ -72,9 +74,13 @@ export function CheckInCard({
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={cn('txt-numDisplay-m', isChecked && 'text-txt-primary-specialmap-disable')}>{coinNum / 100}</div>
|
<div
|
||||||
|
className={cn('txt-numDisplay-m', isChecked && 'text-txt-primary-specialmap-disable')}
|
||||||
|
>
|
||||||
|
{coinNum / 100}
|
||||||
|
</div>
|
||||||
{/* 装饰星星 */}
|
{/* 装饰星星 */}
|
||||||
<div className="absolute top-[-4px] right-[-28px] h-7 w-6">
|
<div className="absolute hidden sm:block top-[-4px] right-[-28px] h-7 w-6">
|
||||||
<Image
|
<Image
|
||||||
src="/images/crushcoin/icon-star.svg"
|
src="/images/crushcoin/icon-star.svg"
|
||||||
className="h-full w-full"
|
className="h-full w-full"
|
||||||
|
|
@ -88,14 +94,18 @@ export function CheckInCard({
|
||||||
{isChecked ? (
|
{isChecked ? (
|
||||||
<div className="mt-2 flex items-center gap-2">
|
<div className="mt-2 flex items-center gap-2">
|
||||||
<Checkbox checked disabled />
|
<Checkbox checked disabled />
|
||||||
<div className="txt-label-s text-txt-primary-specialmap-disable">Checked</div>
|
<div className="txt-label-s text-txt-primary-specialmap-disable">{t('checked')}</div>
|
||||||
|
</div>
|
||||||
|
) : isSignInLoading ? (
|
||||||
|
<div className="txt-label-s text-txt-primary-specialmap-disable mt-2 text-center">
|
||||||
|
{t('loading')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="txt-label-s text-txt-primary-specialmap-disable mt-2 text-center">
|
<div className="txt-label-s text-txt-primary-specialmap-disable mt-2 text-center">
|
||||||
Not Started
|
{t('notStarted')}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,54 +1,20 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useGetSevenDaysSignList, useSignIn } from '@/hooks/useHome';
|
|
||||||
import { SignInListOutput } from '@/services/home/types';
|
import { SignInListOutput } from '@/services/home/types';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
|
||||||
import { homeKeys } from '@/lib/query-keys';
|
|
||||||
import { CheckInCard } from './CheckInCard';
|
import { CheckInCard } from './CheckInCard';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { toast } from 'sonner';
|
import { useSignIn } from '@/hooks/services/signin';
|
||||||
|
|
||||||
export function CheckInGrid() {
|
export function CheckInGrid() {
|
||||||
const queryClient = useQueryClient();
|
const { signInListData, fetchSignInListLoading, handleSignIn, signInLoading } = useSignIn();
|
||||||
const { data: signListData, isLoading } = useGetSevenDaysSignList();
|
|
||||||
const signInMutation = useSignIn();
|
|
||||||
const hasSignRef = useRef(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initializeCheckIn = async () => {
|
if (signInListData?.list?.length) {
|
||||||
if (hasSignRef.current) return;
|
handleSignIn();
|
||||||
hasSignRef.current = true;
|
|
||||||
try {
|
|
||||||
// 先进行签到
|
|
||||||
const resp = await signInMutation.mutateAsync();
|
|
||||||
if (resp) {
|
|
||||||
toast.success('Check-in Successful!');
|
|
||||||
}
|
}
|
||||||
// 签到成功后再获取列表数据
|
}, [signInListData?.list?.length]);
|
||||||
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) {
|
if (fetchSignInListLoading) {
|
||||||
initializeCheckIn();
|
|
||||||
}
|
|
||||||
}, [signListData]);
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
return (
|
||||||
<div className="grid h-[328px] grid-cols-3 grid-rows-2 gap-4">
|
<div className="grid h-[328px] grid-cols-3 grid-rows-2 gap-4">
|
||||||
{Array.from({ length: 6 }).map((_, index) => (
|
{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 today = new Date();
|
||||||
const todayStr = today.toISOString().split('T')[0]; // yyyy-MM-dd 格式
|
const todayStr = today.toISOString().split('T')[0]; // yyyy-MM-dd 格式
|
||||||
|
|
||||||
|
|
@ -91,23 +57,12 @@ export function CheckInGrid() {
|
||||||
const currentDayIndex =
|
const currentDayIndex =
|
||||||
todayIndex >= 0 ? todayIndex : fullSignList.findIndex((item) => !item.signIn);
|
todayIndex >= 0 ? todayIndex : fullSignList.findIndex((item) => !item.signIn);
|
||||||
|
|
||||||
|
const day7 = fullSignList[6];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-4 grid-rows-2 gap-4">
|
<div className="flex flex-col gap-4 sm:flex-row">
|
||||||
{fullSignList.map((item, index) => {
|
<div className="grid flex-1 sm:flex-[3] grid-cols-3 gap-4">
|
||||||
if (index === 3) {
|
{fullSignList.slice(0, 6).map((item, index) => {
|
||||||
return (
|
|
||||||
<CheckInCard
|
|
||||||
key={fullSignList[6].day}
|
|
||||||
day={fullSignList[6].day}
|
|
||||||
coinNum={fullSignList[6].coinNum || 0}
|
|
||||||
signIn={fullSignList[6].signIn || false}
|
|
||||||
isToday={fullSignList[6].dayStr === todayStr}
|
|
||||||
loading={signInMutation.isPending}
|
|
||||||
className="col-span-1 row-span-2"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (index < 3) {
|
|
||||||
return (
|
return (
|
||||||
<CheckInCard
|
<CheckInCard
|
||||||
key={item.day}
|
key={item.day}
|
||||||
|
|
@ -115,25 +70,22 @@ export function CheckInGrid() {
|
||||||
coinNum={item.coinNum || 0}
|
coinNum={item.coinNum || 0}
|
||||||
signIn={item.signIn || false}
|
signIn={item.signIn || false}
|
||||||
isToday={index === currentDayIndex}
|
isToday={index === currentDayIndex}
|
||||||
loading={signInMutation.isPending}
|
loading={signInLoading}
|
||||||
className="col-span-1 row-span-1"
|
className="col-span-1 row-span-1"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<CheckInCard
|
|
||||||
key={fullSignList[index - 1].day}
|
|
||||||
day={fullSignList[index - 1].day}
|
|
||||||
coinNum={fullSignList[index - 1].coinNum || 0}
|
|
||||||
signIn={fullSignList[index - 1].signIn || false}
|
|
||||||
isToday={fullSignList[index - 1].dayStr === todayStr}
|
|
||||||
loading={signInMutation.isPending}
|
|
||||||
className="col-span-1 row-span-1"
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
<CheckInCard
|
||||||
|
key={day7.day}
|
||||||
|
day={day7.day}
|
||||||
|
coinNum={day7.coinNum || 0}
|
||||||
|
signIn={day7.signIn || false}
|
||||||
|
isToday={day7.dayStr === todayStr}
|
||||||
|
loading={signInLoading}
|
||||||
|
className="col-span-1 sm:flex-1 row-span-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
const CrushcoinBackground = () => {
|
const CrushcoinBackground = () => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-background-default absolute inset-0 overflow-hidden">
|
<div className="bg-background-default absolute inset-0 overflow-hidden">
|
||||||
<div className="absolute top-0 right-0 left-1/2 h-[240px] min-w-[1230px] -translate-x-1/2">
|
<div className="absolute top-0 right-0 left-1/2 h-30 sm:h-60 min-w-300 -translate-x-1/2">
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0"
|
className="absolute inset-0"
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -15,7 +15,7 @@ const CrushcoinBackground = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default CrushcoinBackground
|
export default CrushcoinBackground;
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,49 @@
|
||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import CrushcoinBackground from './components/CrushcoinBackground'
|
import CrushcoinBackground from './components/CrushcoinBackground';
|
||||||
import { GradientDivider } from '@/components/ui/gradient-divider'
|
import { GradientDivider } from '@/components/ui/gradient-divider';
|
||||||
import { useGetSevenDaysSignList } from '@/hooks/useHome'
|
import CheckInGrid from './components/CheckInGrid';
|
||||||
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 CrushCoinPage = () => {
|
||||||
const { data: signListData } = useGetSevenDaysSignList()
|
const { signInListData } = useSignIn();
|
||||||
|
const router = useRouter();
|
||||||
// 计算连续签到天数
|
const t = useTranslations('crushcoin');
|
||||||
const consecutiveDays = signListData?.continuousDays || 0
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-16">
|
<div className="px-4 sm:px-16">
|
||||||
<CrushcoinBackground />
|
<CrushcoinBackground />
|
||||||
<div className="relative z-1">
|
<div className="relative max-w-[752px] flex flex-col mx-auto">
|
||||||
<h1 className="txt-display-xl mt-14 text-center">Daily Free CrushCoins</h1>
|
<IconButton
|
||||||
<div className="txt-title-m mt-10 py-2 text-center">
|
variant="ghost"
|
||||||
{/* You have checked in for {consecutiveDays} Consecutive days Consecutive days */}
|
size="large"
|
||||||
You’ve checked in for {consecutiveDays} consecutive days.
|
onClick={() => router.back()}
|
||||||
|
className="absolute top-1 left-0 z-10"
|
||||||
|
>
|
||||||
|
<i className="iconfont icon-arrow-left !text-[16px]" />
|
||||||
|
</IconButton>
|
||||||
|
<h1 className="txt-display-m sm:txt-display-xl mt-14 text-center">{t('title')}</h1>
|
||||||
|
<div className="txt-title-xs sm:txt-title-m mt-10 py-2 text-center">
|
||||||
|
{t('consecutiveDays', { days: signInListData?.continuousDays || '-' })}
|
||||||
</div>
|
</div>
|
||||||
{/* 渐变分割线 */}
|
{/* 渐变分割线 */}
|
||||||
<GradientDivider />
|
<GradientDivider />
|
||||||
|
|
||||||
{/* 签到网格 */}
|
{/* 签到网格 */}
|
||||||
<div className="mx-auto mt-8 max-w-[752px]">
|
<div className="mt-8">
|
||||||
<CheckInGrid />
|
<CheckInGrid />
|
||||||
|
|
||||||
<div className="txt-body-m text-center text-txt-primary-normal mt-4">
|
<div className="txt-body-m text-center text-txt-primary-normal mt-4">
|
||||||
<p>Diamonds can be used to pay for chat services and unlock other items. </p>
|
<p>{t('description1')}</p>
|
||||||
<p>
|
<p>{t('description2')}</p>
|
||||||
If you miss a check-in, the check-in count will reset and start again from day one.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default CrushCoinPage
|
export default CrushCoinPage;
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ const Header = React.memo(() => {
|
||||||
const t = useTranslations('home');
|
const t = useTranslations('home');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// <Link href="/crushcoin">
|
<Link href="/crushcoin">
|
||||||
<div
|
<div
|
||||||
className="h-25 sm:h-50 rounded-2xl sm:rounded-4xl px-6 mb-12 flex items-center justify-between"
|
className="h-25 sm:h-50 rounded-2xl sm:rounded-4xl px-6 mb-12 flex items-center justify-between"
|
||||||
style={{
|
style={{
|
||||||
|
|
@ -55,10 +55,15 @@ const Header = React.memo(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{response?.lg && (
|
{response?.lg && (
|
||||||
<Image src="/images/home/banner-header.png" alt="banner-header" width={250} height={250} />
|
<Image
|
||||||
|
src="/images/home/banner-header.png"
|
||||||
|
alt="banner-header"
|
||||||
|
width={250}
|
||||||
|
height={250}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
// </Link>
|
</Link>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,11 @@
|
||||||
'use client'
|
'use client';
|
||||||
|
|
||||||
import React from 'react'
|
import { cn } from '@/lib/utils';
|
||||||
import { cn } from '@/lib/utils'
|
|
||||||
|
|
||||||
interface GradientDividerProps {
|
export function GradientDivider() {
|
||||||
className?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GradientDivider({ className }: GradientDividerProps) {
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('relative mx-auto max-w-[660px]', className)}>
|
<div className={cn('relative mx-auto max-w-[660px]')}>
|
||||||
<img src="/images/crushcoin/divider.png" className="absolute -top-[24px] mx-auto w-full" />
|
<img src="/images/crushcoin/divider.png" className="absolute -top-[24px] mx-auto w-full" />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
return (
|
|
||||||
<div className={cn('relative mx-auto my-4 h-[3px] max-w-[598px]', className)}>
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 rounded-full blur-lg"
|
|
||||||
style={{
|
|
||||||
background: '#D668F2',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="absolute inset-0 px-[20px]">
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 rounded-full blur-sm"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
'linear-gradient(90deg, rgba(255, 222, 222, 0.00) 0%, #FFE8E8 49.04%, rgba(255, 227, 227, 0.00) 100%)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -651,6 +651,13 @@
|
||||||
line-height: var(--glo-font-lineheight-size16);
|
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 {
|
@utility txt-bodySemibold-l {
|
||||||
font-family: var(--font-poppins);
|
font-family: var(--font-poppins);
|
||||||
font-size: var(--glo-font-size-16);
|
font-size: var(--glo-font-size-16);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -152,6 +152,18 @@ export default {
|
||||||
female: 'Female',
|
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: {
|
footer: {
|
||||||
slogan: "Grow your love story with Spicyxx.AI AI—From 'Hi' to 'I Do', sparked by every chat",
|
slogan: "Grow your love story with Spicyxx.AI AI—From 'Hi' to 'I Do', sparked by every chat",
|
||||||
features: 'Features',
|
features: 'Features',
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,17 @@ export default {
|
||||||
female: '女性',
|
female: '女性',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
crushcoin: {
|
||||||
|
title: '每日免费金币',
|
||||||
|
consecutiveDays: '你已经连续签到 {days} 天',
|
||||||
|
description1: '金币可用于支付聊天服务和解锁其他物品。',
|
||||||
|
description2: '如果错过签到,签到计数将重置并从第一天重新开始。',
|
||||||
|
day: '第 {day} 天',
|
||||||
|
checked: '已签到',
|
||||||
|
notStarted: '未开始',
|
||||||
|
loading: '签到中ing',
|
||||||
|
checked_success: '签到成功',
|
||||||
|
},
|
||||||
footer: {
|
footer: {
|
||||||
slogan: '用 Spicyxx.AI 成长你的爱情故事——从"你好"到"我愿意",每一次对话都点燃火花',
|
slogan: '用 Spicyxx.AI 成长你的爱情故事——从"你好"到"我愿意",每一次对话都点燃火花',
|
||||||
features: '功能',
|
features: '功能',
|
||||||
|
|
|
||||||
|
|
@ -114,9 +114,12 @@ function Topbar() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className={cn('flex h-16 w-full items-center justify-between px-4 sm:px-8 transition-all', {
|
className={cn(
|
||||||
|
'z-10 flex h-16 w-full items-center justify-between px-4 sm:px-8 transition-all',
|
||||||
|
{
|
||||||
'backdrop-blur-[10px]': shouldBlur,
|
'backdrop-blur-[10px]': shouldBlur,
|
||||||
})}
|
}
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{shouldBlur && <div className="bg-background-default absolute inset-0 opacity-85" />}
|
{shouldBlur && <div className="bg-background-default absolute inset-0 opacity-85" />}
|
||||||
<div className="relative inset-0 flex w-full items-center justify-between">
|
<div className="relative inset-0 flex w-full items-center justify-between">
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export const topbarRouteConfigs: Record<string, TopbarRouteConfig> = {
|
||||||
'/profile/account': { hideOnMobile: true },
|
'/profile/account': { hideOnMobile: true },
|
||||||
'/character/:id': { hideOnMobile: true },
|
'/character/:id': { hideOnMobile: true },
|
||||||
'/chat/:id': { enableBlur: true },
|
'/chat/:id': { enableBlur: true },
|
||||||
|
'/crushcoin': { hideOnMobile: true },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { editorRequest } from '@/lib/client';
|
import { editorRequest } from '@/lib/client';
|
||||||
import { LikeObjectParamsType } from './type';
|
import { LikeObjectParamsType, SignInListType } from './type';
|
||||||
|
|
||||||
export async function fetchCharacters({ index, limit, query }: any) {
|
export async function fetchCharacters({ index, limit, query }: any) {
|
||||||
const { data } = await editorRequest('/api/character/list', {
|
const { data } = await editorRequest('/api/character/list', {
|
||||||
|
|
@ -25,3 +25,12 @@ export async function thmubObject(params: LikeObjectParamsType) {
|
||||||
export async function getLikeStatus(params: Pick<LikeObjectParamsType, 'objectId' | 'userId'>) {
|
export async function getLikeStatus(params: Pick<LikeObjectParamsType, 'objectId' | 'userId'>) {
|
||||||
return editorRequest('/api/like/getLikeStatus', { method: 'POST', data: params });
|
return editorRequest('/api/like/getLikeStatus', { method: 'POST', data: params });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getSevenDaysSignList(params: any = {}): Promise<SignInListType> {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ export type CharacterType = {
|
||||||
[x: string]: any;
|
[x: string]: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 点赞点踩
|
||||||
export enum LikeTargetType {
|
export enum LikeTargetType {
|
||||||
Character = 0,
|
Character = 0,
|
||||||
Story = 1,
|
Story = 1,
|
||||||
|
|
@ -36,3 +37,13 @@ export type LikeObjectParamsType = {
|
||||||
userId?: number;
|
userId?: number;
|
||||||
likeType?: LikeType;
|
likeType?: LikeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 签到
|
||||||
|
export type SignInListType = {
|
||||||
|
continuousDays: number;
|
||||||
|
list: {
|
||||||
|
coinNum: number;
|
||||||
|
dayStr: string;
|
||||||
|
signIn: boolean;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue