import type { ReactNode } from "react";
import Link from "next/link";
import { chatGPTSignInPath, chatGPTSignOutPath } from "../chatgpt-auth";
import { getCloudViewer, type CloudViewer } from "../cloud-auth";
import {
getActiveBeta,
getServiceStatusSnapshot,
type ServiceStatusSnapshot,
} from "@/db/repository";
import { ConnectivityBanner } from "./ConnectivityBanner";
const serviceStatusLabels = {
operational: "服务正常",
maintenance: "计划维护",
degraded: "服务降级",
outage: "服务中断",
} as const;
export function Brand({ compact = false }: { compact?: boolean }) {
return (
{!compact && (
NekoNest
Cloud
)}
);
}
export function StatusPill({
tone = "neutral",
children,
}: {
tone?: "good" | "warn" | "danger" | "neutral" | "info";
children: ReactNode;
}) {
return {children};
}
export async function PublicShell({
children,
serviceStatus: providedServiceStatus,
}: {
children: ReactNode;
serviceStatus?: ServiceStatusSnapshot;
}) {
const [viewer, beta, serviceStatus] = await Promise.all([
getCloudViewer(),
getActiveBeta(),
providedServiceStatus ?? getServiceStatusSnapshot(),
]);
return (
);
}
const dashboardNav = [
{ href: "/dashboard", label: "总览", icon: "⌂" },
{ href: "/dashboard/hosts", label: "主机", icon: "▣" },
{ href: "/dashboard/billing", label: "公测权益", icon: "○" },
{ href: "/dashboard/security", label: "安全与设备", icon: "◇" },
{ href: "/dashboard/feedback", label: "问题反馈", icon: "?" },
] as const;
export async function DashboardShell({
viewer,
active,
children,
serviceStatus: providedServiceStatus,
}: {
viewer: CloudViewer;
active: string;
children: ReactNode;
serviceStatus?: ServiceStatusSnapshot;
}) {
const serviceStatus =
providedServiceStatus ?? (await getServiceStatusSnapshot());
return (
{serviceStatus.activeIncidents.length > 0 && (
{serviceStatusLabels[serviceStatus.status]}
{serviceStatus.activeIncidents[0].title}
查看详情 →
)}
{viewer.isLocalDemo && (
当前使用本地演示身份;正式托管环境会要求登录,演示数据不会代表真实在线服务。
)}
{children}
);
}
export function PageHeading({
eyebrow,
title,
description,
actions,
}: {
eyebrow?: string;
title: string;
description: string;
actions?: ReactNode;
}) {
return (
{eyebrow &&
{eyebrow}}
{title}
{description}
{actions && {actions}
}
);
}
export function formatMoney(amountMinor: number, currency = "CNY") {
return new Intl.NumberFormat("zh-CN", {
style: "currency",
currency,
minimumFractionDigits: 0,
maximumFractionDigits: 2,
}).format(amountMinor / 100);
}
export function formatDate(value: string | null, includeTime = false) {
if (!value) return "未设定";
return new Intl.DateTimeFormat("zh-CN", {
year: "numeric",
month: "short",
day: "numeric",
...(includeTime ? { hour: "2-digit", minute: "2-digit" } : {}),
}).format(new Date(value));
}