125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
"use client";
|
||
|
||
import { motion } from "framer-motion";
|
||
import { useSession } from "next-auth/react";
|
||
import { useEffect, useState } from "react";
|
||
|
||
type QuotaInfo = {
|
||
used: number;
|
||
limit: number | null;
|
||
remaining: number | null;
|
||
unlimited: boolean;
|
||
percentage: number;
|
||
};
|
||
|
||
export const Greeting = () => {
|
||
const { data: session } = useSession();
|
||
const [imageCount, setImageCount] = useState<number | null>(null);
|
||
const [quota, setQuota] = useState<QuotaInfo | null>(null);
|
||
|
||
// 获取用户名或邮箱前缀
|
||
const getUserDisplayName = () => {
|
||
if (session?.user?.name) {
|
||
return session.user.name;
|
||
}
|
||
if (session?.user?.email) {
|
||
// 如果没有名字,使用邮箱的前缀部分
|
||
return session.user.email.split('@')[0];
|
||
}
|
||
return "";
|
||
};
|
||
|
||
const displayName = getUserDisplayName();
|
||
|
||
// 获取用户生图统计数据和配额信息
|
||
useEffect(() => {
|
||
const fetchData = async () => {
|
||
try {
|
||
// 获取生图统计
|
||
const statsResponse = await fetch("/api/user/image-stats");
|
||
if (statsResponse.ok) {
|
||
const statsData = await statsResponse.json();
|
||
setImageCount(statsData.totalImages);
|
||
}
|
||
|
||
// 获取配额信息
|
||
const quotaResponse = await fetch("/api/user/image-quota");
|
||
if (quotaResponse.ok) {
|
||
const quotaData = await quotaResponse.json();
|
||
setQuota(quotaData);
|
||
}
|
||
} catch (error) {
|
||
console.error("Failed to fetch data:", error);
|
||
}
|
||
};
|
||
|
||
if (session?.user?.id) {
|
||
fetchData();
|
||
}
|
||
}, [session?.user?.id]);
|
||
|
||
// 获取进度条颜色
|
||
const getProgressColor = (percentage: number) => {
|
||
if (percentage >= 90) return "bg-red-500";
|
||
if (percentage >= 70) return "bg-yellow-500";
|
||
return "bg-green-500";
|
||
};
|
||
|
||
return (
|
||
<div
|
||
className="mx-auto mt-4 flex size-full max-w-3xl flex-col justify-center px-4 md:mt-16 md:px-8"
|
||
key="overview"
|
||
>
|
||
<motion.div
|
||
animate={{ opacity: 1, y: 0 }}
|
||
className="font-semibold text-xl md:text-2xl"
|
||
exit={{ opacity: 0, y: 10 }}
|
||
initial={{ opacity: 0, y: 10 }}
|
||
transition={{ delay: 0.5 }}
|
||
>
|
||
你好{displayName && `,${displayName}`},很高兴见到你
|
||
{imageCount !== null && imageCount > 0 && (
|
||
<span className="ml-2 text-base font-normal text-zinc-500">
|
||
(截止今日已为您处理 <span className="font-semibold text-green-600">{imageCount.toLocaleString()}</span> 张图)
|
||
</span>
|
||
)}
|
||
</motion.div>
|
||
<motion.div
|
||
animate={{ opacity: 1, y: 0 }}
|
||
className="text-xl text-zinc-500 md:text-2xl"
|
||
exit={{ opacity: 0, y: 10 }}
|
||
initial={{ opacity: 0, y: 10 }}
|
||
transition={{ delay: 0.6 }}
|
||
>
|
||
今天我能为你做些什么?
|
||
</motion.div>
|
||
|
||
{/* 配额进度条 - 仅当有限额时显示 */}
|
||
{quota && !quota.unlimited && quota.limit && (
|
||
<motion.div
|
||
animate={{ opacity: 1, y: 0 }}
|
||
initial={{ opacity: 0, y: 10 }}
|
||
transition={{ delay: 0.7 }}
|
||
className="mt-4"
|
||
>
|
||
<div className="text-sm text-zinc-500 mb-1">
|
||
本月出图:{quota.used}/{quota.limit} 张
|
||
{quota.remaining !== null && quota.remaining <= 10 && quota.remaining > 0 && (
|
||
<span className="ml-2 text-yellow-600">(剩余 {quota.remaining} 张)</span>
|
||
)}
|
||
{quota.remaining === 0 && (
|
||
<span className="ml-2 text-red-600">(配额已用完)</span>
|
||
)}
|
||
</div>
|
||
<div className="w-64 h-2 bg-zinc-200 dark:bg-zinc-700 rounded-full overflow-hidden">
|
||
<div
|
||
className={`h-full rounded-full transition-all ${getProgressColor(quota.percentage)}`}
|
||
style={{ width: `${Math.min(quota.percentage, 100)}%` }}
|
||
/>
|
||
</div>
|
||
</motion.div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|