import { count, eq, inArray } from "drizzle-orm";
import Image from "next/image";
import type { Session } from "next-auth";
import { getTranslations } from "next-intl/server";
import { signOutAndRevokeTicket } from "@/actions/sessions";
import { HeaderUserIdentity } from "@/components/header-user-identity";
import Link from "@/components/link";
import { LiveOnlineCount } from "@/components/live-online-count";
import { RoomQuickEntry } from "@/components/room-quick-entry";
import { UserAvatarThumbnail } from "@/components/shared/user-avatar-thumbnail";
import {
db,
MessengerFriendrequests,
MessengerOffline,
User,
UsersCurrency,
} from "@/lib/db";
import { resolveHotelName } from "@/lib/hotel-name";
import { canAccess, getApiAdminContext, PERMS } from "@/lib/permissions";
import { logServerError } from "@/lib/server-log";
import { cachedOnlineCount } from "@/lib/services/public-counters";
function Currency({
icon,
amount,
label,
}: {
icon: string;
amount: number;
label: string;
}) {
const textColor =
"var(--color-navbar-text-readable, var(--color-navbar-text))";
const valueColor =
"var(--color-navbar-text-readable, var(--color-navbar-text, var(--color-text)))";
return (
{amount.toLocaleString()}
{" "}
{label}
);
}
export async function TopHeader({ session }: { session: Session | null }) {
if (!session?.user?.id) return null;
const notificationText = await getTranslations("pages.notifications");
const id = Number(session.user.id);
let credits = 0;
let duckets = 0;
let diamonds = 0;
let look = "";
let showAdmin = false;
let showMod = false;
let hotelName = "";
let online = 0;
let friendRequests: { userFromId: number }[] = [];
let friendRequestUsers: {
id: number;
username: string;
look: string;
}[] = [];
// One round of queries instead of five sequential ones: the header is on
// every authenticated page, so each extra round-trip is paid on every view.
const [wallet, ctx, name, onlineCount, requests] = await Promise.all([
(async () => {
try {
const [user, currencies] = await Promise.all([
db
.select({ credits: User.credits, look: User.look })
.from(User)
.where(eq(User.id, id))
.limit(1),
db
.select({ type: UsersCurrency.type, amount: UsersCurrency.amount })
.from(UsersCurrency)
.where(eq(UsersCurrency.userId, id)),
]);
return user[0]
? { credits: user[0].credits, look: user[0].look, currencies }
: null;
} catch (error) {
logServerError("top-header.wallet_failed", error, { userId: id });
return null;
}
})(),
getApiAdminContext().catch(() => null),
resolveHotelName().catch(() => ""),
cachedOnlineCount().catch((error) => {
logServerError("top-header.online_count_failed", error);
return 0;
}),
db
.select({ userFromId: MessengerFriendrequests.userFromId })
.from(MessengerFriendrequests)
.where(eq(MessengerFriendrequests.userToId, id))
.catch(() => [] as { userFromId: number }[]),
]);
if (wallet) {
credits = wallet.credits;
look = wallet.look;
for (const c of wallet.currencies) {
if (c.type === 0) duckets = c.amount;
if (c.type === 5) diamonds = c.amount;
}
}
if (ctx) {
showAdmin = canAccess(
ctx.permissions,
PERMS.ADMIN_DASHBOARD,
ctx.session.user.rank,
);
showMod =
canAccess(ctx.permissions, PERMS.MOD_DASHBOARD, ctx.session.user.rank) ||
canAccess(
ctx.permissions,
PERMS.MODERATION_VIEW,
ctx.session.user.rank,
) ||
canAccess(ctx.permissions, PERMS.MOD_CFH_VIEW, ctx.session.user.rank) ||
canAccess(ctx.permissions, PERMS.MOD_ACTIONS, ctx.session.user.rank);
}
hotelName = name;
online = onlineCount;
friendRequests = requests;
if (friendRequests.length > 0) {
friendRequestUsers = await db
.select({ id: User.id, username: User.username, look: User.look })
.from(User)
.where(
inArray(
User.id,
friendRequests.map((r) => r.userFromId),
),
)
.catch(() => [] as { id: number; username: string; look: string }[]);
}
// The count and the rows are the same question, so derive it from the rows.
const pendingFriendRequests = friendRequests.length;
const textColor =
"var(--color-navbar-text-readable, var(--color-navbar-text, var(--color-text)))";
// Unread offline messages is the only count we still need on its own.
let unreadMessages = 0;
try {
const [row] = await db
.select({ total: count() })
.from(MessengerOffline)
.where(eq(MessengerOffline.userId, id));
unreadMessages = row?.total ?? 0;
} catch (error) {
logServerError("top-header.messenger_counts_failed", error, { userId: id });
}
const totalNotifications = unreadMessages + pendingFriendRequests;
return (
{totalNotifications > 0 && (
{totalNotifications > 99 ? "99+" : totalNotifications}
)}
{showAdmin || showMod ? (
Staff
Staff
{showMod ? (
Mod panel
) : null}
{showAdmin ? (
<>
Admin panel
Commandocentrum
Logs
>
) : null}
Logo generator
) : null}
My dashboard
My profile
{notificationText("title")}
User settings
Two-factor
Login activity
{pendingFriendRequests > 0 && (
<>
Friend requests ({pendingFriendRequests})
{friendRequestUsers.slice(0, 3).map((u) => (
{u.username}
))}
{friendRequestUsers.length > 3 && (
+{friendRequestUsers.length - 3} more
)}
>
)}
);
}