本次提交完成了多个核心功能迭代: 1. 新增用户每日签到系统,包含签到状态查询、连续天数统计、积分累计逻辑 2. 新增"新帖子"排序选项,替换原有的latest排序为新评论排序 3. 重构首页信息流布局,优化移动端与桌面端展示样式 4. 新增最新评论、最新注册用户模块,完善社区面板数据展示 5. 新增静态友情链接配置模块 6. 提取通用时间格式化工具函数,统一全站相对时间展示 7. 补充数据库签到模型与后端API接口,完善前后端交互链路
114 lines
3.7 KiB
TypeScript
114 lines
3.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import Link from "next/link";
|
|
import { CalendarDays, CalendarCheck, LogIn, Loader2 } from "lucide-react";
|
|
import { apiDoCheckin, type CheckinStatus } from "@/lib/api";
|
|
|
|
/**
|
|
* 每日签到卡(右栏)。
|
|
* initial 为 undefined 表示游客;否则为 SSR 注入的登录用户签到状态。
|
|
*/
|
|
export default function CheckinCard({ initial }: { initial?: CheckinStatus | null }) {
|
|
const [status, setStatus] = useState<CheckinStatus | null>(initial ?? null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
// 游客态
|
|
if (initial === undefined) {
|
|
return (
|
|
<section className="j13-module" aria-label="每日签到">
|
|
<div className="flex items-center gap-3">
|
|
<span
|
|
className="w-10 h-10 rounded-xl flex items-center justify-center shrink-0"
|
|
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
|
|
>
|
|
<CalendarDays size={19} />
|
|
</span>
|
|
<div className="min-w-0">
|
|
<p className="text-[14px] font-bold" style={{ color: "var(--ink)" }}>
|
|
每日签到
|
|
</p>
|
|
<p className="meta text-[12px] mt-0.5">登录后每日签到领积分</p>
|
|
</div>
|
|
</div>
|
|
<Link href="/login?redirect=/" className="btn btn-line btn-sm w-full justify-center mt-3.5">
|
|
<LogIn size={14} /> 登录后签到
|
|
</Link>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
const checked = status?.checked_today ?? false;
|
|
|
|
async function doCheckin() {
|
|
if (loading || checked) return;
|
|
setLoading(true);
|
|
setError("");
|
|
try {
|
|
setStatus(await apiDoCheckin());
|
|
} catch (e) {
|
|
setError(e instanceof Error ? e.message : "签到失败");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<section className="j13-module" aria-label="每日签到">
|
|
<div className="flex items-center gap-3">
|
|
<span
|
|
className="w-10 h-10 rounded-xl flex items-center justify-center shrink-0"
|
|
style={{
|
|
background: checked ? "var(--accent-soft)" : "var(--panel-2)",
|
|
color: checked ? "var(--accent)" : "var(--ink-2)",
|
|
}}
|
|
>
|
|
{checked ? <CalendarCheck size={19} /> : <CalendarDays size={19} />}
|
|
</span>
|
|
<div className="flex-1 min-w-0">
|
|
<p className="text-[14px] font-bold" style={{ color: "var(--ink)" }}>
|
|
每日签到
|
|
</p>
|
|
<p className="meta text-[12px] mt-0.5">
|
|
{checked
|
|
? `已连续签到 ${status?.streak ?? 0} 天 · 累计 ${status?.total_points ?? 0} 积分`
|
|
: `连续 ${status?.streak ?? 0} 天 · 今日可得 ${status?.today_points ?? 5} 积分`}
|
|
</p>
|
|
</div>
|
|
{!checked && (
|
|
<span className="text-[17px] font-extrabold tabular-nums shrink-0" style={{ color: "var(--accent)" }}>
|
|
+{status?.today_points ?? 5}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={doCheckin}
|
|
disabled={loading || checked}
|
|
aria-disabled={loading || checked}
|
|
className={`btn w-full justify-center mt-3.5 ${checked ? "btn-line" : "btn-primary"}`}
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<Loader2 size={15} className="animate-spin" /> 签到中…
|
|
</>
|
|
) : checked ? (
|
|
<>
|
|
<CalendarCheck size={15} /> 今日已签到
|
|
</>
|
|
) : (
|
|
"立即签到"
|
|
)}
|
|
</button>
|
|
|
|
{error && (
|
|
<p className="text-[12px] mt-2" style={{ color: "var(--clay)" }} role="alert">
|
|
{error}
|
|
</p>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|