降权全站字重;优化右栏公告/数据块;类型状态徽章用固定浅色区分;TipHost 仅在文案不一致、非文字或省略时显示。 Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
3.3 KiB
TypeScript
104 lines
3.3 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 function PulseCheckin({ initial }: { initial?: CheckinStatus | null }) {
|
|
const [status, setStatus] = useState<CheckinStatus | null>(initial ?? null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState("");
|
|
|
|
const checked = status?.checked_today ?? false;
|
|
const guest = initial === undefined;
|
|
|
|
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 (
|
|
<div aria-label="每日签到">
|
|
<div className="flex items-center gap-2.5">
|
|
<span
|
|
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg"
|
|
style={{
|
|
background: checked || guest ? "var(--accent-soft)" : "var(--tint)",
|
|
color: checked || guest ? "var(--accent)" : "var(--ink-2)",
|
|
}}
|
|
>
|
|
{checked ? <CalendarCheck size={16} /> : <CalendarDays size={16} />}
|
|
</span>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-[12.5px] leading-tight" style={{ color: "var(--ink)" }}>
|
|
每日签到
|
|
</p>
|
|
<p className="meta mt-0.5 truncate text-[11px]">
|
|
{guest
|
|
? "登录后每日签到领积分"
|
|
: checked
|
|
? `连签 ${status?.streak ?? 0} 天 · 余额 ${status?.total_points ?? 0}`
|
|
: `连签 ${status?.streak ?? 0} 天 · 今日 +${status?.today_points ?? 5}`}
|
|
{!guest && (
|
|
<>
|
|
{" · "}
|
|
<Link href="/points" className="hover:text-[var(--accent)] transition-colors">
|
|
流水
|
|
</Link>
|
|
</>
|
|
)}
|
|
</p>
|
|
</div>
|
|
{guest ? (
|
|
<Link
|
|
href="/login?redirect=/"
|
|
className="btn btn-line btn-sm j13-pulse-checkin-btn shrink-0"
|
|
>
|
|
<LogIn size={13} /> 登录
|
|
</Link>
|
|
) : checked ? (
|
|
<span
|
|
className="j13-pulse-checkin-done shrink-0"
|
|
style={{ background: "var(--accent-soft)", color: "var(--accent)" }}
|
|
>
|
|
<CalendarCheck size={12.5} /> 已签
|
|
</span>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={doCheckin}
|
|
disabled={loading}
|
|
className="btn btn-primary btn-sm j13-pulse-checkin-btn shrink-0"
|
|
>
|
|
{loading ? (
|
|
<>
|
|
<Loader2 size={13} className="animate-spin" /> 签到中
|
|
</>
|
|
) : (
|
|
`签到 +${status?.today_points ?? 5}`
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
{error && (
|
|
<p className="mt-1.5 text-[11.5px]" style={{ color: "var(--clay)" }} role="alert">
|
|
{error}
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|