151 lines
5.2 KiB
TypeScript
151 lines
5.2 KiB
TypeScript
import { CopyButton } from "@/components/copy-button";
|
|
import { buttonVariants } from "@/components/ui/button";
|
|
import { SubmitButton } from "@/components/submit-button";
|
|
import type { Metadata } from "next";
|
|
import { redirect } from "next/navigation";
|
|
|
|
import { formatLocalDateTime } from "@/lib/datetime";
|
|
import { getRequestOrigin } from "@/lib/request-origin";
|
|
import {
|
|
buildSubscriptionUrl,
|
|
describeRemainingTraffic,
|
|
} from "@/lib/subscription";
|
|
import { requireUserSession } from "@/lib/session";
|
|
import { formatBytes, getUserById } from "@/lib/store";
|
|
|
|
export const metadata: Metadata = {
|
|
title: "我的订阅",
|
|
};
|
|
|
|
export default async function MePage() {
|
|
const session = await requireUserSession();
|
|
const [user, origin] = await Promise.all([
|
|
getUserById(session.user_id),
|
|
getRequestOrigin(),
|
|
]);
|
|
|
|
if (!user) {
|
|
redirect("/login");
|
|
}
|
|
|
|
const usedBytes = user.used_tx_bytes + user.used_rx_bytes;
|
|
const subscriptionUrl = user.subscription_token
|
|
? buildSubscriptionUrl(user.subscription_token, origin)
|
|
: null;
|
|
const clashImportUrl = subscriptionUrl
|
|
? `clash://install-config?url=${encodeURIComponent(subscriptionUrl)}`
|
|
: null;
|
|
|
|
return (
|
|
<main className="me-shell">
|
|
<section className="me-stage">
|
|
<article className="me-panel me-hero">
|
|
<div className="me-hero-copy">
|
|
<span className="eyebrow">账户中心</span>
|
|
<h1>{user.username}</h1>
|
|
<div className="badge-row">
|
|
<span
|
|
className={`status-pill ${
|
|
user.enabled ? "status-pill--ok" : "status-pill--danger"
|
|
}`}
|
|
>
|
|
{user.enabled ? "账号已启用" : "账号已禁用"}
|
|
</span>
|
|
<span className="status-pill status-pill--neutral">
|
|
{formatLocalDateTime(user.expires_at, "长期有效")}
|
|
</span>
|
|
<span className="status-pill status-pill--warm">
|
|
{describeRemainingTraffic(user)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="me-identity-card">
|
|
<span className="feature-kicker">用户 ID</span>
|
|
<strong className="mono break-all">{user.auth_id}</strong>
|
|
<p className="muted">
|
|
订阅名称:{user.subscription_label || user.username}
|
|
</p>
|
|
</div>
|
|
</article>
|
|
|
|
<section className="me-grid">
|
|
<article className="me-panel me-subscription-card">
|
|
<div className="section-title section-title--top">
|
|
<div>
|
|
<h2>订阅</h2>
|
|
</div>
|
|
<span className="status-pill status-pill--ink">
|
|
Clash / Mihomo
|
|
</span>
|
|
</div>
|
|
<div className="me-action-row">
|
|
{clashImportUrl ? (
|
|
<a
|
|
className={buttonVariants({ size: "sm" })}
|
|
href={clashImportUrl}
|
|
>
|
|
一键导入
|
|
</a>
|
|
) : (
|
|
<span className="status-pill status-pill--off">
|
|
订阅暂不可用
|
|
</span>
|
|
)}
|
|
{subscriptionUrl ? (
|
|
<a
|
|
className={buttonVariants({ variant: "outline", size: "sm" })}
|
|
href={subscriptionUrl}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
打开订阅链接
|
|
</a>
|
|
) : null}
|
|
</div>
|
|
<div className="me-link-card">
|
|
<span className="feature-kicker">订阅链接</span>
|
|
<div className="copy-line">
|
|
<code>{subscriptionUrl ?? "暂不可用"}</code>
|
|
{subscriptionUrl && <CopyButton value={subscriptionUrl} />}
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<div className="me-side-stack">
|
|
<article className="me-panel">
|
|
<div className="section-title">
|
|
<h2>流量概览</h2>
|
|
</div>
|
|
<div className="me-metrics-grid">
|
|
<div className="me-metric-card me-metric-card--paper">
|
|
<span>总用量</span>
|
|
<strong>{formatBytes(usedBytes)}</strong>
|
|
</div>
|
|
<div className="me-metric-card me-metric-card--warm">
|
|
<span>剩余额度</span>
|
|
<strong>{describeRemainingTraffic(user)}</strong>
|
|
</div>
|
|
<div className="me-metric-card me-metric-card--ink">
|
|
<span>上传</span>
|
|
<strong>{formatBytes(user.used_tx_bytes)}</strong>
|
|
</div>
|
|
<div className="me-metric-card me-metric-card--sage">
|
|
<span>下载</span>
|
|
<strong>{formatBytes(user.used_rx_bytes)}</strong>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
|
|
<article className="me-panel me-support-card">
|
|
<h2>账户</h2>
|
|
<form action="/logout" method="post">
|
|
<SubmitButton variant="outline">退出登录</SubmitButton>
|
|
</form>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|