mirror of
https://github.com/lukevella/rallly.git
synced 2025-08-03 16:38:34 +02:00
137 lines
4.2 KiB
TypeScript
137 lines
4.2 KiB
TypeScript
import { PageIcon } from "@/app/components/page-icons";
|
|
import { requireAdmin } from "@/auth/queries";
|
|
import {
|
|
FullWidthLayout,
|
|
FullWidthLayoutContent,
|
|
FullWidthLayoutHeader,
|
|
FullWidthLayoutTitle,
|
|
} from "@/components/full-width-layout";
|
|
import { Trans } from "@/components/trans";
|
|
import { getLicense } from "@/features/licensing/queries";
|
|
import { prisma } from "@rallly/database";
|
|
import { cn } from "@rallly/ui";
|
|
import { Tile, TileGrid, TileTitle } from "@rallly/ui/tile";
|
|
import {
|
|
GaugeIcon,
|
|
KeySquareIcon,
|
|
SettingsIcon,
|
|
UsersIcon,
|
|
} from "lucide-react";
|
|
import Link from "next/link";
|
|
|
|
async function loadData() {
|
|
await requireAdmin();
|
|
|
|
const [userCount, license] = await Promise.all([
|
|
prisma.user.count(),
|
|
getLicense(),
|
|
]);
|
|
|
|
return {
|
|
userCount,
|
|
userLimit: license?.seats ?? 1,
|
|
tier: license?.type,
|
|
};
|
|
}
|
|
|
|
export default async function AdminPage() {
|
|
const { userCount, userLimit, tier } = await loadData();
|
|
return (
|
|
<FullWidthLayout>
|
|
<FullWidthLayoutHeader>
|
|
<FullWidthLayoutTitle
|
|
icon={
|
|
<PageIcon size="sm" color="indigo">
|
|
<GaugeIcon />
|
|
</PageIcon>
|
|
}
|
|
>
|
|
<Trans i18nKey="controlPanel" defaults="Control Panel" />
|
|
</FullWidthLayoutTitle>
|
|
</FullWidthLayoutHeader>
|
|
<FullWidthLayoutContent>
|
|
<div className="space-y-4">
|
|
<h2 className="text-muted-foreground text-sm">
|
|
<Trans i18nKey="homeNavTitle" defaults="Navigation" />
|
|
</h2>
|
|
<TileGrid>
|
|
{/* USERS */}
|
|
<Tile asChild>
|
|
<Link href="/control-panel/users">
|
|
<div className="flex justify-between">
|
|
<div>
|
|
<PageIcon color="darkGray">
|
|
<UsersIcon />
|
|
</PageIcon>
|
|
<TileTitle>
|
|
<Trans i18nKey="users" defaults="Users" />
|
|
</TileTitle>
|
|
</div>
|
|
<div className="text-muted-foreground text-sm">
|
|
<span
|
|
className={cn({
|
|
"text-destructive":
|
|
userLimit !== null && userCount > userLimit,
|
|
})}
|
|
>
|
|
<Trans
|
|
i18nKey="userCount"
|
|
defaults="{count, number, ::compact-short}"
|
|
values={{ count: userCount }}
|
|
/>
|
|
/
|
|
{userLimit === Number.POSITIVE_INFINITY
|
|
? "unlimited"
|
|
: userLimit}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</Link>
|
|
</Tile>
|
|
{/* LICENSE */}
|
|
<Tile asChild>
|
|
<Link href="/control-panel/license">
|
|
<div className="flex justify-between">
|
|
<PageIcon color="darkGray">
|
|
<KeySquareIcon />
|
|
</PageIcon>
|
|
{tier ? (
|
|
<span className="text-primary text-sm capitalize">
|
|
{tier}
|
|
</span>
|
|
) : (
|
|
<span className="text-muted-foreground text-sm">
|
|
<Trans i18nKey="unlicensed" defaults="Unlicensed" />
|
|
</span>
|
|
)}
|
|
</div>
|
|
<TileTitle>
|
|
<Trans i18nKey="license" defaults="License" />
|
|
</TileTitle>
|
|
</Link>
|
|
</Tile>
|
|
{/* INSTANCE SETTINGS */}
|
|
<Tile asChild>
|
|
<Link href="/control-panel/settings">
|
|
<div className="flex justify-between">
|
|
<PageIcon color="darkGray">
|
|
<SettingsIcon />
|
|
</PageIcon>
|
|
</div>
|
|
<TileTitle>
|
|
<Trans i18nKey="settings" defaults="Settings" />
|
|
</TileTitle>
|
|
</Link>
|
|
</Tile>
|
|
</TileGrid>
|
|
</div>
|
|
</FullWidthLayoutContent>
|
|
</FullWidthLayout>
|
|
);
|
|
}
|
|
|
|
export async function generateMetadata() {
|
|
return {
|
|
title: "Control Panel",
|
|
};
|
|
}
|